Source code for sisl.mixing.linear

# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at https://mozilla.org/MPL/2.0/.
from __future__ import annotations

from typing import Any, Optional

import numpy.typing as npt

from sisl._internal import set_module

from .base import BaseHistoryWeightMixer, T

__all__ = ["LinearMixer", "AndersonMixer"]


@set_module("sisl.mixing")
class LinearMixer(BaseHistoryWeightMixer):
    r"""Linear mixing

    The linear mixing is solely defined using a weight, and the resulting functional
    may then be calculated via:

    .. math::

        \mathbf f^{i+1} = \mathbf f^i + w \delta \mathbf f^i

    Parameters
    ----------
    weight : float, optional
       mixing weight
    """

    __slots__ = ()

[docs] def __call__(self, f: T, df: T, append: bool = True) -> T: r"""Calculate a new variable :math:`\mathbf f'` using input and output of the functional Parameters ---------- f : object input variable for the functional df : object derivative of the functional append : bool, optional whether to append to the history """ super().__call__(f, df, append=append) return f + self.weight * df
[docs] class AndersonMixer(BaseHistoryWeightMixer): r""" Anderson mixing The Anderson mixing assumes that the mixed input/output are linearly related. Hence .. math:: |\bar{n}^{m}_{\mathrm{in}/\mathrm{out}\rangle = (1 - \beta)|n^{m}_{\mathrm{in}/\mathrm{out}\rangle + \beta|n^{m-1}_{\mathrm{in}/\mathrm{out}\rangle Here the optimal choice :math:`\beta` is calculated as: .. math:: \boldsymbol\delta_i &= \mathbf f_i^{\mathrm{out}} - \mathbf f_i^{\mathrm{in}} \\ \beta &= \frac{\langle \boldsymbol\delta_i | \boldsymbol\delta_i - \boldsymbol\delta_{i-1}\rangle} {\langle \boldsymbol\delta_i - \boldsymbol\delta_{i-1}| \boldsymbol\delta_i - \boldsymbol\delta_{i-1} \rangle} Finally the resulting output becomes: .. math:: |n^{m+1}\rangle = (1 - \alpha)|\bar n^m_{\mathrm{in}}\rangle + \alpha|\bar n^m_{\mathrm{out}}\rangle See :cite:`Johnson1988` for more details. """ __slots__ = () @staticmethod def _beta(df1: T, df2: T) -> npt.NDArray: # Minimize the average densities for the delta variable def metric(a, b): return a.ravel().conj().dot(b.ravel()).real ddf = df2 - df1 beta = metric(df2, ddf) / metric(ddf, ddf) return beta
[docs] def __call__( self, f: T, df: T, delta: Optional[Any] = None, append: bool = True ) -> T: r"""Calculate a new variable :math:`\mathbf f'` using input and output of the functional Parameters ---------- f : object input variable for the functional df : object derivative of the functional """ if delta is None: # not a copy, simply the same reference delta = df # Get last elements if len(self.history) > 0: last = self.history[-1] f1 = last[0] fdf1 = last[1] d1 = last[-1] else: f1 = None # the current iterations input + output variables f2 = f fdf2 = f + df d2 = delta # store new last variables # delta is used for calculating beta, nothing more # here n refers to the variable (density) we are mixing # and the integer corresponds to the iteration count super().__call__(f2, fdf2, d2, append=append) if f1 is None: # this is linear mixing for the first step return f + self.weight * df # calculate next position beta = self._beta(d1, d2) # Now calculate the new averages nin = (1 - beta) * f2 + beta * f1 nout = (1 - beta) * fdf2 + beta * fdf1 return (1 - self.weight) * nin + self.weight * nout