Skip to content

xTB (tblite)

GFN-xTB charges and optimisation through the optional tblite package.

Charges

tblite_available

tblite_available() -> bool

True if the optional tblite package can be imported.

Examples:

>>> from cage_isomer_builder.utils.tblite_charges import tblite_available
>>> isinstance(tblite_available(), bool)
True
Source code in cage_isomer_builder/utils/tblite_charges.py
def tblite_available() -> bool:
    """True if the optional tblite package can be imported.

    Examples
    --------
    >>> from cage_isomer_builder.utils.tblite_charges import tblite_available
    >>> isinstance(tblite_available(), bool)
    True
    """
    return importlib.util.find_spec("tblite") is not None

require_tblite

require_tblite() -> None

Raise a clear ImportError if tblite is not installed.

Examples:

>>> from cage_isomer_builder.utils.tblite_charges import require_tblite
>>> require_tblite()        # returns quietly when tblite is installed, else ImportError
Source code in cage_isomer_builder/utils/tblite_charges.py
def require_tblite() -> None:
    """Raise a clear ImportError if tblite is not installed.

    Examples
    --------
    >>> from cage_isomer_builder.utils.tblite_charges import require_tblite
    >>> require_tblite()        # returns quietly when tblite is installed, else ImportError
    """
    if not tblite_available():
        raise ImportError(
            "GFN-xTB needs the tblite package. Install it with "
            "'pip install cage_isomer_builder[xtb]' (no Windows wheels; on "
            "Windows use conda-forge or WSL), or use method='UFF4MOF'."
        )

predict_tblite_charges

predict_tblite_charges(atoms: Atoms, method: str = DEFAULT_METHOD, charge: float = 0.0) -> np.ndarray

Predict per-atom partial charges for a non-periodic structure via tblite.

Parameters:

Name Type Description Default
atoms Atoms

Structure to charge. Atom order is preserved in the returned array.

required
method str

tblite xTB Hamiltonian to use (e.g. "GFN1-xTB", "GFN2-xTB").

"GFN2-xTB"
charge float

Total charge of the structure.

0.0

Returns:

Type Description
(ndarray, shape(len(atoms)))

Per-atom partial charges (elementary charge units), neutralised to sum to exactly charge.

Examples:

>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.tblite_charges import predict_tblite_charges
>>> q = predict_tblite_charges(molecule("H2O"))          # needs tblite
>>> bool(q[0] < 0 < q[1]), round(float(q.sum()), 6)      # O negative, neutral overall
(True, 0.0)
Source code in cage_isomer_builder/utils/tblite_charges.py
def predict_tblite_charges(
    atoms: Atoms,
    method: str = DEFAULT_METHOD,
    charge: float = 0.0,
) -> np.ndarray:
    """
    Predict per-atom partial charges for a non-periodic structure via tblite.

    Parameters
    ----------
    atoms : ase.Atoms
        Structure to charge. Atom order is preserved in the returned array.
    method : str, default "GFN2-xTB"
        tblite xTB Hamiltonian to use (e.g. "GFN1-xTB", "GFN2-xTB").
    charge : float, default 0.0
        Total charge of the structure.

    Returns
    -------
    np.ndarray, shape (len(atoms),)
        Per-atom partial charges (elementary charge units), neutralised to
        sum to exactly ``charge``.

    Examples
    --------
    >>> from ase.build import molecule
    >>> from cage_isomer_builder.utils.tblite_charges import predict_tblite_charges
    >>> q = predict_tblite_charges(molecule("H2O"))          # needs tblite
    >>> bool(q[0] < 0 < q[1]), round(float(q.sum()), 6)      # O negative, neutral overall
    (True, 0.0)
    """
    require_tblite()
    from tblite.ase import TBLite  # slow import, kept local

    calc_atoms = atoms.copy()
    calc_atoms.pbc = False
    calc_atoms.calc = TBLite(method=method, charge=charge, verbosity=0)
    calc_atoms.get_potential_energy()
    charges = np.asarray(calc_atoms.calc.get_charges(), dtype=float)

    if len(charges) != len(atoms):
        raise RuntimeError(
            f"tblite returned {len(charges)} charges for a "
            f"{len(atoms)}-atom structure."
        )
    if not np.any(charges):
        raise RuntimeError(
            "tblite output contained only zero charges."
        )

    charges += (charge - charges.sum()) / len(charges)
    return charges

Optimisation

run_tblite_optimisation

run_tblite_optimisation(atoms: Atoms, fmax: float = 0.05, steps: int = 500, optimizer_cls=None, trajectory: Optional[str] = None, charge: float = 0.0, method: str = DEFAULT_METHOD, logfile: Optional[str] = '-') -> tuple[Atoms, np.ndarray]

Relax a molecular geometry with ASE using a GFN-xTB (tblite) calculator.

Parameters:

Name Type Description Default
atoms Atoms

Starting geometry. Any constraint already set on atoms (e.g. FixRigidBodies) is preserved through the optimisation.

required
fmax float

Force convergence threshold in eV/Angstrom.

0.05
steps int

Maximum number of optimisation steps.

500
optimizer_cls ASE optimizer class

Any ASE Optimizer subclass (LBFGS, BFGS, FIRE, ...). Defaults to LBFGS.

None
trajectory str

File path for an ASE .traj trajectory.

None
charge float

Total charge of the structure.

0.0
method str

tblite xTB Hamiltonian to use (e.g. "GFN1-xTB", "GFN2-xTB").

"GFN2-xTB"
logfile str or None

Where the optimiser writes its step log: "-" for the terminal, a file path, or None for no output.

"-"

Returns:

Type Description
Atoms

Optimised structure (a copy; the input atoms is not modified).

(ndarray, shape(len(atoms)))

Per-atom partial charges from the final electronic structure, obtained as a byproduct of the last energy/force evaluation.

Examples:

>>> from ase.build import molecule
>>> from cage_isomer_builder.utils.tblite_optimise import run_tblite_optimisation
>>> relaxed, charges = run_tblite_optimisation(molecule("H2O"), logfile=None)   # needs tblite
>>> round(float(relaxed.get_distance(0, 1)), 2), charges.shape
(0.96, (3,))
Source code in cage_isomer_builder/utils/tblite_optimise.py
def run_tblite_optimisation(
    atoms: Atoms,
    fmax: float = 0.05,
    steps: int = 500,
    optimizer_cls=None,
    trajectory: Optional[str] = None,
    charge: float = 0.0,
    method: str = DEFAULT_METHOD,
    logfile: Optional[str] = "-",
) -> tuple[Atoms, np.ndarray]:
    """
    Relax a molecular geometry with ASE using a GFN-xTB (tblite) calculator.

    Parameters
    ----------
    atoms : ase.Atoms
        Starting geometry. Any constraint already set on ``atoms``
        (e.g. ``FixRigidBodies``) is preserved through the optimisation.
    fmax : float, default 0.05
        Force convergence threshold in eV/Angstrom.
    steps : int, default 500
        Maximum number of optimisation steps.
    optimizer_cls : ASE optimizer class, optional
        Any ASE ``Optimizer`` subclass (LBFGS, BFGS, FIRE, ...).
        Defaults to ``LBFGS``.
    trajectory : str, optional
        File path for an ASE ``.traj`` trajectory.
    charge : float, default 0.0
        Total charge of the structure.
    method : str, default "GFN2-xTB"
        tblite xTB Hamiltonian to use (e.g. "GFN1-xTB", "GFN2-xTB").
    logfile : str or None, default "-"
        Where the optimiser writes its step log: ``"-"`` for the terminal,
        a file path, or ``None`` for no output.

    Returns
    -------
    ase.Atoms
        Optimised structure (a copy; the input ``atoms`` is not modified).
    np.ndarray, shape (len(atoms),)
        Per-atom partial charges from the final electronic structure,
        obtained as a byproduct of the last energy/force evaluation.

    Examples
    --------
    >>> from ase.build import molecule
    >>> from cage_isomer_builder.utils.tblite_optimise import run_tblite_optimisation
    >>> relaxed, charges = run_tblite_optimisation(molecule("H2O"), logfile=None)   # needs tblite
    >>> round(float(relaxed.get_distance(0, 1)), 2), charges.shape
    (0.96, (3,))
    """
    require_tblite()
    from tblite.ase import TBLite  # slow import, kept local

    if optimizer_cls is None:
        optimizer_cls = LBFGS

    opt_atoms = atoms.copy()
    opt_atoms.pbc = False
    opt_atoms.calc = TBLite(method=method, charge=charge, verbosity=0)

    opt = optimizer_cls(opt_atoms, trajectory=trajectory, logfile=logfile)
    opt.run(fmax=fmax, steps=steps)

    charges = np.asarray(opt_atoms.calc.get_charges(), dtype=float)

    return opt_atoms, charges