Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add non-conservative form for convection operator #45

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 48 additions & 8 deletions exponax/nonlin_fun/_convection.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class ConvectionNonlinearFun(BaseNonlinearFun):
derivative_operator: Complex[Array, "D ... (N//2)+1"]
scale: float
single_channel: bool
conservative: bool

def __init__(
self,
Expand All @@ -18,13 +19,14 @@ def __init__(
dealiasing_fraction: float = 2 / 3,
scale: float = 1.0,
single_channel: bool = False,
conservative: bool = False,
qiauil marked this conversation as resolved.
Show resolved Hide resolved
):
"""
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₁ 1/2 (u²)ₓ
𝒩(u) = b₁ u (u)ₓ
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi Qiang,
I think we should keep the minus here, right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes! That was my mistake... all the minus should be kept here.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great 👍
Could you add them again in the PR?

```

with a scale `b₁`. The minus arises because `Exponax` follows the
Expand All @@ -37,10 +39,23 @@ def __init__(
channels as spatial dimensions and then gives

```
𝒩(u) = - b₁ 1/2 ∇ ⋅ (u ⊗ u)
𝒩(u) = b₁ u ⋅ ∇ u
qiauil marked this conversation as resolved.
Show resolved Hide resolved
```

with `∇ ⋅` the divergence operator and the outer product `u ⊗ u`.

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

```
𝒩(u) = b₁ u (u)ₓ
qiauil marked this conversation as resolved.
Show resolved Hide resolved
```
for 1D and

```
𝒩(u) = b₁ ∇ ⋅ (u ⊗ u)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you tell me what the reason was for removing the 1/2? Now, I think that the conservative and nonconservative no longer match with each other.

```

for 2D and 3D.

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

Expand All @@ -49,7 +64,6 @@ def __init__(
```

**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**
Expand All @@ -63,17 +77,19 @@ def __init__(
- `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_eval(
def _multi_channel_conservative_eval(
self, u_hat: Complex[Array, "C ... (N//2)+1"]
) -> Complex[Array, "C ... (N//2)+1"]:
"""
Expand Down Expand Up @@ -101,14 +117,35 @@ def _multi_channel_eval(
)
u_hat_dealiased = self.dealias(u_hat)
u = self.ifft(u_hat_dealiased)
u_outer_product = u[:, None] * u[None, :]
u_outer_product = u[None, :] * u[:, None]
u_outer_product_hat = self.fft(u_outer_product)
convection = 0.5 * jnp.sum(
convection = 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"]:
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[:, None])
conv_u = jnp.sum(
u[None, :] * nabla_u,
axis=1,
)
#conv_u=sum(
# [u[i:i+1]*self.ifft(self.derivative_operator[i:i+1]*u_hat) for i in range(num_channels)]
#)
# Requires minus to move term to the rhs
return -self.scale * self.fft(conv_u)

def _single_channel_eval(
self, u_hat: Complex[Array, "C ... (N//2)+1"]
Expand Down Expand Up @@ -148,4 +185,7 @@ def __call__(
if self.single_channel:
return self._single_channel_eval(u_hat)
else:
return self._multi_channel_eval(u_hat)
if self.conservative:
return self._multi_channel_conservative_eval(u_hat)
else:
return self._multi_channel_nonconservative_eval(u_hat)
6 changes: 6 additions & 0 deletions exponax/stepper/_burgers.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ class Burgers(BaseStepper):
convection_scale: float
dealiasing_fraction: float
single_channel: bool
conservative: bool

def __init__(
self,
Expand All @@ -21,6 +22,7 @@ def __init__(
diffusivity: float = 0.1,
convection_scale: float = 1.0,
single_channel: bool = False,
conservative: bool = False,
order=2,
dealiasing_fraction: float = 2 / 3,
num_circle_points: int = 16,
Expand Down Expand Up @@ -75,6 +77,8 @@ def __init__(
dimensions. In this case the the convection is `b₁ (∇ ⋅ 1)(u²)`. In
this case, the state always has a single channel, no matter the
spatial dimension. Default: False.
- `conservative`: Whether to use the conservative form of the convection
term. Default: False.
- `order`: The order of the Exponential Time Differencing Runge
Kutta method. Must be one of {0, 1, 2, 3, 4}. The option `0` only
solves the linear part of the equation. Use higher values for higher
Expand Down Expand Up @@ -112,6 +116,7 @@ def __init__(
self.diffusivity = diffusivity
self.convection_scale = convection_scale
self.single_channel = single_channel
self.conservative = conservative
self.dealiasing_fraction = dealiasing_fraction

if single_channel:
Expand Down Expand Up @@ -149,4 +154,5 @@ def _build_nonlinear_fun(
dealiasing_fraction=self.dealiasing_fraction,
scale=self.convection_scale,
single_channel=self.single_channel,
conservative=False,
)
6 changes: 6 additions & 0 deletions exponax/stepper/_korteweg_de_vries.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class KortewegDeVries(BaseStepper):
advect_over_diffuse: bool
diffuse_over_diffuse: bool
single_channel: bool
conservaitve: bool

def __init__(
self,
Expand All @@ -34,6 +35,7 @@ def __init__(
advect_over_diffuse: bool = False,
diffuse_over_diffuse: bool = False,
single_channel: bool = False,
conservative: bool = False,
order: int = 2,
dealiasing_fraction: float = 2 / 3,
num_circle_points: int = 16,
Expand Down Expand Up @@ -108,6 +110,8 @@ def __init__(
dimensions. In this case the the convection is `b₁ (∇ ⋅ 1)(u²)`. In
this case, the state always has a single channel, no matter the
spatial dimension. Default: False.
- `conservative`: Whether to use the conservative form of the convection
term. Default: False.
- `order`: The order of the Exponential Time Differencing Runge
Kutta method. Must be one of {0, 1, 2, 3, 4}. The option `0` only
solves the linear part of the equation. Use higher values for higher
Expand Down Expand Up @@ -145,6 +149,7 @@ def __init__(
self.advect_over_diffuse = advect_over_diffuse
self.diffuse_over_diffuse = diffuse_over_diffuse
self.single_channel = single_channel
self.conservative = conservative
self.dealiasing_fraction = dealiasing_fraction

if single_channel:
Expand Down Expand Up @@ -210,4 +215,5 @@ def _build_nonlinear_fun(
dealiasing_fraction=self.dealiasing_fraction,
scale=self.convection_scale,
single_channel=self.single_channel,
conservative=self.conservative,
)
4 changes: 4 additions & 0 deletions exponax/stepper/_kuramoto_sivashinsky.py
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,7 @@ class KuramotoSivashinskyConservative(BaseStepper):
second_order_diffusivity: float
fourth_order_diffusivity: float
single_channel: bool
conservative: bool
dealiasing_fraction: float

def __init__(
Expand All @@ -189,6 +190,7 @@ def __init__(
second_order_diffusivity: float = 1.0,
fourth_order_diffusivity: float = 1.0,
single_channel: bool = False,
conservative: bool = False,
dealiasing_fraction: float = 2 / 3,
order: int = 2,
num_circle_points: int = 16,
Expand All @@ -203,6 +205,7 @@ def __init__(
self.second_order_diffusivity = second_order_diffusivity
self.fourth_order_diffusivity = fourth_order_diffusivity
self.single_channel = single_channel
self.conservative = conservative
self.dealiasing_fraction = dealiasing_fraction

if num_spatial_dims > 1:
Expand Down Expand Up @@ -252,4 +255,5 @@ def _build_nonlinear_fun(
dealiasing_fraction=self.dealiasing_fraction,
scale=self.convection_scale,
single_channel=self.single_channel,
conservative=self.conservative,
)
13 changes: 13 additions & 0 deletions exponax/stepper/generic/_convection.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ class GeneralConvectionStepper(BaseStepper):
convection_scale: float
dealiasing_fraction: float
single_channel: bool
conservative: bool

def __init__(
self,
Expand All @@ -25,6 +26,7 @@ def __init__(
coefficients: tuple[float, ...] = (0.0, 0.0, 0.01),
convection_scale: float = 1.0,
single_channel: bool = False,
conservative: bool = False,
order=2,
dealiasing_fraction: float = 2 / 3,
num_circle_points: int = 16,
Expand Down Expand Up @@ -83,6 +85,8 @@ def __init__(
dimensions. In this case the the convection is `b₁ (∇ ⋅ 1)(u²)`. In
this case, the state always has a single channel, no matter the
spatial dimension. Default: False.
- `conservative`: Whether to use the conservative form of the convection
term. Default: False.
- `order`: The order of the Exponential Time Differencing Runge
Kutta method. Must be one of {0, 1, 2, 3, 4}. The option `0` only
solves the linear part of the equation. Use higher values for higher
Expand All @@ -103,6 +107,7 @@ def __init__(
self.convection_scale = convection_scale
self.single_channel = single_channel
self.dealiasing_fraction = dealiasing_fraction
self.conservative = conservative

if single_channel:
num_channels = 1
Expand Down Expand Up @@ -146,6 +151,7 @@ def _build_nonlinear_fun(
dealiasing_fraction=self.dealiasing_fraction,
scale=self.convection_scale,
single_channel=self.single_channel,
conservative=self.conservative,
)


Expand All @@ -161,6 +167,7 @@ def __init__(
normalized_coefficients: tuple[float, ...] = (0.0, 0.0, 0.01 * 0.1),
normalized_convection_scale: float = 1.0 * 0.1,
single_channel: bool = False,
conservative: bool = False,
order: int = 2,
dealiasing_fraction: float = 2 / 3,
num_circle_points: int = 16,
Expand Down Expand Up @@ -210,6 +217,8 @@ def __init__(
dimensions. In this case the the convection is `β (∇ ⋅ 1)(u²)`. In
this case, the state always has a single channel, no matter the
spatial dimension. Default: False.
- `conservative`: Whether to use the conservative form of the convection
term. Default: False.
- `order`: The order of the Exponential Time Differencing Runge
Kutta method. Must be one of {0, 1, 2, 3, 4}. The option `0` only
solves the linear part of the equation. Use higher values for higher
Expand Down Expand Up @@ -240,6 +249,7 @@ def __init__(
num_circle_points=num_circle_points,
circle_radius=circle_radius,
single_channel=single_channel,
conservative=conservative,
)


Expand All @@ -255,6 +265,7 @@ def __init__(
linear_difficulties: tuple[float, ...] = (0.0, 0.0, 4.5),
convection_difficulty: float = 5.0,
single_channel: bool = False,
conservative: bool = False,
maximum_absolute: float = 1.0,
order: int = 2,
dealiasing_fraction: float = 2 / 3,
Expand Down Expand Up @@ -315,6 +326,7 @@ def __init__(
dimensions. In this case the the convection is `δ (∇ ⋅ 1)(u²)`. In
this case, the state always has a single channel, no matter the
spatial dimension. Default: False.
- `conservative`: Whether to use the conservative form of the convection
- `maximum_absolute`: The maximum absolute value of the state. This is
used to extract the normalized dynamics from the convection
difficulty.
Expand Down Expand Up @@ -359,4 +371,5 @@ def __init__(
dealiasing_fraction=dealiasing_fraction,
num_circle_points=num_circle_points,
circle_radius=circle_radius,
conservative=conservative,
)