Modifiers
The modifiers form the building block of the code. They determine how the atoms are modified during each basin hopping step. The code provides basic modifiers as building blocks for more complex modifiers.
Many modifiers support unique_method and unique_depth for controlling how duplicate structures are filtered.
Add Monodentate
The modifier can add a monodentate adsorbate, or moiety at specific sites on the parent atoms object.
from gg.modifiers import Add
from gg.predefined_sites import FlexibleSites
from ase.build import fcc111
atoms = fcc111("Pt", size=(3, 3 , 4), vacuum=10.0)
FS = FlexibleSites(constraints=True,max_bond_ratio=1.2) #Define class to figure out surface
add_OH = Add(FS, ads="OH", surf_coord=[1,2,3], ads_id=["O"], surf_sym=["Pt"], print_movie=True, unique=True)
modified_atoms = add_OH.get_modified_atoms(atoms) #Atoms with the adsorbate
- class gg.modifiers.add.Add(surface_sites: Sites, ads: str, surf_coord: int, surf_sym: list, ads_id: str = None, ads_dist: float = None, print_movie: bool = False, unique: bool = True, ads_rotate: bool = True, weight: float = 1, normal_method: str = 'svd', unique_method: str = 'fullgraph', unique_depth: int = 3, tag: bool = True)[source]
Bases:
ParentModifier- Args:
surface_sites (gg.Sites): Class that figures out surface sites
ads (str) or (ase.Atoms): Adsorbate to add
surf_coord (list[int]): How many bonds the adsorbate will make with the surface
surf_sym (list[str]): Surface elements where adsorbate can add
ads_id (list[float]): Strings denoting chemical symbol of adsorbate atom Defaults to None
ads_dist (str, optional): Distance of adsorbate from substrate. Defaults to covalent radii of ads_id from ase database.
print_movie (bool, optional): return a movie of all sites or one random site. Defaults to False.
unique (bool, optional): return only unique sites. Defaults to True.
normal_method (str): Determines how normals are calculated. It could be “svd” or “mean” Defaults to “svd”
unique_method (str): Determines how uniqueness is calculated. User can specify atom symbols/mol to construct subgraphs e.g: unique_method = [“CO”] Defaults to “fullgraph”
unique_depth (int): Determines the depth of subgraphs created to calculate uniqueness. Defaults to 3. If unique_method is “fullgraph” the value is ignored
tag (bool): add to tag=-1 to the adsorbate (imp for clusters) Defaults to True.
weight (float): weight for gcbh. Defaults to 1.
Add Bidentate
The modifier can add a bidentate adsorbate, or moiety at specific sites on the parent atoms object.
from gg.modifiers import AddBi
from gg.predefined_sites import FlexibleSites
from ase.build import fcc111
atoms = fcc111("Pt", size=(3, 3 , 4), vacuum=10.0)
FS = FlexibleSites(constraints=True,max_bond_ratio=1.2) #Define class to figure out surface
add_formate = AddBi(FS, ads="HCOO", surf_coord=[1,2,3], ads_id=["O"], surf_sym=["Pt"], print_movie=True, unique=True)
modified_atoms = add_formate.get_modified_atoms(atoms) #Atoms with the adsorbate
- class gg.modifiers.add.AddBi(surface_sites: Sites, ads: str, surf_coord: int, surf_sym: list, ads_id: list, ads_dist: float | str = None, print_movie: bool = False, unique: bool = True, ads_rotate: bool = True, add_ads_error: float = 0.5, normal_method: str = 'mean', tag: bool = True, unique_method: str = 'fullgraph', unique_depth: int = 3, weight: float = 1)[source]
Bases:
AddArgs:
surface_sites (gg.Sites): Class that figures out surface sites.
ads (str) or (ase.Atoms): Adsorbate to add.
surf_coord (list[int]): How many bonds the adsorbate will make with the surface.
surf_sym (list[str]): Surface elements where adsorbate can add.
ads_id (list of [int or str]): Strings denoting chemical symbol of adsorbate atom.
ads_dist (list of [float or str, optional]): Distance of adsorbate from surface site. If its string denoting chemical symbol of adsorbate atom, then distance is set by atomic radii. Defaults to covalent radii of atoms mentioned in ads_id.
print_movie (bool, optional): Return a movie of all sites or one random site. Defaults to False.
unique (bool, optional): Return only unique sites. Defaults to True.
ads_rotate (bool,optional): Rotate atoms such that they point in +z direction. Defaults to True.
add_ads_error (float): The error in distance between bidentate adsorbate sites. Defaults to 0.5 (equivalent to 50%)
normal_method (str): Determines how normals are calculated. It could be “svd” or “mean” Defaults to “mean”
unique_method (str): Determines how uniqueness is calculated. User can specify atom symbols/mol to construct subgraphs e.g: unique_method = [“C”] Defaults to “fullgraph”
unique_depth (int): Determines the depth of subgraphs created to calculate uniqueness. Defaults to 3. If unique_method is “fullgraph” the value is ignored
tag (bool): add to tag=-1 to the adsorbate (imp for clusters) Defaults to 1.
weight (float): weight for gcbh. Defaults to 1.
Remove Adsorbate
Remove an adsorbate from the surface
from gg.modifiers import Remove
from gg.predefined_sites import FlexibleSites
FS = FlexibleSites(constraints=True,max_bond_ratio=1.2) #Define class to figure out surface
remove_OH = Remove(FS, to_del="OH", print_movie=True)
atoms = read("POSCAR_with_OH") #The atoms object that has OHs to be removed
modified_atoms = remove_OH.get_modified_atoms(atoms) #Atoms with the adsorbate
- class gg.modifiers.modifiers.Remove(surface_sites: Sites, to_del: Atoms | str, max_bond_ratio: float = 1.2, max_bond: float = 0, allow_external_symbols: Iterable[str] | None = None, max_external_neighbors: int | None = None, print_movie: bool = False, unique: bool = False, unique_method: str = 'fullgraph', unique_depth: int = 3, weight: float = 1)[source]
Bases:
ParentModifier- Args:
surface_sites (gg.Sites): Class that figures out surface sites
to_del (str) or (ase.Atoms): Atoms to delete. If a string is provided, it tries to make a molecule out of it.
max_bond_ratio (float, optional): Max bond ratio to make graph of atoms to delete. Defaults to 1.2.
max_bond (int, optional): Max bond ratio to make graph of atoms to delete. Defaults to 0.
allow_external_symbols (Iterable[str], optional): Symbols allowed to be bonded to the target subgraph but not part of it. Defaults to None.
max_external_neighbors (int, optional): Maximum number of external neighbors allowed for the target subgraph. Defaults to None.
print_movie (bool, optional): Return a movie of all sites or one random site. Defaults to False.
unique (bool, optional): Return only unique sites.
weight (float): weight for gcbh. Defaults to 1.
Replace Atoms/Molecules
Remove an adsorbate from the surface
from gg.modifiers import Replace
from gg.predefined_sites import FlexibleSites
from ase.io import read
FS = FlexibleSites(constraints=True,max_bond_ratio=1.2) #Define class to figure out surface
remove_OH = Replace(FS, to_del="OH", with_replace="NO", print_movie=True)
atoms = read("POSCAR_with_OH") #The atoms object that has OHs to be removed
modified_atoms = remove_OH.get_modified_atoms(atoms) #Atoms with the adsorbate
- class gg.modifiers.modifiers.Replace(surface_sites: Sites, to_del: Atoms | str, with_replace: Atoms | str, max_bond_ratio: float = 1.2, max_bond: float = 0, print_movie: bool = False, unique: bool = True, unique_method: str = 'fullgraph', unique_depth: int = 3, weight: float = 1)[source]
Bases:
Remove- Args:
surface_sites (gg.Sites): Class that figures out surface sites
to_del (str) or (ase.Atoms): Atoms to delete. If a string is provided, it tries to make a molecule out of it.
with_replace (str) or (ase.Atoms): Atoms to replace with. If a string is provided, it tries to make a molecule out of it.
max_bond_ratio (float, optional): Max bond ratio to make graph of atoms to delete. Defaults to 1.2.
max_bond (int, optional): Max bond ratio to make graph of atoms to delete. Defaults to 0.
print_movie (bool, optional): Return a movie of all sites or one random site. Defaults to False.
unique (bool, optional): Return only unique sites.
unique_method (str): Determines how uniqueness is calculated. User can specify atom symbols/mol to construct subgraphs e.g: unique_method = [“C”] Defaults to “fullgraph”
unique_depth (int): Determines the depth of subgraphs created to calculate uniqueness. Defaults to 3. If unique_method is “fullgraph” the value is ignored
weight (float): weight for gcbh. Defaults to 1.
Swap Atoms
Swap two atoms on the surface
from gg.modifiers import Swap
from gg.predefined_sites import FlexibleSites
from ase.io import read
FS = FlexibleSites(constraints=True,max_bond_ratio=1.2) #Define class to figure out surface
swap = Swap(FS, swap_sym=["Pt","Au"], print_movie=True)
atoms = read("POSCAR_PtAu") #The atoms object with Pt and Au that can be swapped
modified_atoms = swap.get_modified_atoms(atoms) #Atoms with the adsorbate
- class gg.modifiers.modifiers.Swap(surface_sites: Sites, swap_sym: list, swap_ind: list = None, print_movie: bool = False, unique: bool = True, unique_method: str = 'fullgraph', unique_depth: int = 3, weight: float = 1)[source]
Bases:
ParentModifier- Args:
surface_sites (gg.Sites): Class which figures out surface sites
swap_sym (list): List of atom symbols that are allowed to swap
swap_ind (list): List of indices to swap. Default to None.
print_movie (bool, optional): Return a movie of all sites or one random site. Defaults to False.
unique (bool, optional): Return only unique sites. Defaults to True
unique_method (str): Determines how uniqueness is calculated. User can specify atom symbols/mol to construct subgraphs e.g: unique_method = [“C”] Defaults to “fullgraph”
unique_depth (int): Determines the depth of subgraphs created to calculate uniqueness. Defaults to 3. If unique_method is “fullgraph” the value is ignored
weight (float): weight for gcbh. Defaults to 1.
Cluster Rotate
Rotate a cluster of atoms
Make sure to tag the cluster atoms using atom.tag = -1
from gg.modifiers import ClusterRotate
from gg.predefined_sites import FlexibleSites
from ase.io import read
atoms = read("POSCAR_Pt_TiO2")
for a in atoms:
if a.symbol=='Pt':
a.tag=-1
FS = FlexibleSites(tag=-1)
rotate = ClusterRotate(FS,contact_error=0.2,rotate_vector=(0,0,1))
modified_atoms = rotate.get_modified_atoms(atoms)
- class gg.modifiers.cluster.ClusterRotate(surface_sites: Sites, max_angle: int = 180, rotate_vector: tuple = None, contact_error: float = 0.2, weight: float = 1, nmovie: int = 1, print_movie: bool = False, unique: bool = True, unique_method: str = 'fullgraph', unique_depth: int = 3)[source]
Bases:
ParentModifier- Args:
surface_sites (gg.Sites): Class that figures out surface sites.
max_angle (float): Maximum rotation angle allowed (degrees). Defaults to 180.
rotate_vector (list/tuple): The vector along which the atoms are rotated. If None, the vector normal to the surface is selected. Defaults to None.
contact_error: Allowable tolerance in atoms touching Defaults to 0.2.
weight (float): weight for gcbh. Defaults to 1.
Cluster Translate
Translate a cluster of atoms
Make sure to tag the cluster atoms using atom.tag = -1
from gg.modifiers import ClusterTranslate
from gg.predefined_sites import FlexibleSites
from ase.io import read
atoms = read("POSCAR_Pt_TiO2")
for a in atoms:
if a.symbol=='Pt':
a.tag=-1
FS = FlexibleSites(tag=-1)
trans = ClusterTranslate(FS,contact_error=0.2)
modified_atoms = trans.get_modified_atoms(atoms)
- class gg.modifiers.cluster.ClusterTranslate(surface_sites: Sites, max_displace: float = 5, allowed_direction: tuple = (True, True, False), contact_error: float = 0.2, weight: float = 1, nmovie: int = 1, print_movie: bool = False, unique: bool = True, unique_method: str = 'fullgraph', unique_depth: int = 3)[source]
Bases:
ParentModifier- Args:
surface_sites (gg.Sites): Class that figures out surface sites.
max_displace (float): Maximum displacement allowed (angstrom). Defaults to 5.
allowed_direction (tuple(bool)): To allow displacement in x,y,x direction Defaults to (True, True, False), surface movement
contact_error: Allowable tolerance in atoms touching Defaults to 0.2.
weight (float): weight for gcbh. Defaults to 1.
Modifier Adder
Combine multiple modifiers into a single sequential modifier. The following example shows how two add modifiers can be combined to create a new dissociatively H2O adsorption modifier.
from gg.modifiers import Add, ModifierAdder
from gg.predefined_sites import FlexibleSites
from ase.build import fcc111
atoms = fcc111("Pt", size=(3, 3 , 4), vacuum=10.0)
FS = FlexibleSites(max_bond_ratio=1.2,com=0.5)
add_H = Add(FS, ads="H", surf_coord=[1], ads_id=["H"], surf_sym=["Pt","O"],print_movie=True)
add_OH = Add(FS, ads="OH", surf_coord=[1], ads_id=["O"], surf_sym=["Pt"],print_movie=True)
add_H2O = ModifierAdder([add_OH, add_H], print_movie=True, unique=True)
modified_atoms = add_H2O.get_modified_atoms(atoms)
- class gg.modifiers.modifiers.ModifierAdder(modifier_instances: list[ParentModifier], max_bond_ratio: float = 1.2, max_bond: float = 0, print_movie: bool = False, unique: bool = True, unique_method: str = 'fullgraph', unique_depth: int = 3, weight: float = 1)[source]
Bases:
ParentModifier- Args:
modifier_instance ([gg.ParentModifier]): List of Modifier instances that need to be added Required input
max_bond_ratio (float, optional): Max bond ratio to make graph of atoms to delete. Defaults to 1.2.
max_bond (int, optional): Max bond ratio to make graph of atoms to delete. Defaults to 0.
print_movie (bool, optional): Return a movie of all sites or one random site. Defaults to False.
unique (bool, optional): Return only unique sites.
unique_method (str): Determines how uniqueness is calculated. User can specify atom symbols/mol to construct subgraphs e.g: unique_method = [“C”] Defaults to “fullgraph”
unique_depth (int): Determines the depth of subgraphs created to calculate uniqueness. Defaults to 3. If unique_method is “fullgraph” the value is ignored
weight (float): weight for gcbh. Defaults to 1.
Creating New Modifiers with ParentModifier
You can implement custom modifiers by inheriting from ParentModifier and defining
get_modified_atoms.
Use this pattern:
from ase import Atoms
from gg.modifiers.modifiers import ParentModifier
from gg.utils import NoReasonableStructureFound
class RaiseTopLayer(ParentModifier):
"""Example custom modifier that shifts selected atoms in +z."""
def __init__(self, z_shift: float = 0.1, weight: float = 1.0):
super().__init__(weight)
self.z_shift = z_shift
def get_modified_atoms(self, atoms: Atoms) -> Atoms:
# Always set self.atoms first so string/Atoms inputs are normalized
self.atoms = atoms
# Modify a copied Atoms object via self.atoms
top_z = max(a.position[2] for a in self.atoms)
for atom in self.atoms:
if atom.position[2] > top_z - 0.5:
atom.position[2] += self.z_shift
# Raise NoReasonableStructureFound when the move is invalid
# raise NoReasonableStructureFound("No valid perturbation found")
return self.atoms
Key points when implementing a new modifier:
Call
super().__init__(weight)in your custom__init__.Set
self.atoms = atomsat the top ofget_modified_atoms.ParentModifierhandles both file paths andase.Atomsinputs.Return an
ase.Atomsobject (or a list of them only for movie-like workflows).Raise
NoReasonableStructureFoundwhen your attempted move cannot produce a valid structure.
For modifiers that can return many candidates (print_movie=True workflows), implement uniqueness filtering the same way as built-in modifiers:
from gg.utils_graph import get_unique_atoms
if self.unique:
return get_unique_atoms(
movie,
max_bond=self.max_bond,
max_bond_ratio=self.max_bond_ratio,
unique_method=self.unique_method,
depth=self.unique_depth,
)
else:
return movie
This pattern ensures your custom modifier honors unique_method and unique_depth consistently.