Atoms
*****

The ChemicalSystem class maintains an array of instances of the "Atom"
class to represent the atoms in the system.


Adding and Modifying Atoms
==========================

The following code provides examples for adding atoms to a
ChemicalSystem and modifying their attributes.

   from scm.base import ChemicalSystem
   from scm.base import Atom

   mol = ChemicalSystem()

   # Three different ways of adding atoms
   mol.add_atom("O", coords=[0, 0, 0])
   mol.add_atom(Atom("H"), coords=[2, 0, 0])
   mol.add_atom(1, coords=[0, 2, 0], unit="Angstrom")

   # Change the coordinates of the Oxygen atom (units: Angstrom)
   mol.atoms[0].coords = [1, 2, 3]

   # This is equivalent to the line above:
   mol.coords[0, :] = [1, 2, 3]

   # Change the first hydrogen atom to deuterium by adjusting the mass (units: Dalton):
   mol.atoms[1].mass = 2.014

**Methods to interact with the atoms in your ChemicalSystem:**

property ChemicalSystem.atoms: AtomList

   A list of all Atom instances that are part of this ChemicalSystem.

property ChemicalSystem.coords: AngstromCoordsArray

   The coordinates of the atoms in angstrom.

ChemicalSystem.contains_atom(atom: Atom) -> bool

   Checks if an Atom instance is part of a ChemicalSystem.

ChemicalSystem.atom_index(atom: Atom) -> int

   Given an Atom instance, returns its index in ChemicalSystem.atoms.

ChemicalSystem.add_atom(atom: Atom) -> None
ChemicalSystem.add_atom(atom: Atom, coords: ArrayLike, unit: str = 'angstrom') -> None
ChemicalSystem.add_atom(Z: int, coords: ArrayLike, unit: str = 'angstrom') -> None
ChemicalSystem.add_atom(element: Element, coords: ArrayLike, unit: str = 'angstrom') -> None
ChemicalSystem.add_atom(symbol: Atom.T_AtomSymbol, coords: ArrayLike, unit: str = 'angstrom') -> None

   Add a new Atom to the ChemicalSystem.

ChemicalSystem.remove_atom(atom_index: int) -> None

   Removes a single atom from the system, given its index.

ChemicalSystem.remove_atoms(atom_indices: ArrayLike) -> None

   Removes multiple atoms from the system, given their atom indices.

ChemicalSystem.set_atom(atom_index: int, atom: Atom) -> None

   Safe assignment of an Atom instance to atoms[atom_index].

   Is essentially the same as "self.atoms[atom_index] = atom", but
   makes sure that all the atomic attributes carried by the Atom
   instance are enabled on the target ChemicalSystem: the direct
   assignment will throw a ChemicalSystemError if the needed atomic
   attributes are not enabled, while calling this method will enable
   them automatically and will not throw.

   If Atom is part of a ChemicalSystem, its will overwrite whatever
   coordinates "atoms[atom_index]" had before. If Atom is not part of
   a ChemicalSystem, the coordinates of "atoms[atom_index]" will
   remain unchanged.


Changing the order of atoms
===========================

ChemicalSystem.reorder_atoms(atom_order: List[int]) -> None

   Reorders the system's atoms given a list of atom indices in the
   desired order.

   The "atom_order" shall be a list of integers. The list shall be of
   length "num_atoms" and its values a permutation of
   "range(0,num_atoms)". It shall contain the indices of the atoms in
   the original system in the order in which the atoms should occur
   after reordering.

   Example: for a 6 atom system the "atom_order = [4, 5, 0, 1, 2, 3]"
   would have the effect of moving the last two atoms to the front of
   the system. (One might also look at "atom_order" as a mapping from
   the atom index in the reordered system to the atom index in the
   original system. Using the above example "atom_order[1] == 5",
   which is the index that the new atom at index "1" had in the
   original system.)

ChemicalSystem.sorted_atom_order(comp: atom_comparator_func_t) -> List[int]

   Returns the atom order based on a user defined comparator function
   as a list of atom indices.

   The returned list can be passed into the "reorder_atoms" method to
   actually sort the atoms according to "comp". See also documentation
   of the "reorder_atoms" and "sort_atoms" method.

ChemicalSystem.sort_atoms(comp: atom_comparator_func_t) -> None

   Sorts the system's atoms according to a user defined comparator
   function.

   Atoms are sorted in ascending order where the user defined
   comparator function implements the < operator.

   Examples:

      # descending atomic number, heavy atoms first
      cs.sort_atoms(lambda A, B: A.Z > B.Z)

      # first by atomic number, then by z coordinate
      cs.sort_atoms(lambda A, B: (A.Z, A.coords[2]) < (B.Z, B.coords[2]))

      # by the number of bonds
      cs.sort_atoms(lambda A, B: cs.bonds.num_bonds(cs.atom_index(A)) < cs.bonds.num_bonds(cs.atom_index(B))

      # by distance to the origin
      import numpy as np
      cs.sort_atoms(lambda A, B: np.linalg.norm(A.coords) < np.linalg.norm(B.coords))

   Note that the atom sorting is stable: two atoms for which
   "comp(A,B)" returns False will keep their relative order in the
   sorted system.


Atomic species
==============

One often needs to group a system's atoms into sets of
indistinguishable atoms, essentially answering the question: which
atoms are considered the same?

In AMS these groups of equivalent atoms are often called "atomic
species". Note that the assignment of atoms into species is
application dependent: an electronic structure code may only care
about the elements of the atoms, while a molecular dynamics driver
will also need to distinguish the different isotopes of the same
element. Engines implementing classical forcefields will even need to
distinguish atoms based on their forcefield atom types and formal
charges.

The ChemicalSystem class allows users to provide a binary predicate
function that implements the comparison of two atoms, to allow for any
definition of atomic species:

ChemicalSystem.determine_species(comp: atom_comparator_func_t) -> Tuple[int, List[int], List[int]]

   Determines the present atomic species, based on a user defined
   comparator function.

   The comparator shall return True for two atoms if they are to be
   considered of the same species.

   Returns a 3-tuple consisting of:

   1. "num_species" is the total number of detected distinct species.

   2. "species_prototypes" a list of size "num_species" with the index
      of the first atom in the system that was recognized as being of
      a distinct species. This atom is considered the species'
      prototype atom and all atoms of the same species compare equal
      to it by the user defined comparator function "comp".

   3. "atom_to_species" a list of size "num_atoms" mapping each atom
      to its species.

   See also the related "get_species" function, which returns the same
   data as a list of atom indices for each species.

In practice, it is usually easiest to implement the comparison as a
lambda function right at the call site:

   # Distinguish isotopes as separate species:
   ... = chemsys.determine_species(lambda A, B: A.Z == B.Z and A.mass == B.mass)

Note that you can access the ChemicalSystem instance from within the
lambda, allowing access to things like regions or bonds, that are not
part of the Atom class itself.

   # Elements as species, but separately for region "substrate" and the rest:
   ... = chemsys.determine_species(
        lambda A, B: A.Z == B.Z and
        chemsys.is_atom_in_region(A, "substrate") == chemsys.is_atom_in_region(B, "substrate")
   )

   # Distinguish species based on the number of bonds
   # e.g. all 4-bonded atoms are equivalent:
   ... = chemsys.determine_species(
   lambda A, B: chemsys.bonds.num_bonds(chemsys.atom_index(A)) == chemsys.bonds.num_bonds(chemsys.atom_index(B))
   )

If you primarily want to iterate over all atoms within a species, the
"get_species" function may return the same information in a more
convenient format:

ChemicalSystem.get_species(comp: atom_comparator_func_t) -> List[List[int]]

   Determines the present atomic species, based on a user defined
   comparator function.

   The comparator shall return True for two atoms if they are to be
   considered of the same species.

   Returns a list of atom indices for each species, allowing for easy
   iteration over all atoms belonging to a species. Atom indices
   within each species are in ascending order.

      for species in chemsys.get_species():
          for iat in species:
              # do something with chemsys.atoms[iat]

   See also the related "determine_species" function, which returns
   the same information in a different format.


Atom Attributes
===============

Atom attributes provide additional, customizable information for each
atom. They are organized into categories, each relevant to different
computation engines or modules.

* Generic Attributes: These are common to all atoms, like mass.

* Engine/Module-Specific Attributes: Specific to certain computation
  engines or modules. For example, attributes relevant only to the ADF
  engine would be in the adf_attribs group and prefixed with 'adf' in
  the System Block.

You can toggle these attribute groups on or off. Here's an example,
where we initialize some atomic attributes in the System Block:

   from scm.base import ChemicalSystem

   mol = ChemicalSystem(
   """
   System
        Atoms
             O 0.0 0.0 0.0 forcefield.type=O_water
             H 1.0 0.0 0.0 forcefield.type=H_water mass=2.014
             H 0.0 1.0 0.0 forcefield.type=H_water
        End
   End
   """
   )

   # The Generic Attributes are always available (and have default values if not specified)
   print(mol.atoms[1].mass) # Outputs 2.014
   print(mol.atoms[2].mass) # Outputs 1.00798

   # The forcefield attributes are enabled because they were defined in the system block
   print(mol.atom_attributes_enabled('forcefield')) # Outputs True

   # Accessing the forcefield atomic attributes
   print(mol.atoms[0].forcefield.type) # Outputs O_water

   # The adf attributes are not enabled
   print(mol.atom_attributes_enabled('adf')) # Outputs False

   # Enable the 'adf' attributes group before using it:
   mol.enable_atom_attributes("adf")
   mol.atoms[0].adf.f = "fragment_1"

Note: The "Atom" class also contains the atom attributes.


Atom attributes in the ChemicalSystem
=====================================

property ChemicalSystem.adf_attribs: AtomAttributesADFList | None

   An optional list of all AtomAttributesADF for all atoms.

property ChemicalSystem.band_attribs: AtomAttributesBANDList | None

   An optional list of all AtomAttributesBAND for all atoms.

property ChemicalSystem.qe_attribs: AtomAttributesQEList | None

   An optional list of all AtomAttributesQE for all atoms.

property ChemicalSystem.vasp_attribs: AtomAttributesVASPList | None

   An optional list of all AtomAttributesVASP for all atoms.

property ChemicalSystem.dftb_attribs: AtomAttributesDFTBList | None

   An optional list of all AtomAttributesDFTB for all atoms.

property ChemicalSystem.reaxff_attribs: AtomAttributesReaxFFList | None

   An optional list of all AtomAttributesReaxFF for all atoms.

property ChemicalSystem.forcefield_attribs: AtomAttributesForcefieldList | None

   An optional list of all AtomAttributesForcefield for all atoms.

property ChemicalSystem.gui_attribs: AtomAttributesGUIList | None

   An optional list of all AtomAttributesGUI for all atoms.

ChemicalSystem.enable_atom_attributes(group_prefix: Literal['gui', 'adf', 'band', 'forcefield', 'dftb', 'reaxff', 'qe', 'vasp']) -> None

   Enables the use of a group of atomic attributes.

ChemicalSystem.disable_atom_attributes(group_prefix: Literal['gui', 'adf', 'band', 'forcefield', 'dftb', 'reaxff', 'qe', 'vasp']) -> None

   Disables the use of a group of atomic attributes. Any set
   attributes within the group will be discarded.

ChemicalSystem.atom_attributes_enabled(group_prefix: Literal['gui', 'adf', 'band', 'forcefield', 'dftb', 'reaxff', 'qe', 'vasp']) -> bool

   Checks if a group of atomic attributes is enabled or not.

ChemicalSystem.has_ghost_atoms() -> bool

   Checks whether the ChemicalSystem contains any Ghost atoms.

   Ghost atoms are a special construct in AMS for calculating basis
   set superposition errors. Please refer to the ADF and BAND manuals
   for details.


Engine/Module-Specific Atom Attributes Classes
==============================================

-[ AtomAttributesADF ]-

class AtomAttributesADF

   Atomic attributes used by the ADF engine.

   clear() -> None

      Unsets any ADF atomic attributes.

   copy() -> AtomAttributesADF

      Creates a deep copy of the AtomAttributesADF.

   empty() -> bool

      Returns whether any of the ADF atomic attributes is set.

   property ChgU: float | None

      Optional atom centered point charge for ADF's 3D-RISM method.

   property EpsU: float | None

      Lennard-Jones parameter for ADF's 3D-RISM method.

   property R: str | None

      The radius of the atom for constructing the COSMO surface.

      If the string represents a real number that is used as the
      radius, otherwise the string is used for looking up the radius
      in the "Solvation%Radii" block.

   property SigU: float | None

      Lennard-Jones parameter for ADF's 3D-RISM method.

   property f: str | None

      Specifies which fragment an atom belongs to; see the Fragments
      block in the ADF engine input.

   property fg: str | None

      TODO

   property fs: str | None

      TODO

   property imol: str | None

      TODO

   property nuclear_charge: float | None

      Alternative nuclear charge for an atom.

   property type: str | None

      The atom sub-type to be used by the ADF engine.

      This can be used to make the ADF engine treat two atoms of the
      same elements as separate atom types:

         System
             Atoms
                 C  x y z   adf.type=1
                 C  x y z   adf.type=2
             End
         End

      This is used as a replacements for the "dot-notation" from
      ADF<2025, which is no longer allowed in ADF>=2025:

         System
             Atoms
                 C.1  x y z
                 C.2  x y z
             End
         End

   property x: ndarray[Any, dtype[float64]] | None

      Custom atomic x-axis to be used by the ADF engine.

   property z: ndarray[Any, dtype[float64]] | None

      Custom atomic z-axis to be used by the ADF engine.

-[ AtomAttributesBAND ]-

class AtomAttributesBAND

   Atomic attributes used by the AMS BAND engine.

   clear() -> None

      Unsets any BAND atomic attributes.

   copy() -> AtomAttributesBAND

      Creates a deep copy of the AtomAttributesBAND.

   empty() -> bool

      Returns whether any of the BAND atomic attributes is set.

   property nuclear_charge: float | None

      Alternative nuclear charge for an atom.

-[ AtomAttributesQE ]-

class AtomAttributesQE

   Atomic attributes used by the AMS QuantumESPRESSO engine.

   clear() -> None

      Unsets any QE atomic attributes.

   copy() -> AtomAttributesQE

      Creates a deep copy of the AtomAttributesQE.

   empty() -> bool

      Returns whether any of the QE atomic attributes is set.

   property label: str | None

      A label to refer to an atom within the QuantumESPRESSO engine
      block.

      Due to technical restrictions on the QE side, this label can not
      exceed a length of 3 characters.

-[ AtomAttributesVASP ]-

class AtomAttributesVASP

   Atomic attributes used by the AMS VASP engine.

   clear() -> None

      Unsets any VASP atomic attributes.

   copy() -> AtomAttributesVASP

      Creates a deep copy of the AtomAttributesVASP.

   empty() -> bool

      Returns whether any of the VASP atomic attributes is set.

   property label: str | None

      A label to refer to an atom within the VASP engine block.

-[ AtomAttributesDFTB ]-

class AtomAttributesDFTB

   Atomic attributes used by the AMS DFTB engine.

   clear() -> None

      Unsets any DFTB atomic attributes.

   copy() -> AtomAttributesDFTB

      Creates a deep copy of the AtomAttributesDFTB.

   empty() -> bool

      Returns whether any of the DFTB atomic attributes is set.

   property Vext: float | None

      The external potential at the atomic nucleus used by the DFTB
      engine.

-[ AtomAttributesReaxFF ]-

class AtomAttributesReaxFF

   Atomic attributes used by the AMS ReaxFF engine.

   clear() -> None

      Unsets any ReaxFF atomic attributes.

   copy() -> AtomAttributesReaxFF

      Creates a deep copy of the AtomAttributesReaxFF.

   empty() -> bool

      Returns whether any of the ReaxFF atomic attributes is set.

   property charge: float | None

      The charge of an atom used for electrostatics by the ReaxFF
      engine.

-[ AtomAttributesForcefield ]-

class AtomAttributesForcefield

   Atomic attributes used by the AMS Forcefield engine.

   clear() -> None

      Unsets any Forcefield atomic attributes.

   copy() -> AtomAttributesForcefield

      Creates a deep copy of the AtomAttributesForcefield.

   empty() -> bool

      Returns whether any of the Forcefield atomic attributes is set.

   property charge: float | None

      The charge of an atom used for electrostatics by the Forcefield
      engine.

   property type: str | None

      The atom type to be used by the Forcefield engine.

-[ AtomAttributesGUI ]-

class AtomAttributesGUI

   Atomic attributes used for visualization in the GUI.

   clear() -> None

      Unsets any GUI atomic attributes.

   copy() -> AtomAttributesGUI

      Creates a deep copy of the AtomAttributesGUI.

   empty() -> bool

      Returns whether any of the GUI atomic attributes is set.

   property color: Tuple[int, int, int] | None

      The color of an atom.

   property radius: float | None

      The radius of an atom for visualization purposes.


Atom class
==========

The Atom class contains all the relevant data for an individual atom:

class Atom
class Atom(Z: int)
class Atom(element: Element)
class Atom(symbol: T_AtomSymbol)

   Class representing a single atom and its properties.

   copy() -> Atom

      Creates a deep copy of the Atom.

   has_identical_attributes(other: Atom) -> bool
   has_identical_attributes(other: Atom, properties: List[T_AtomAttributeGroup]) -> bool

      Helper for @overload to raise when called.

   in_chemicalsystem() -> bool

      Check if this Atom instance is part of a ChemicalSystem.

   transmute(Z: int)
   transmute(element: Element)
   transmute(symbol: T_AtomSymbol)

      Helper for @overload to raise when called.

   property Z: int

      The atomic number of the atom.

   property adf: AtomAttributesADF | None

      Atomic attributes used by the ADF engine.

   property band: AtomAttributesBAND | None

      Atomic attributes used by the AMS BAND engine.

   property coords: AngstromCoordsArray

      The coordinates of the atom in angstrom.

   property dftb: AtomAttributesDFTB | None

      Atomic attributes used by the AMS DFTB engine.

   property element: Element

      Returns the atom's element.

   property forcefield: AtomAttributesForcefield | None

      Atomic attributes used by the AMS Forcefield engine.

   property gui: AtomAttributesGUI | None

      Atomic attributes used for visualization in the GUI.

   property is_ghost: bool

      Whether the atom is a Ghost atom.

      Ghost atoms are a special construct in AMS for calculating basis
      set superposition errors. Please refer to the ADF and BAND
      manuals for details.

   property mass: float

      The mass of the atom in dalton.

   property qe: AtomAttributesQE | None

      Atomic attributes used by the AMS QuantumESPRESSO engine.

   property reaxff: AtomAttributesReaxFF | None

      Atomic attributes used by the AMS ReaxFF engine.

   property symbol: T_AtomSymbol

      The atom's symbol (e.g. 'C' or 'Au'). May be 'Gh.C' for ghost
      atoms.

   property vasp: AtomAttributesVASP | None

      Atomic attributes used by the AMS VASP engine.
