Damping and DIIS control.

ebcc.core.damping.BaseDamping(*args, options=None, **kwargs)

Bases: ABC

Base class for damping.

Initialise the damping object.

Source code in ebcc/core/damping.py
def __init__(self, *args: Any, options: Optional[_BaseOptions] = None, **kwargs: Any) -> None:
    """Initialise the damping object."""
    self._arrays = {}
    self._errors = {}
    self._counter = 0

ebcc.core.damping.BaseDamping.__call__(array, error=None)

Apply damping to the array.

Parameters:
  • array (NDArray[T]) –

    The array to damp.

  • error (Optional[NDArray[T]], default: None ) –

    The error array.

Returns:
  • NDArray[T]

    The damped array.

Source code in ebcc/core/damping.py
def __call__(self, array: NDArray[T], error: Optional[NDArray[T]] = None) -> NDArray[T]:
    """Apply damping to the array.

    Args:
        array: The array to damp.
        error: The error array.

    Returns:
        The damped array.
    """
    self.push(array, error=error)
    return self.extrapolate()

ebcc.core.damping.BaseDamping.push(array, error=None) abstractmethod

Push the array and error into the damping object.

Parameters:
  • array (NDArray[T]) –

    The array to push.

  • error (Optional[NDArray[T]], default: None ) –

    The error array to push.

Source code in ebcc/core/damping.py
@abstractmethod
def push(self, array: NDArray[T], error: Optional[NDArray[T]] = None) -> None:
    """Push the array and error into the damping object.

    Args:
        array: The array to push.
        error: The error array to push.
    """
    pass

ebcc.core.damping.BaseDamping.extrapolate() abstractmethod

Extrapolate the next array.

Returns:
  • NDArray[T]

    The extrapolated array.

Source code in ebcc/core/damping.py
@abstractmethod
def extrapolate(self) -> NDArray[T]:
    """Extrapolate the next array.

    Returns:
        The extrapolated array.
    """
    pass

ebcc.core.damping.BaseDamping.reset()

Reset the damping object.

Source code in ebcc/core/damping.py
def reset(self) -> None:
    """Reset the damping object."""
    self._arrays = {}
    self._errors = {}
    self._counter = 0

ebcc.core.damping.BaseDamping.__len__()

Get the number of arrays stored in the damping object.

Source code in ebcc/core/damping.py
def __len__(self) -> int:
    """Get the number of arrays stored in the damping object."""
    return len(self._arrays)

ebcc.core.damping.NoDamping(*args, options=None, **kwargs)

Bases: BaseDamping

No damping.

Apply damping to the array.

Parameters:
  • array

    The array to damp.

  • error

    The error array.

Returns:
  • None

    The damped array.

Source code in ebcc/core/damping.py
def __init__(self, *args: Any, options: Optional[_BaseOptions] = None, **kwargs: Any) -> None:
    """Apply damping to the array.

    Args:
        array: The array to damp.
        error: The error array.

    Returns:
        The damped array.
    """
    pass

ebcc.core.damping.NoDamping.__call__(array, error=None)

Apply damping to the array.

Parameters:
  • array (NDArray[T]) –

    The array to damp.

  • error (Optional[NDArray[T]], default: None ) –

    The error array.

Returns:
  • NDArray[T]

    The damped array.

Source code in ebcc/core/damping.py
def __call__(self, array: NDArray[T], error: Optional[NDArray[T]] = None) -> NDArray[T]:
    """Apply damping to the array.

    Args:
        array: The array to damp.
        error: The error array.

    Returns:
        The damped array.
    """
    return array

ebcc.core.damping.NoDamping.push(array, error=None)

Push the array and error into the damping object.

Parameters:
  • array (NDArray[T]) –

    The array to push.

  • error (Optional[NDArray[T]], default: None ) –

    The error array to push.

Source code in ebcc/core/damping.py
def push(self, array: NDArray[T], error: Optional[NDArray[T]] = None) -> None:
    """Push the array and error into the damping object.

    Args:
        array: The array to push.
        error: The error array to push.
    """
    raise NotImplementedError

ebcc.core.damping.NoDamping.extrapolate()

Extrapolate the next array.

Returns:
  • NDArray[T]

    The extrapolated array.

Source code in ebcc/core/damping.py
def extrapolate(self) -> NDArray[T]:
    """Extrapolate the next array.

    Returns:
        The extrapolated array.
    """
    raise NotImplementedError

ebcc.core.damping.LinearDamping(*args, factor=None, options=None)

Bases: BaseDamping

Linear damping.

Initialise the damping object.

Parameters:
  • factor (Optional[float], default: None ) –

    The damping factor. If None, use options.damping, if available. Otherwise, use 0.5.

  • options (Optional[_BaseOptions], default: None ) –

    The options object.

Source code in ebcc/core/damping.py
def __init__(
    self, *args: Any, factor: Optional[float] = None, options: Optional[_BaseOptions] = None
) -> None:
    """Initialise the damping object.

    Args:
        factor: The damping factor. If `None`, use `options.damping`, if available. Otherwise,
            use `0.5`.
        options: The options object.
    """
    super().__init__()
    if factor is None:
        factor = getattr(options, "damping", 0.5)
    self.factor = factor

ebcc.core.damping.LinearDamping.push(array, error=None)

Push the array and error into the damping object.

Parameters:
  • array (NDArray[T]) –

    The array to push.

  • error (Optional[NDArray[T]], default: None ) –

    The error array to push.

Source code in ebcc/core/damping.py
def push(self, array: NDArray[T], error: Optional[NDArray[T]] = None) -> None:
    """Push the array and error into the damping object.

    Args:
        array: The array to push.
        error: The error array to push.
    """
    if len(self) == 2:
        self._arrays.pop(min(self._arrays.keys()))
    self._arrays[self._counter] = array
    self._counter += 1

ebcc.core.damping.LinearDamping.extrapolate()

Extrapolate the next array.

Returns:
  • NDArray[T]

    The extrapolated array.

Source code in ebcc/core/damping.py
def extrapolate(self) -> NDArray[T]:
    """Extrapolate the next array.

    Returns:
        The extrapolated array.
    """
    if len(self) < 2:
        return next(iter(self._arrays.values()))
    (_, previous), (_, current) = sorted(self._arrays.items())
    return current * (1.0 - self.factor) + previous * self.factor

ebcc.core.damping.DIIS(space=None, min_space=None, factor=None, options=None)

Bases: BaseDamping

Direct inversion in the iterative subspace.

Initialize the DIIS object.

Parameters:
  • space (Optional[int], default: None ) –

    The number of vectors to store in the DIIS space. If None, use options.diis_space, if available. Otherwise, use 6.

  • min_space (Optional[int], default: None ) –

    The minimum number of vectors to store in the DIIS space. If None, use options.diis_min_space, if available. Otherwise, use 1.

  • factor (Optional[float], default: None ) –

    The damping factor. If None, use options.damping, if available. Otherwise, use 0.5.

  • options (Optional[_BaseOptions], default: None ) –

    The options object.

Source code in ebcc/core/damping.py
def __init__(
    self,
    space: Optional[int] = None,
    min_space: Optional[int] = None,
    factor: Optional[float] = None,
    options: Optional[_BaseOptions] = None,
) -> None:
    """Initialize the DIIS object.

    Args:
        space: The number of vectors to store in the DIIS space. If `None`, use
            `options.diis_space`, if available. Otherwise, use `6`.
        min_space: The minimum number of vectors to store in the DIIS space. If `None`, use
            `options.diis_min_space`, if available. Otherwise, use `1`.
        factor: The damping factor. If `None`, use `options.damping`, if available. Otherwise,
            use `0.5`.
        options: The options object.
    """
    super().__init__()
    if space is None:
        space = getattr(options, "diis_space", 6)
    if min_space is None:
        min_space = getattr(options, "diis_min_space", 1)
    if factor is None:
        factor = getattr(options, "damping", 0.5)
    self.space = space
    self.min_space = min_space
    self.factor = factor
    self._norm_cache: dict[tuple[int, int], T] = {}

ebcc.core.damping.DIIS.push(array, error=None)

Push the array and error into the damping object.

Parameters:
  • array (NDArray[T]) –

    The array to push.

  • error (Optional[NDArray[T]], default: None ) –

    The error array to push.

Source code in ebcc/core/damping.py
def push(self, array: NDArray[T], error: Optional[NDArray[T]] = None) -> None:
    """Push the array and error into the damping object.

    Args:
        array: The array to push.
        error: The error array to push.
    """
    # Get the error if not provided
    if error is None and -1 in self._arrays:
        error = array - self._arrays[-1]
    elif error is None or len(self) == 0:
        self._arrays[-1] = array
        return

    # Push the array and error into the DIIS subspace
    self._arrays[self._counter] = array
    self._errors[self._counter] = error
    self._counter += 1

    # Remove an array if the space is exceeded
    if len(self) > self.space:
        errors = {
            counter: self._error_norm(counter, counter) for counter in self._errors.keys()
        }
        counter = max(errors, key=errors.__getitem__)  # type: ignore[arg-type]
        self._arrays.pop(counter)
        self._errors.pop(counter)

ebcc.core.damping.DIIS.extrapolate()

Extrapolate the next array.

Returns:
  • NDArray[T]

    The extrapolated array.

Source code in ebcc/core/damping.py
def extrapolate(self) -> NDArray[T]:
    """Extrapolate the next array.

    Returns:
        The extrapolated array.
    """
    # Return the last array if the space is less than the minimum space
    counters = sorted(self._errors.keys())
    size = len(counters)
    if size < self.min_space:
        return self._arrays[-1]

    # Build the error matrix
    errors = np.array(
        [
            [self._error_norm(counter_i, counter_j) for counter_j in counters]
            for counter_i in counters
        ]
    )
    zeros = np.zeros((1, 1), dtype=errors.dtype)
    ones = np.ones((size, 1), dtype=errors.dtype)
    matrix = np.block([[errors, -ones], [-np.transpose(ones), zeros]])

    # Build the right-hand side
    zeros = np.zeros((size,), dtype=errors.dtype)
    ones = np.ones((1,), dtype=errors.dtype)
    residual = np.block([zeros, -ones])

    # Solve the linear problem
    try:
        c = np.linalg.solve(matrix, residual)
    except Exception:
        w, v = np.linalg.eigh(matrix)
        if np.any(np.abs(w) < 1e-14):
            # avoiding fancy indexing for compatibility
            for i in range(size + 1):
                if np.abs(w[i]) < 1e-14:
                    _put(v, np.ix_(np.arange(size + 1), np.array([i])), np.zeros_like(v[:, i]))
        c = util.einsum("pi,qi,i,q->p", v, np.conj(v), w**-1.0, residual)

    # Construct the new array
    array = np.zeros_like(self._arrays[next(iter(self._arrays))])
    for counter, coefficient in zip(counters, c):
        array += self._arrays[counter] * coefficient
    error = np.zeros_like(self._errors[next(iter(self._errors))])
    for counter, coefficient in zip(counters, c):
        error += self._errors[counter] * coefficient

    # Apply the damping factor
    if self.factor:
        array = array * (1.0 - self.factor) + self._arrays[-1] * self.factor

    # Replace the previous array with the extrapolated array
    self._arrays[-1] = array
    self._arrays[self._counter] = array
    self._errors[self._counter] = error

    return array

ebcc.core.damping.DIIS.reset()

Reset the damping object.

Source code in ebcc/core/damping.py
def reset(self) -> None:
    """Reset the damping object."""
    super().reset()
    self._norm_cache = {}