Filters#

Filters act as a wrapper around an Atoms object in order to modify its degrees of freedom during structure optimization. A typical use case will look like this:

 -------       --------       ----------
|       |     |        |     |          |
| Atoms |<----| Filter |<----| Dynamics |
|       |     |        |     |          |
 -------       --------       ----------

and in Python this would be:

>>> atoms = Atoms(...)
>>> filter = Filter(atoms, ...)
>>> dyn = Dynamics(filter, ...)

The following filters are available:

Filter

fix positions of specified atoms

StrainFilter

relax cell shape with fixing scaled positions of atoms

UnitCellFilter

relax both atomic positions and cell shape

ExpCellFilter

relax both atomic positions and cell shape

FrechetCellFilter

relax both atomic positions and cell shape

class ase.filters.Filter(atoms: Atoms, indices=None, mask: np.ndarray | None = None)[source]#

Filter “hiding” specified atoms.

This filter “hides” some atoms in the ASE Atoms object and thus fixes their positions during structure optimization.

This can be used as an alternative to the FixAtoms constraint.

Parameters:
  • atoms (Atoms) – ASE Atoms object.

  • indices (list[int] | None, default: None) – Indices for of the atoms that should remain visible.

  • mask (np.ndarray | None, default: None) – One boolean per atom indicating if the atom should remain visible or not.

Notes

You must supply either indices or mask.

If Trajectory tries to save this object, it will instead save the underlying Atoms object. To prevent this, override the iterimages() method.

Examples

>>> from ase.build import molecule
>>> from ase.filters import Filter
>>> atoms=molecule('H2O')
>>> list(atoms.symbols)
['O', 'H', 'H']
>>> atoms.get_positions()
array([[ 0.      ,  0.      ,  0.119262],
       [ 0.      ,  0.763239, -0.477047],
       [ 0.      , -0.763239, -0.477047]])
>>> f1 = Filter(atoms, indices=[1, 2])
>>> f2 = Filter(atoms, mask=[0, 1, 1])
>>> f3 = Filter(atoms, mask=[a.z == 1 for a in atoms])
>>> f1.get_positions()
array([[ 0.      ,  0.763239, -0.477047],
       [ 0.      , -0.763239, -0.477047]])

In all three filters, only the hydrogen atoms are made visible. When asking for the positions, only the positions of the hydrogen atoms are returned.

class ase.filters.StrainFilter(atoms: Atoms, mask: np.ndarray | None = None, include_ideal_gas: bool = False)[source]#

Filter applying a homogeneous strain to a list of atoms.

This class is used together with an ASE optimizer in order to optimize the cell shape with minimizing the stress while keeping the scaled positions fixed.

This class regards the strain of the cell as the generalized positions and the global stress tensor (times the volume) as the generalized force.

The stress and strain are presented as 6-vectors, the order of the components follow the standard engineering practice: xx, yy, zz, yz, xz, xy.

Parameters:
  • atoms (Atoms) – ASE Atoms object.

  • mask (np.ndarray | None, default: None) – Array of six booleans, indicating which of the six independent components of the strain that are allowed to become non-zero. The default (None) relaxes all the six components, i.e., [True, True, True, True, True, True].

  • include_ideal_gas (bool, default: False) – If True, the kinetic contribution of the stress is also considered.

Notes

If this filter is used together with MDMin, the timestep (dt) to be used depends on the system size. 0.01/x where x is a typical dimension seems like a good choice.

class ase.filters.UnitCellFilter(atoms: Atoms, mask: np.ndarray | None = None, cell_factor: float | None = None, hydrostatic_strain: bool = False, constant_volume: bool = False, orig_cell: np.ndarray | None = None, scalar_pressure: float = 0.0)[source]#

Filter returning the atomic forces and the cell stresses together.

This class is used together with an ASE optimizer in order to minimize both the atomic forces and the cell stresses simultaneously.

Degrees of freedom are the positions in the original undeformed cell, plus the deformation gradient \(\mathbf{F}\) (extra 3 “atoms”). If \(\mathbf{r}_i\) denotes a Cartesian atomic position, the corresponding atomic degree of freedom is

\[\mathbf{q}_i = \mathbf{F}^{-1}\mathbf{r}_i.\]

Thus, when \(\mathbf{F}\) changes while \(\mathbf{q}_i\) is kept fixed, the atoms follow the cell deformation affinely, \(\mathbf{r}_i = \mathbf{F}\mathbf{q}_i\). This separates homogeneous cell deformation from internal atomic displacements. The cell forces are the negative derivatives of the energy with respect to \(\mathbf{F}\). For full details, see [1].

Parameters:
  • atoms (Atoms) – ASE Atoms object.

  • mask (np.ndarray | None, default: None) –

    Mask indicating which of the six independent components of the strain are relaxed.

    • True: relax to zero

    • False: fixed, ignore this component

  • cell_factor (float, default: float(len(atoms))) – Factor by which deformation gradient is multiplied to put it on the same scale as the positions when assembling the combined position/cell vector. The stress contribution to the forces is scaled down by the same factor. This can be thought of as a very simple preconditioners. Default is number of atoms which gives approximately the correct scaling.

  • hydrostatic_strain (bool, default: False) –

    Constrain the cell by only allowing hydrostatic deformation. The virial tensor is replaced by np.diag([np.trace(virial)] * 3).

    Added in version 3.17.0.

  • constant_volume (bool, default: False) –

    Project out the diagonal elements of the virial tensor to allow relaxations at constant volume, e.g. for mapping out an energy-volume curve. Note: this only approximately conserves the volume and breaks energy/force consistency so can only be used with optimizers that do require do a line minimisation (e.g. FIRE).

    Added in version 3.17.0.

  • scalar_pressure (float, default: 0.0) –

    Applied pressure in eV/Å3 to use for enthalpy pV term. (Note: 1 eV/Å3 ≈ 160.2 GPa). For example, set 1.0 * ase.units.GPa to provide 1 GPa. As above, this breaks energy/force consistency.

    Added in version 3.17.0.

Notes

FrechetCellFilter will probably perform better.

Examples

You can still use constraints on the atoms, e.g. FixAtoms, to control the relaxation of the atoms.

>>> from ase.build import bulk
>>> from ase.constraints import FixAtoms
>>> atoms = bulk('BN', 'wurtzite', a=2.55, c=4.23)
>>> # this should be equivalent to the StrainFilter
>>> atoms.set_constraint(FixAtoms(mask=[True for atom in atoms]))
>>> ucf = UnitCellFilter(atoms)

You should not attach UnitCellFilter to a trajectory. Instead, create a trajectory for the atoms, and attach it to an optimizer like this:

>>> from ase.io import Trajectory
>>> from ase.optimize import BFGS
>>> atoms = bulk('BN', 'wurtzite', a=2.55, c=4.23)
>>> # atoms.calc = ...
>>> ucf = UnitCellFilter(atoms)
>>> opt = BFGS(ucf)
>>> traj = Trajectory('BN.traj', 'w', atoms)
>>> opt.attach(traj)
>>> # opt.run(fmax=0.05)
class ase.filters.ExpCellFilter(atoms, mask=None, cell_factor=None, *args, **kwargs)[source]#

Filter returning the atomic forces and the cell stresses together.

Added in version 3.17.0.

Deprecated since version 3.23.0: Use FrechetCellFilter for better convergence w.r.t. cell variables.

This class is used together with an ASE optimizer in order to minimize both the atomic forces and the cell stresses simultaneously.

Degrees of freedom are the positions in the original undeformed cell, plus the log of the deformation tensor (extra 3 “atoms”).

Parameters:
  • cell_factor (float(DEPRECATED)) –

    Retained for backwards compatibility, but no longer used.

    Deprecated since version 3.23.0.

  • *args – Additional arguments passed to UnitCellFilter.

  • **kwargs – Additional arguments passed to UnitCellFilter.

Notes

The implementation is based on that of Christoph Ortner in JuLIP.jl: libAtoms/JuLIP.jl

In ExpCellFilter, we express the deformation gradient \(\mathbf{F} \in \mathbb{R}^{3 \times 3}\) as

\[\mathbf{F} = \exp(\mathbf{U}),\]

where \(\mathbf{U} = \log(\mathbf{F})\) is used as the cell optimization variable.

For a perturbation \(\mathbf{V} \in \mathbb{R}^{3 \times 3}\) of \(\mathbf{U}\), the corresponding first-order change of \(\mathbf{F}\) is given by the Fréchet derivative of the matrix exponential,

\[L_{\exp}(\mathbf{U}, \mathbf{V}) \equiv \left. \frac{\mathrm{d}}{\mathrm{d}t} \exp(\mathbf{U} + t\mathbf{V}) \right|_{t=0}.\]

Let \(\mathbf{S}\) denote the virial tensor, defined here as \(\mathbf{S} = -\Omega \boldsymbol\sigma\), where \(\Omega\) is the current cell volume and \(\boldsymbol\sigma\) is the Cauchy stress tensor. Since the energy gradient with respect to \(\mathbf{F}\) is \(-\mathbf{S}\mathbf{F}^{-\top}\), the directional derivative of the energy with respect to \(\mathbf{U}\) is

\[\left. \frac{\mathrm{d}}{\mathrm{d}t} E(\mathbf{U} + t\mathbf{V}) \right|_{t=0} = [-\mathbf{S}\exp(-\mathbf{U}^{\top})] : L_{\exp}(\mathbf{U},\mathbf{V}).\]

Here \(:\) denotes the Frobenius inner product, \(\mathbf{A}:\mathbf{B} = \operatorname{tr}(\mathbf{A}^{\!\top}\mathbf{B})\).

Using the integral representation of the Fréchet derivative and the cyclic property of the trace, this can be rewritten as

\[[-\mathbf{S}\exp(-\mathbf{U}^{\top})] : L_{\exp}(\mathbf{U},\mathbf{V}) = L_{\exp}\left( \mathbf{U}^{\top}, -\mathbf{S}\exp(-\mathbf{U}^{\top}) \right) : \mathbf{V}.\]

Therefore,

\[\left. \frac{\mathrm{d}}{\mathrm{d}t} E(\mathbf{U} + t\mathbf{V}) \right|_{t=0} = L_{\exp}\left( \mathbf{U}^{\top}, -\mathbf{S}\exp(-\mathbf{U}^{\top}) \right) : \mathbf{V}.\]

If \(\mathbf{U}\) is symmetric, an assumption that does not hold in general, this reduces to

\[\left. \frac{\mathrm{d}}{\mathrm{d}t} E(\mathbf{U} + t\mathbf{V}) \right|_{t=0} = L_{\exp}\left( \mathbf{U}, -\mathbf{S}\exp(-\mathbf{U}) \right) : \mathbf{V}.\]

Thus, under this assumption, the generalized cell force with respect to \(\mathbf{U}\), i.e. the negative energy gradient, is

\[-\frac{\partial E}{\partial \mathbf{U}} = -L_{\exp}\left(\mathbf{U}, -\mathbf{S}\exp(-\mathbf{U})\right).\]
class ase.filters.FrechetCellFilter(atoms, mask=None, exp_cell_factor=None, *args, **kwargs)[source]#

Filter returning the atomic forces and the cell stresses together.

This class is used together with an ASE optimizer in order to minimize both the atomic forces and the cell stresses simultaneously.

Added in version 3.23.0.

Degrees of freedom are the positions in the original undeformed cell, plus the matrix logarithm \(\mathbf{U} = \log(\mathbf{F})\) of the deformation gradient (extra 3 “atoms”). The cell forces are computed using the Fréchet derivative of the matrix exponential, giving the negative derivatives of the energy with respect to \(\mathbf{U}\).

Parameters:
  • exp_cell_factor (float, default: float(len(atoms))) – Scaling factor for cell variables. The cell gradients in get_forces() is divided by exp_cell_factor. By default, set the number of atoms. We recommend to set an extensive value for this parameter.

  • *args – Additional arguments passed to UnitCellFilter.

  • **kwargs – Additional arguments passed to UnitCellFilter.

Notes

The original ExpCellFilter implementation also uses the matrix logarithm of the deformation gradient as the cell variable, but its cell forces are not in general consistent with numerical energy derivatives. FrechetCellFilter instead evaluates the required Fréchet derivatives explicitly. If you would like to keep the previous behavior, please use ExpCellFilter.

The derivation of gradients of energy w.r.t positions and the log of the deformation tensor is given in lan496/lan496.github.io