Skip to content

Convectionยค

exponax.nonlin_fun.ConvectionNonlinearFun ยค

Bases: BaseNonlinearFun

Source code in exponax/nonlin_fun/_convection.py
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
class ConvectionNonlinearFun(BaseNonlinearFun):
    derivative_operator: Complex[Array, "D ... (N//2)+1"]
    scale: float
    single_channel: bool
    conservative: bool

    def __init__(
        self,
        num_spatial_dims: int,
        num_points: int,
        *,
        derivative_operator: Complex[Array, "D ... (N//2)+1"],
        dealiasing_fraction: float = 2 / 3,
        scale: float = 1.0,
        single_channel: bool = False,
        conservative: bool = False,
    ):
        """
        Performs a pseudo-spectral evaluation of the nonlinear convection, e.g.,
        found in the Burgers equation. In 1d and state space, this reads

        ```
            ๐’ฉ(u) = -bโ‚ u (u)โ‚“
        ```

        with a scale `bโ‚`. The minus arises because `Exponax` follows the
        convention that all nonlinear and linear differential operators are on
        the right-hand side of the equation. Typically, the convection term is
        on the left-hand side. Hence, the minus is required to move the term to
        the right-hand side.

        The typical extension to higher dimensions requires u to have as many
        channels as spatial dimensions and then gives

        ```
            ๐’ฉ(u) = -bโ‚ u โ‹… โˆ‡ u
        ```

        Meanwhile, if you use a conservative form, the convection term is given
        by

        ```
            ๐’ฉ(u) = -1/2 bโ‚ (uยฒ)โ‚“
        ```

        for 1D and

        ```
            ๐’ฉ(u) = -1/2 bโ‚ โˆ‡ โ‹… (u โŠ— u)
        ```

        for 2D and 3D with `โˆ‡ โ‹…` the divergence operator and the outer product
        `u โŠ— u`.

        Another option is a "single-channel" hack requiring only one channel no
        matter the spatial dimensions. This reads

        ```
            ๐’ฉ(u) = -bโ‚ 1/2 (1โƒ— โ‹… โˆ‡)(uยฒ)
        ```

        for the conservative form and

        ```
            ๐’ฉ(u) = -bโ‚ u (1โƒ— โ‹… โˆ‡)u
        ```

        for the non-conservative form.

        **Arguments:**

        - `num_spatial_dims`: The number of spatial dimensions `d`.
        - `num_points`: The number of points `N` used to discretize the
            domain. This **includes** the left boundary point and **excludes**
            the right boundary point. In higher dimensions; the number of points
            in each dimension is the same.
        - `derivative_operator`: A complex array of shape `(d, ..., N//2+1)`
            that represents the derivative operator in Fourier space.
        - `dealiasing_fraction`: The fraction of the highest resolved modes
            that are not aliased. Defaults to `2/3` which corresponds to
            Orszag's 2/3 rule.
        - `scale`: The scale `bโ‚` of the convection term. Defaults to `1.0`.
        - `single_channel`: Whether to use the single-channel hack. Defaults
            to `False`.
        - `conservative`: Whether to use the conservative form. Defaults to
          `False`.
        """
        self.derivative_operator = derivative_operator
        self.scale = scale
        self.single_channel = single_channel
        self.conservative = conservative
        super().__init__(
            num_spatial_dims,
            num_points,
            dealiasing_fraction=dealiasing_fraction,
        )

    def _multi_channel_conservative_eval(
        self, u_hat: Complex[Array, "C ... (N//2)+1"]
    ) -> Complex[Array, "C ... (N//2)+1"]:
        """
        Evaluates the conservative convection term for a multi-channel state
        `u_hat` in Fourier space. The convection term is given by

        ```
            ๐’ฉ(u) = -bโ‚ 1/2 โˆ‡ โ‹… (u โŠ— u)
        ```

        with `โˆ‡ โ‹…` the divergence operator and the outer product `u โŠ— u`.

        **Arguments:**

        - `u_hat`: The state in Fourier space.

        **Returns:**

        - `convection`: The evaluation of the convection term in Fourier space.
        """
        num_channels = u_hat.shape[0]
        if num_channels != self.num_spatial_dims:
            raise ValueError(
                "Number of channels in u_hat should match number of spatial dimensions"
            )
        u_hat_dealiased = self.dealias(u_hat)
        u = self.ifft(u_hat_dealiased)
        u_outer_product = u[None, :] * u[:, None]
        u_outer_product_hat = self.fft(u_outer_product)
        convection = 0.5 * jnp.sum(
            self.derivative_operator[None, :] * u_outer_product_hat,
            axis=1,
        )
        # Requires minus to move term to the rhs
        return -self.scale * convection

    def _multi_channel_nonconservative_eval(
        self, u_hat: Complex[Array, "C ... (N//2)+1"]
    ) -> Complex[Array, "C ... (N//2)+1"]:
        """
        Evaluates the non-conservative convection term for a multi-channel state
        `u_hat` in Fourier space. The convection term is given by

        ```
            ๐’ฉ(u) = -bโ‚ u โ‹… โˆ‡ u
        ```

        **Arguments:**

        - `u_hat`: The state in Fourier space.

        **Returns:**

        - `convection`: The evaluation of the convection term in Fourier space.
        """
        num_channels = u_hat.shape[0]
        if num_channels != self.num_spatial_dims:
            raise ValueError(
                "Number of channels in u_hat should match number of spatial dimensions"
            )
        u_hat_dealiased = self.dealias(u_hat)
        u = self.ifft(u_hat_dealiased)
        nabla_u = self.ifft(
            self.derivative_operator[None, :] * u_hat_dealiased[:, None]
        )
        conv_u = jnp.sum(
            u[None, :] * nabla_u,
            axis=1,
        )
        # Requires minus to move term to the rhs
        return -self.scale * self.fft(conv_u)

    def _single_channel_conservative_eval(
        self, u_hat: Complex[Array, "C ... (N//2)+1"]
    ) -> Complex[Array, "C ... (N//2)+1"]:
        """
        Evaluates the conservative convection term for a single-channel state
        `u_hat` in Fourier space. The convection term is given by

        ```
            ๐’ฉ(u) = -bโ‚ 1/2 (1โƒ— โ‹… โˆ‡)(uยฒ)
        ```

        with `โˆ‡ โ‹…` the divergence operator and `1โƒ—` a vector of ones.

        **Arguments:**

        - `u_hat`: The state in Fourier space.

        **Returns:**

        - `convection`: The evaluation of the convection term in Fourier space.
        """
        u_hat_dealiased = self.dealias(u_hat)
        u = self.ifft(u_hat_dealiased)
        u_square = u**2
        u_square_hat = self.fft(u_square)
        sum_of_derivatives_operator = jnp.sum(
            self.derivative_operator, axis=0, keepdims=True
        )
        convection = 0.5 * sum_of_derivatives_operator * u_square_hat
        # Requires minus to bring convection to the right-hand side
        return -self.scale * convection

    def _single_channel_nonconservative_eval(
        self, u_hat: Complex[Array, "C ... (N//2)+1"]
    ) -> Complex[Array, "C ... (N//2)+1"]:
        """
        Evaluates the non-conservative convection term for a single-channel
        state `u_hat` in Fourier space. The convection term is given by

        ```
            ๐’ฉ(u) = -bโ‚ u (1โƒ— โ‹… โˆ‡)u
        ```

        **Arguments:**

        - `u_hat`: The state in Fourier space.

        **Returns:**

        - `convection`: The evaluation of the convection term in Fourier space.
        """
        u_hat_dealiased = self.dealias(u_hat)
        u = self.ifft(u_hat_dealiased)
        nabla_u = self.ifft(self.derivative_operator * u_hat_dealiased)
        conv_u = jnp.sum(
            u * nabla_u,
            axis=0,
            keepdims=True,
        )
        # Requires minus to bring convection to the right-hand side
        return -self.scale * self.fft(conv_u)

    def __call__(
        self, u_hat: Complex[Array, "C ... (N//2)+1"]
    ) -> Complex[Array, "C ... (N//2)+1"]:
        if self.single_channel:
            if self.conservative:
                return self._single_channel_conservative_eval(u_hat)
            else:
                return self._single_channel_nonconservative_eval(u_hat)
        else:
            if self.conservative:
                return self._multi_channel_conservative_eval(u_hat)
            else:
                return self._multi_channel_nonconservative_eval(u_hat)
__init__ ยค
__init__(
    num_spatial_dims: int,
    num_points: int,
    *,
    derivative_operator: Complex[Array, "D ... (N//2)+1"],
    dealiasing_fraction: float = 2 / 3,
    scale: float = 1.0,
    single_channel: bool = False,
    conservative: bool = False
)

Performs a pseudo-spectral evaluation of the nonlinear convection, e.g., found in the Burgers equation. In 1d and state space, this reads

    ๐’ฉ(u) = -bโ‚ u (u)โ‚“

with a scale bโ‚. The minus arises because Exponax follows the convention that all nonlinear and linear differential operators are on the right-hand side of the equation. Typically, the convection term is on the left-hand side. Hence, the minus is required to move the term to the right-hand side.

The typical extension to higher dimensions requires u to have as many channels as spatial dimensions and then gives

    ๐’ฉ(u) = -bโ‚ u โ‹… โˆ‡ u

Meanwhile, if you use a conservative form, the convection term is given by

    ๐’ฉ(u) = -1/2 bโ‚ (uยฒ)โ‚“

for 1D and

    ๐’ฉ(u) = -1/2 bโ‚ โˆ‡ โ‹… (u โŠ— u)

for 2D and 3D with โˆ‡ โ‹… the divergence operator and the outer product u โŠ— u.

Another option is a "single-channel" hack requiring only one channel no matter the spatial dimensions. This reads

    ๐’ฉ(u) = -bโ‚ 1/2 (1โƒ— โ‹… โˆ‡)(uยฒ)

for the conservative form and

    ๐’ฉ(u) = -bโ‚ u (1โƒ— โ‹… โˆ‡)u

for the non-conservative form.

Arguments:

  • num_spatial_dims: The number of spatial dimensions d.
  • num_points: The number of points N used to discretize the domain. This includes the left boundary point and excludes the right boundary point. In higher dimensions; the number of points in each dimension is the same.
  • derivative_operator: A complex array of shape (d, ..., N//2+1) that represents the derivative operator in Fourier space.
  • dealiasing_fraction: The fraction of the highest resolved modes that are not aliased. Defaults to 2/3 which corresponds to Orszag's 2/3 rule.
  • scale: The scale bโ‚ of the convection term. Defaults to 1.0.
  • single_channel: Whether to use the single-channel hack. Defaults to False.
  • conservative: Whether to use the conservative form. Defaults to False.
Source code in exponax/nonlin_fun/_convection.py
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
def __init__(
    self,
    num_spatial_dims: int,
    num_points: int,
    *,
    derivative_operator: Complex[Array, "D ... (N//2)+1"],
    dealiasing_fraction: float = 2 / 3,
    scale: float = 1.0,
    single_channel: bool = False,
    conservative: bool = False,
):
    """
    Performs a pseudo-spectral evaluation of the nonlinear convection, e.g.,
    found in the Burgers equation. In 1d and state space, this reads

    ```
        ๐’ฉ(u) = -bโ‚ u (u)โ‚“
    ```

    with a scale `bโ‚`. The minus arises because `Exponax` follows the
    convention that all nonlinear and linear differential operators are on
    the right-hand side of the equation. Typically, the convection term is
    on the left-hand side. Hence, the minus is required to move the term to
    the right-hand side.

    The typical extension to higher dimensions requires u to have as many
    channels as spatial dimensions and then gives

    ```
        ๐’ฉ(u) = -bโ‚ u โ‹… โˆ‡ u
    ```

    Meanwhile, if you use a conservative form, the convection term is given
    by

    ```
        ๐’ฉ(u) = -1/2 bโ‚ (uยฒ)โ‚“
    ```

    for 1D and

    ```
        ๐’ฉ(u) = -1/2 bโ‚ โˆ‡ โ‹… (u โŠ— u)
    ```

    for 2D and 3D with `โˆ‡ โ‹…` the divergence operator and the outer product
    `u โŠ— u`.

    Another option is a "single-channel" hack requiring only one channel no
    matter the spatial dimensions. This reads

    ```
        ๐’ฉ(u) = -bโ‚ 1/2 (1โƒ— โ‹… โˆ‡)(uยฒ)
    ```

    for the conservative form and

    ```
        ๐’ฉ(u) = -bโ‚ u (1โƒ— โ‹… โˆ‡)u
    ```

    for the non-conservative form.

    **Arguments:**

    - `num_spatial_dims`: The number of spatial dimensions `d`.
    - `num_points`: The number of points `N` used to discretize the
        domain. This **includes** the left boundary point and **excludes**
        the right boundary point. In higher dimensions; the number of points
        in each dimension is the same.
    - `derivative_operator`: A complex array of shape `(d, ..., N//2+1)`
        that represents the derivative operator in Fourier space.
    - `dealiasing_fraction`: The fraction of the highest resolved modes
        that are not aliased. Defaults to `2/3` which corresponds to
        Orszag's 2/3 rule.
    - `scale`: The scale `bโ‚` of the convection term. Defaults to `1.0`.
    - `single_channel`: Whether to use the single-channel hack. Defaults
        to `False`.
    - `conservative`: Whether to use the conservative form. Defaults to
      `False`.
    """
    self.derivative_operator = derivative_operator
    self.scale = scale
    self.single_channel = single_channel
    self.conservative = conservative
    super().__init__(
        num_spatial_dims,
        num_points,
        dealiasing_fraction=dealiasing_fraction,
    )
__call__ ยค
__call__(
    u_hat: Complex[Array, "C ... (N//2)+1"]
) -> Complex[Array, "C ... (N//2)+1"]
Source code in exponax/nonlin_fun/_convection.py
239
240
241
242
243
244
245
246
247
248
249
250
251
def __call__(
    self, u_hat: Complex[Array, "C ... (N//2)+1"]
) -> Complex[Array, "C ... (N//2)+1"]:
    if self.single_channel:
        if self.conservative:
            return self._single_channel_conservative_eval(u_hat)
        else:
            return self._single_channel_nonconservative_eval(u_hat)
    else:
        if self.conservative:
            return self._multi_channel_conservative_eval(u_hat)
        else:
            return self._multi_channel_nonconservative_eval(u_hat)