Skip to content

Optimisation

UFF4MOF geometry optimisation via gulp_setup's atom-typing backend: cage_isomer_builder.utils.optimise.

UFF4MOFCalculator

UFF4MOFCalculator(bond_matrix, mmtypes, uff_db, charges=None, **kwargs)

Bases: Calculator

ASE Calculator evaluating the UFF4MOF energy/forces via OpenMM.

Parameters:

Name Type Description Default
bond_matrix (ndarray, shape(n_atoms, n_atoms))

Symmetric bond-order matrix (0 = no bond).

required
mmtypes sequence of str

UFF4MOF type name for each atom (e.g. "C_3", "Zr3+4"), as returned by :func:gulp_setup.mmanalysis.analyze_mm.

required
uff_db dict[str, UFFAtomParams]

Parameter table from :func:cage_isomer_builder.utils.uff4mof_params.load_uff4mof_db.

required
charges (ndarray, shape(n_atoms))

Fixed per-atom partial charges (elementary charge units). When given, a Coulomb term (ke*qi*qj/r) is added over the same non-bonded pairs as the van der Waals term (graph distance > 2) - UFF4MOF, like the original UFF, is parameterised assuming a QEq-like electrostatic contribution is present alongside the bond/angle/ torsion/vdW terms; omitting it understates the electrostatic pull between charged fragments (e.g. an anionic carboxylate node and a cationic metal cluster) that helps hold a MOF-like assembly together correctly. Charges are held fixed for the whole optimisation (not re-equilibrated every step), matching standard UFF/QEq practice. When None (default), no electrostatic term is added at all.

None
Source code in cage_isomer_builder/utils/optimise.py
def __init__(self, bond_matrix, mmtypes, uff_db, charges=None, **kwargs):
    super().__init__(**kwargs)
    self.bond_matrix = np.asarray(bond_matrix, dtype=float)
    self.mmtypes = np.asarray(mmtypes)
    self.uff_db = uff_db
    self.charges = None if charges is None else np.asarray(charges, dtype=float)
    self._context = None

run_uff4mof_optimisation

run_uff4mof_optimisation(atoms, bond_matrix, fmax: float = 0.05, steps: int = 500, optimizer_cls=None, trajectory: Optional[str] = None, charges: Optional[ndarray] = None)

Relax a molecular geometry with ASE using a UFF4MOF (OpenMM) calculator.

UFF atom types are assigned automatically from bond_matrix via :func:gulp_setup.mmanalysis.analyze_mm (a heuristic cost function over local coordination number, angle and radius - see that module for details); the bond matrix itself is taken as-is, never re-derived from geometry.

Parameters:

Name Type Description Default
atoms Atoms

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

required
bond_matrix (ndarray, shape(len(atoms), len(atoms)))

Symmetric bond-order matrix, e.g. from :func:cage_isomer_builder.utils.read_write.stk_2_ase_atoms.

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
charges (ndarray, shape(len(atoms)))

Fixed per-atom partial charges forwarded to :class:UFF4MOFCalculator to add its Coulomb term. When None (default), the optimisation runs without electrostatics, as before.

None

Returns:

Type Description
Atoms

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

Source code in cage_isomer_builder/utils/optimise.py
def run_uff4mof_optimisation(
    atoms,
    bond_matrix,
    fmax: float = 0.05,
    steps: int = 500,
    optimizer_cls=None,
    trajectory: Optional[str] = None,
    charges: Optional[np.ndarray] = None,
):
    """
    Relax a molecular geometry with ASE using a UFF4MOF (OpenMM) calculator.

    UFF atom types are assigned automatically from ``bond_matrix`` via
    :func:`gulp_setup.mmanalysis.analyze_mm` (a heuristic cost function over
    local coordination number, angle and radius - see that module for
    details); the bond matrix itself is taken as-is, never re-derived from
    geometry.

    Parameters
    ----------
    atoms : ase.Atoms
        Starting geometry. Any constraint already set on ``atoms``
        (e.g. ``FixRigidBodies``) is preserved through the optimisation.
    bond_matrix : np.ndarray, shape (len(atoms), len(atoms))
        Symmetric bond-order matrix, e.g. from
        :func:`cage_isomer_builder.utils.read_write.stk_2_ase_atoms`.
    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.
    charges : np.ndarray, shape (len(atoms),), optional
        Fixed per-atom partial charges forwarded to
        :class:`UFF4MOFCalculator` to add its Coulomb term. When ``None``
        (default), the optimisation runs without electrostatics, as before.

    Returns
    -------
    ase.Atoms
        Optimised structure (a copy - the input ``atoms`` is not modified).
    """
    from gulp_setup.mmanalysis import analyze_mm

    from cage_isomer_builder.utils.uff4mof_params import load_uff4mof_db

    if optimizer_cls is None:
        optimizer_cls = LBFGS

    opt_atoms = atoms.copy()
    opt_atoms.pbc = False

    _, mmtypes = analyze_mm(opt_atoms, bond_order=np.asarray(bond_matrix, dtype=float))
    uff_db = load_uff4mof_db()
    opt_atoms.calc = UFF4MOFCalculator(
        bond_matrix=bond_matrix, mmtypes=mmtypes, uff_db=uff_db, charges=charges
    )

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

    return opt_atoms