Skip to content

Spectral Filters

SpectralFilter1D

Bases: Module

1D Spectral filter for smoothing and numerical stabilization.

Mathematical Formulation:

Filters are applied as a multiplicative mask in Fourier space: u_hat_filtered(k) = F(k) * u_hat(k)

where F(k) is the filter kernel.

Attributes:

Name Type Description
grid FourierGrid1D

The 1D Fourier grid object containing wavenumbers k [N].

Source code in spectraldiffx/_src/fourier/filters.py
class SpectralFilter1D(eqx.Module):
    """
    1D Spectral filter for smoothing and numerical stabilization.

    Mathematical Formulation:
    -------------------------
    Filters are applied as a multiplicative mask in Fourier space:
        u_hat_filtered(k) = F(k) * u_hat(k)

    where F(k) is the filter kernel.

    Attributes
    ----------
        grid : FourierGrid1D
            The 1D Fourier grid object containing wavenumbers k [N].
    """

    grid: FourierGrid1D

    def exponential_filter(
        self, u: Array, alpha: float = 36.0, power: int = 16, spectral: bool = False
    ) -> Array:
        """
        Apply 1D exponential filter: F(k) = exp(-alpha * (abs(k) / k_max)^power)

        This filter is near unity for low wavenumbers and falls off sharply
        near the grid scale (k_max), removing poorly resolved high frequencies.

        Parameters
        ----------
        u : Array [N]
            Physical space field or spectral coefficients of the field.
        alpha : float, optional
            Damping coefficient. Default is 36.0.
        power : int, optional
            Sharpening order (even integer). Default is 16.
        spectral : bool, optional
            If True, u is treated as spectral coefficients (u_hat). Default is False.

        Returns
        -------
        Array [N]
            Filtered field or spectral coefficients.
        """
        u_hat = u if spectral else self.grid.transform(u)
        filter_mask = _exponential_1d(self.grid.k, alpha, power)
        u_hat_f = u_hat * filter_mask
        return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

    def hyperviscosity(
        self,
        u: Array,
        nu_hyper: float,
        dt: float,
        power: int = 4,
        spectral: bool = False,
    ) -> Array:
        """
        Apply 1D hyperviscous damping: F(k) = exp(-nu_h * abs(k)^power * dt)

        Simulates the effect of high-order diffusion: du/dt = -nu_h * (-1)^(p/2) * d^p u / dx^p.

        Parameters
        ----------
        u : Array [N]
            Physical space field or spectral coefficients of the field.
        nu_hyper : float
            Hyperviscosity coefficient.
        dt : float
            Effective time step for the damping.
        power : int, optional
            Order of the Laplacian power (e.g., 4 for biharmonic). Default is 4.
        spectral : bool, optional
            If True, u is treated as spectral coefficients (u_hat). Default is False.
        """
        u_hat = u if spectral else self.grid.transform(u)
        k = self.grid.k
        filter_mask = jnp.exp(-nu_hyper * jnp.abs(k) ** power * dt)
        u_hat_f = u_hat * filter_mask
        return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

Functions

exponential_filter(u, alpha=36.0, power=16, spectral=False)

Apply 1D exponential filter: F(k) = exp(-alpha * (abs(k) / k_max)^power)

This filter is near unity for low wavenumbers and falls off sharply near the grid scale (k_max), removing poorly resolved high frequencies.

Parameters:

Name Type Description Default
u Array[N]

Physical space field or spectral coefficients of the field.

required
alpha float

Damping coefficient. Default is 36.0.

36.0
power int

Sharpening order (even integer). Default is 16.

16
spectral bool

If True, u is treated as spectral coefficients (u_hat). Default is False.

False

Returns:

Type Description
Array[N]

Filtered field or spectral coefficients.

Source code in spectraldiffx/_src/fourier/filters.py
def exponential_filter(
    self, u: Array, alpha: float = 36.0, power: int = 16, spectral: bool = False
) -> Array:
    """
    Apply 1D exponential filter: F(k) = exp(-alpha * (abs(k) / k_max)^power)

    This filter is near unity for low wavenumbers and falls off sharply
    near the grid scale (k_max), removing poorly resolved high frequencies.

    Parameters
    ----------
    u : Array [N]
        Physical space field or spectral coefficients of the field.
    alpha : float, optional
        Damping coefficient. Default is 36.0.
    power : int, optional
        Sharpening order (even integer). Default is 16.
    spectral : bool, optional
        If True, u is treated as spectral coefficients (u_hat). Default is False.

    Returns
    -------
    Array [N]
        Filtered field or spectral coefficients.
    """
    u_hat = u if spectral else self.grid.transform(u)
    filter_mask = _exponential_1d(self.grid.k, alpha, power)
    u_hat_f = u_hat * filter_mask
    return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

hyperviscosity(u, nu_hyper, dt, power=4, spectral=False)

Apply 1D hyperviscous damping: F(k) = exp(-nu_h * abs(k)^power * dt)

Simulates the effect of high-order diffusion: du/dt = -nu_h * (-1)^(p/2) * d^p u / dx^p.

Parameters:

Name Type Description Default
u Array[N]

Physical space field or spectral coefficients of the field.

required
nu_hyper float

Hyperviscosity coefficient.

required
dt float

Effective time step for the damping.

required
power int

Order of the Laplacian power (e.g., 4 for biharmonic). Default is 4.

4
spectral bool

If True, u is treated as spectral coefficients (u_hat). Default is False.

False
Source code in spectraldiffx/_src/fourier/filters.py
def hyperviscosity(
    self,
    u: Array,
    nu_hyper: float,
    dt: float,
    power: int = 4,
    spectral: bool = False,
) -> Array:
    """
    Apply 1D hyperviscous damping: F(k) = exp(-nu_h * abs(k)^power * dt)

    Simulates the effect of high-order diffusion: du/dt = -nu_h * (-1)^(p/2) * d^p u / dx^p.

    Parameters
    ----------
    u : Array [N]
        Physical space field or spectral coefficients of the field.
    nu_hyper : float
        Hyperviscosity coefficient.
    dt : float
        Effective time step for the damping.
    power : int, optional
        Order of the Laplacian power (e.g., 4 for biharmonic). Default is 4.
    spectral : bool, optional
        If True, u is treated as spectral coefficients (u_hat). Default is False.
    """
    u_hat = u if spectral else self.grid.transform(u)
    k = self.grid.k
    filter_mask = jnp.exp(-nu_hyper * jnp.abs(k) ** power * dt)
    u_hat_f = u_hat * filter_mask
    return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

SpectralFilter2D

Bases: Module

2D Spectral filter for doubly periodic domains.

Attributes:

Name Type Description
grid FourierGrid2D

The 2D Fourier grid object [Ny, Nx].

Source code in spectraldiffx/_src/fourier/filters.py
class SpectralFilter2D(eqx.Module):
    """
    2D Spectral filter for doubly periodic domains.

    Attributes
    ----------
        grid : FourierGrid2D
            The 2D Fourier grid object [Ny, Nx].
    """

    grid: FourierGrid2D

    def exponential_filter(
        self, u: Array, alpha: float = 36.0, power: int = 16, spectral: bool = False
    ) -> Array:
        """Apply the 2D exponential filter, a tensor product of 1D filters.

            F(kx, ky) = exp(-alpha (|kx|/kx_max)^power) · exp(-alpha (|ky|/ky_max)^power)

        with kx_max, ky_max the per-axis Nyquist wavenumbers, so every
        axis-Nyquist mode is damped by exp(-alpha), as in 1D and consistent
        with the per-axis 2/3 mask. (Normalising |k| by the corner of the
        wavenumber box instead left the axis Nyquist modes almost undamped,
        gh-89.)

        Parameters
        ----------
        u : Array [Ny, Nx]
            Physical field, or its spectral coefficients if ``spectral``.
        alpha : float, optional
            Damping at each axis Nyquist, exp(-alpha). Default 36.0.
        power : int, optional
            Sharpening order (even integer). Default 16.
        spectral : bool, optional
            If True, ``u`` is already spectral and the result stays spectral.

        Returns
        -------
        Array [Ny, Nx]
            Filtered field (or spectral coefficients).
        """
        u_hat = u if spectral else self.grid.transform(u)
        filter_mask = (
            _exponential_1d(self.grid.ky, alpha, power)[:, None]
            * _exponential_1d(self.grid.kx, alpha, power)[None, :]
        )
        u_hat_f = u_hat * filter_mask
        return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

    def hyperviscosity(
        self,
        u: Array,
        nu_hyper: float,
        dt: float,
        power: int = 4,
        spectral: bool = False,
    ) -> Array:
        """Apply 2D hyperviscosity: F(k) = exp(-nu_h * |k|^power * dt)"""
        u_hat = u if spectral else self.grid.transform(u)
        K2 = self.grid.K2
        filter_mask = jnp.exp(-nu_hyper * K2 ** (power / 2) * dt)
        u_hat_f = u_hat * filter_mask
        return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

Functions

exponential_filter(u, alpha=36.0, power=16, spectral=False)

Apply the 2D exponential filter, a tensor product of 1D filters.

F(kx, ky) = exp(-alpha (|kx|/kx_max)^power) · exp(-alpha (|ky|/ky_max)^power)

with kx_max, ky_max the per-axis Nyquist wavenumbers, so every axis-Nyquist mode is damped by exp(-alpha), as in 1D and consistent with the per-axis 2/3 mask. (Normalising |k| by the corner of the wavenumber box instead left the axis Nyquist modes almost undamped, gh-89.)

Parameters:

Name Type Description Default
u Array[Ny, Nx]

Physical field, or its spectral coefficients if spectral.

required
alpha float

Damping at each axis Nyquist, exp(-alpha). Default 36.0.

36.0
power int

Sharpening order (even integer). Default 16.

16
spectral bool

If True, u is already spectral and the result stays spectral.

False

Returns:

Type Description
Array[Ny, Nx]

Filtered field (or spectral coefficients).

Source code in spectraldiffx/_src/fourier/filters.py
def exponential_filter(
    self, u: Array, alpha: float = 36.0, power: int = 16, spectral: bool = False
) -> Array:
    """Apply the 2D exponential filter, a tensor product of 1D filters.

        F(kx, ky) = exp(-alpha (|kx|/kx_max)^power) · exp(-alpha (|ky|/ky_max)^power)

    with kx_max, ky_max the per-axis Nyquist wavenumbers, so every
    axis-Nyquist mode is damped by exp(-alpha), as in 1D and consistent
    with the per-axis 2/3 mask. (Normalising |k| by the corner of the
    wavenumber box instead left the axis Nyquist modes almost undamped,
    gh-89.)

    Parameters
    ----------
    u : Array [Ny, Nx]
        Physical field, or its spectral coefficients if ``spectral``.
    alpha : float, optional
        Damping at each axis Nyquist, exp(-alpha). Default 36.0.
    power : int, optional
        Sharpening order (even integer). Default 16.
    spectral : bool, optional
        If True, ``u`` is already spectral and the result stays spectral.

    Returns
    -------
    Array [Ny, Nx]
        Filtered field (or spectral coefficients).
    """
    u_hat = u if spectral else self.grid.transform(u)
    filter_mask = (
        _exponential_1d(self.grid.ky, alpha, power)[:, None]
        * _exponential_1d(self.grid.kx, alpha, power)[None, :]
    )
    u_hat_f = u_hat * filter_mask
    return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

hyperviscosity(u, nu_hyper, dt, power=4, spectral=False)

Apply 2D hyperviscosity: F(k) = exp(-nu_h * |k|^power * dt)

Source code in spectraldiffx/_src/fourier/filters.py
def hyperviscosity(
    self,
    u: Array,
    nu_hyper: float,
    dt: float,
    power: int = 4,
    spectral: bool = False,
) -> Array:
    """Apply 2D hyperviscosity: F(k) = exp(-nu_h * |k|^power * dt)"""
    u_hat = u if spectral else self.grid.transform(u)
    K2 = self.grid.K2
    filter_mask = jnp.exp(-nu_hyper * K2 ** (power / 2) * dt)
    u_hat_f = u_hat * filter_mask
    return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

SpectralFilter3D

Bases: Module

3D Spectral filter for triply periodic domains.

Attributes:

Name Type Description
grid FourierGrid3D

The 3D Fourier grid object [Nz, Ny, Nx].

Source code in spectraldiffx/_src/fourier/filters.py
class SpectralFilter3D(eqx.Module):
    """
    3D Spectral filter for triply periodic domains.

    Attributes
    ----------
        grid : FourierGrid3D
            The 3D Fourier grid object [Nz, Ny, Nx].
    """

    grid: FourierGrid3D

    def exponential_filter(
        self, u: Array, alpha: float = 36.0, power: int = 16, spectral: bool = False
    ) -> Array:
        """Apply the 3D exponential filter, a tensor product of 1D filters.

            F = Π_{d ∈ z, y, x} exp(-alpha (|k_d| / k_d,max)^power)

        with per-axis Nyquist normalisation (see ``SpectralFilter2D``, gh-89).

        Parameters
        ----------
        u : Array [Nz, Ny, Nx]
            Physical field, or its spectral coefficients if ``spectral``.
        alpha : float, optional
            Damping at each axis Nyquist, exp(-alpha). Default 36.0.
        power : int, optional
            Sharpening order (even integer). Default 16.
        spectral : bool, optional
            If True, ``u`` is already spectral and the result stays spectral.

        Returns
        -------
        Array [Nz, Ny, Nx]
            Filtered field (or spectral coefficients).
        """
        u_hat = u if spectral else self.grid.transform(u)
        filter_mask = (
            _exponential_1d(self.grid.kz, alpha, power)[:, None, None]
            * _exponential_1d(self.grid.ky, alpha, power)[None, :, None]
            * _exponential_1d(self.grid.kx, alpha, power)[None, None, :]
        )
        u_hat_f = u_hat * filter_mask
        return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

    def hyperviscosity(
        self,
        u: Array,
        nu_hyper: float,
        dt: float,
        power: int = 4,
        spectral: bool = False,
    ) -> Array:
        """Apply 3D hyperviscosity: exp(-nu_h * |k|^power * dt)"""
        u_hat = u if spectral else self.grid.transform(u)
        K2 = self.grid.K2
        filter_mask = jnp.exp(-nu_hyper * K2 ** (power / 2) * dt)
        u_hat_f = u_hat * filter_mask
        return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

Functions

exponential_filter(u, alpha=36.0, power=16, spectral=False)

Apply the 3D exponential filter, a tensor product of 1D filters.

F = Π_{d ∈ z, y, x} exp(-alpha (|k_d| / k_d,max)^power)

with per-axis Nyquist normalisation (see SpectralFilter2D, gh-89).

Parameters:

Name Type Description Default
u Array[Nz, Ny, Nx]

Physical field, or its spectral coefficients if spectral.

required
alpha float

Damping at each axis Nyquist, exp(-alpha). Default 36.0.

36.0
power int

Sharpening order (even integer). Default 16.

16
spectral bool

If True, u is already spectral and the result stays spectral.

False

Returns:

Type Description
Array[Nz, Ny, Nx]

Filtered field (or spectral coefficients).

Source code in spectraldiffx/_src/fourier/filters.py
def exponential_filter(
    self, u: Array, alpha: float = 36.0, power: int = 16, spectral: bool = False
) -> Array:
    """Apply the 3D exponential filter, a tensor product of 1D filters.

        F = Π_{d ∈ z, y, x} exp(-alpha (|k_d| / k_d,max)^power)

    with per-axis Nyquist normalisation (see ``SpectralFilter2D``, gh-89).

    Parameters
    ----------
    u : Array [Nz, Ny, Nx]
        Physical field, or its spectral coefficients if ``spectral``.
    alpha : float, optional
        Damping at each axis Nyquist, exp(-alpha). Default 36.0.
    power : int, optional
        Sharpening order (even integer). Default 16.
    spectral : bool, optional
        If True, ``u`` is already spectral and the result stays spectral.

    Returns
    -------
    Array [Nz, Ny, Nx]
        Filtered field (or spectral coefficients).
    """
    u_hat = u if spectral else self.grid.transform(u)
    filter_mask = (
        _exponential_1d(self.grid.kz, alpha, power)[:, None, None]
        * _exponential_1d(self.grid.ky, alpha, power)[None, :, None]
        * _exponential_1d(self.grid.kx, alpha, power)[None, None, :]
    )
    u_hat_f = u_hat * filter_mask
    return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real

hyperviscosity(u, nu_hyper, dt, power=4, spectral=False)

Apply 3D hyperviscosity: exp(-nu_h * |k|^power * dt)

Source code in spectraldiffx/_src/fourier/filters.py
def hyperviscosity(
    self,
    u: Array,
    nu_hyper: float,
    dt: float,
    power: int = 4,
    spectral: bool = False,
) -> Array:
    """Apply 3D hyperviscosity: exp(-nu_h * |k|^power * dt)"""
    u_hat = u if spectral else self.grid.transform(u)
    K2 = self.grid.K2
    filter_mask = jnp.exp(-nu_hyper * K2 ** (power / 2) * dt)
    u_hat_f = u_hat * filter_mask
    return u_hat_f if spectral else self.grid.transform(u_hat_f, inverse=True).real