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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Args:

atoms (ase.Atoms): The atoms object on which the adsorbate will be added

Returns:

ase.Atoms if print_movie = True list[ase.Atoms] if print_movie = False

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: Add

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

get_modified_atoms(atoms: Atoms) Atoms[source]
Args:

atoms (ase.Atoms): The atoms object on which the adsorbate will be added

Returns:

ase.Atoms if print_movie = True, list[ase.Atoms] if print_movie = False

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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Args:

atoms (ase.Atoms): The atoms object on which the adsorbate will be added

Returns:

ase.Atoms if print_movie = True list[ase.Atoms] if print_movie = False

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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Args:

atoms (ase.Atoms): The atoms object on which the adsorbate will be added

Returns:

ase.Atoms if print_movie = True list[ase.Atoms] if print_movie = False

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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Args:

atoms (ase.Atoms): The atoms object on which the adsorbate will be added

Returns:

ase.Atoms if print_movie = True list[ase.Atoms] if print_movie = False

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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Returns:

ase.Atoms:

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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Returns:

ase.Atoms:

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.

get_modified_atoms(atoms: Atoms) Atoms[source]
Returns:

ase.Atoms:

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 = atoms at the top of get_modified_atoms. ParentModifier handles both file paths and ase.Atoms inputs.

  • Return an ase.Atoms object (or a list of them only for movie-like workflows).

  • Raise NoReasonableStructureFound when 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.

class gg.modifiers.modifiers.ParentModifier(weight: float)[source]

Bases: object

Parent bare bones modifier which serves as the basis for other modifiers Args:

weight (float): Modifier Weight

property atoms
Returns:

ase.Atoms

get_modified_atoms(atoms) Atoms[source]
Returns:

ase.Atoms: