Skip to content

5. field

torchfsm.field.diffused_noise ¤

diffused_noise(
    mesh: Union[
        Sequence[tuple[float, float, int]],
        MeshGrid,
        FourierMesh,
    ],
    diffusion_coef: float = 1.0,
    device: Optional[device] = None,
    dtype: Optional[dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode: Optional[
        Literal["normal_distribution", "-1_1", "0_1"]
    ] = None,
) -> SpatialTensor["B C H ..."]

Generate a diffused noise field. The noise is generated by integrating a Laplacian operator with a random initial condition. The diffusion coefficient controls the amount of diffusion applied to the noise.

Parameters:

Name Type Description Default
mesh Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]

The mesh to generate the noise on. If a sequence is provided, it should be in the form of [(x_min, x_max, n_points), ...].

required
diffusion_coef float

The diffusion coefficient. Default is 1.0.

1.0
device Optional[device]

The device to generate the noise on. Default is None.

None
dtype Optional[dtype]

The data type of the generated noise. Default is None.

None
batch_size int

The number of batches. Default is 1.

1
n_channel int

The number of channels. Default is 1.

1
normalize_mode Optional[Literal['normal_distribution', '-1_1', '0_1']]

The normalization mode for the generated noise. If None, no normalization is applied. Default is None.

None

Returns:

Type Description
SpatialTensor['B C H ...']

SpatialTensor["B C H ...]: The generated diffused noise field.

Source code in torchfsm/field/_diffused_noise.py
 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
def diffused_noise(
    mesh: Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh],
    diffusion_coef: float = 1.0,
    device: Optional[torch.device] = None,
    dtype: Optional[torch.dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode:Optional[Literal["normal_distribution","-1_1","0_1"]]=None,
) -> SpatialTensor["B C H ..."]:
    r"""
    Generate a diffused noise field.
        The noise is generated by integrating a Laplacian operator with a random initial condition.
        The diffusion coefficient controls the amount of diffusion applied to the noise.

    Args:
        mesh (Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]): The mesh to generate the noise on.
            If a sequence is provided, it should be in the form of [(x_min, x_max, n_points), ...].
        diffusion_coef (float): The diffusion coefficient. Default is 1.0.
        device (Optional[torch.device]): The device to generate the noise on. Default is None.
        dtype (Optional[torch.dtype]): The data type of the generated noise. Default is None.
        batch_size (int): The number of batches. Default is 1.
        n_channel (int): The number of channels. Default is 1.
        normalize_mode (Optional[Literal["normal_distribution","-1_1","0_1"]]): The normalization mode for the generated noise.
            If None, no normalization is applied. Default is None.

    Returns:
        SpatialTensor["B C H ...]: The generated diffused noise field.
    """
    if device is None and (isinstance(mesh, FourierMesh) or isinstance(mesh, MeshGrid)):
        device = mesh.device
    if dtype is None and (isinstance(mesh, FourierMesh) or isinstance(mesh, MeshGrid)):
        dtype = mesh.dtype
    u_0 = torch.randn(
        *mesh_shape(mesh, batch_size=batch_size, n_channel=n_channel),
        device=device,
        dtype=dtype
    )
    diffusion = diffusion_coef * Laplacian()
    u_0 = diffusion.integrate(u_0, dt=1, step=1, mesh=mesh)
    del diffusion
    clean_up_memory()
    if normalize_mode is not None:
        u_0 = normalize(u_0, normalize_mode=normalize_mode)
    return u_0

torchfsm.field.kolm_force ¤

kolm_force(
    x: Tensor,
    drag_coef: float = -0.1,
    k: float = 4.0,
    length_scale: float = 1.0,
) -> Operator

Generate cosine force field for 2d kolmogorov flow in vorticity form. It is defined as \(a \omega - k cos (k l x)\)

Parameters:

Name Type Description Default
x Tensor

The input tensor.

required
drag_coef float

The drag coefficient \(a\). Default is -0.1.

-0.1
k float

The wave number. Default is 4.0.

4.0
length_scale float

The length scale \(l\). Default is 1.0.

1.0
Source code in torchfsm/field/_kolm_force.py
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
def kolm_force(
    x: torch.Tensor,
    drag_coef: float = -0.1,
    k: float = 4.0,
    length_scale: float = 1.0,
) -> Operator:
    r"""
    Generate cosine force field for 2d kolmogorov flow in vorticity form.
        It is defined as $a \omega - k cos (k l x)$

    Args:
        x (torch.Tensor): The input tensor.
        drag_coef (float): The drag coefficient $a$. Default is -0.1.
        k (float): The wave number. Default is 4.0.
        length_scale (float): The length scale $l$. Default is 1.0.
    """


    return drag_coef * ImplicitSource() - ExplicitSource(
        k * torch.cos(k * length_scale * x)
    )

torchfsm.field.wave_1d ¤

wave_1d(
    x: SpatialTensor["B C H ..."],
    min_k: int = 1,
    max_k: int = 5,
    min_amplitude: float = 0.5,
    max_amplitude: float = 1.0,
    n_polynomial: int = 5,
    zero_mean: bool = False,
    mean_shift_coef=0.3,
    batched: bool = False,
) -> SpatialTensor["B C H ..."]

Generate a 1D wave field with multiple harmonics.

Parameters:

Name Type Description Default
x SpatialTensor['B C H ...']

The input tensor.

required
min_k int

The minimum wave number. Default is 1.

1
max_k int

The maximum wave number. Default is 5.

5
min_amplitude float

The minimum amplitude. Default is 0.5.

0.5
max_amplitude float

The maximum amplitude. Default is 1.0.

1.0
n_polynomial int

The number of polynomial terms. Default is 5.

5
zero_mean bool

If True, the mean of the wave will be zero. Default is False.

False
mean_shift_coef float

The coefficient for mean shift. Default is 0.3.

0.3
batched bool

If True, the input tensor is batched. Default is False.

False

Returns:

Type Description
SpatialTensor['B C H ...']

SpatialTensor["B C H ..."]: The generated wave field.

Source code in torchfsm/field/_wave_1d.py
 5
 6
 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
def wave_1d(
    x: SpatialTensor["B C H ..."],
    min_k: int = 1,
    max_k: int = 5,
    min_amplitude: float = 0.5,
    max_amplitude: float = 1.0,
    n_polynomial: int = 5,
    zero_mean: bool = False,
    mean_shift_coef=0.3,
    batched: bool = False,
) -> SpatialTensor["B C H ..."]:
    r"""
    Generate a 1D wave field with multiple harmonics.

    Args:
        x (SpatialTensor["B C H ..."]): The input tensor.
        min_k (int): The minimum wave number. Default is 1.
        max_k (int): The maximum wave number. Default is 5.
        min_amplitude (float): The minimum amplitude. Default is 0.5.
        max_amplitude (float): The maximum amplitude. Default is 1.0.
        n_polynomial (int): The number of polynomial terms. Default is 5.
        zero_mean (bool): If True, the mean of the wave will be zero. Default is False.
        mean_shift_coef (float): The coefficient for mean shift. Default is 0.3.
        batched (bool): If True, the input tensor is batched. Default is False.

    Returns:
        SpatialTensor["B C H ..."]: The generated wave field.
    """
    x_new = x / x.max() * torch.pi * 2
    y = torch.zeros_like(x)
    if not batched:
        x_new = x_new.unsqueeze(0)
        y = y.unsqueeze(0)
    batch = x_new.shape[0]
    shape = [batch, n_polynomial] + [1] * (x_new.dim() - 2)
    k = torch.randint(min_k, max_k + 1, shape, device=x.device, dtype=x.dtype)
    amplitude = (
        torch.rand(*shape, device=x.device, dtype=x.dtype)
        * (max_amplitude - min_amplitude)
        + min_amplitude
    )
    shift = torch.rand(*shape, device=x.device, dtype=x.dtype) * torch.pi * 2
    for i in range(n_polynomial):
        y += amplitude[:, i : i + 1, ...] * torch.sin(
            k[:, i : i + 1, ...] * (x_new + shift[:, i : i + 1, ...])
        )
    if not zero_mean:
        value_shift = torch.rand(
            [batch] + [1] * (x_new.dim() - 1), device=x.device, dtype=x.dtype
        )
        value_shift = (value_shift - 0.5) * 2 * (
            max_amplitude - min_amplitude
        ) * mean_shift_coef + min_amplitude
        y += value_shift
    if not batched:
        y = y.squeeze(0)
    return y

torchfsm.field.random_gaussian_blobs ¤

random_gaussian_blobs(
    mesh: Union[
        Sequence[tuple[float, float, int]],
        MeshGrid,
        FourierMesh,
    ],
    position_range: Tuple[float, float] = (0.4, 0.6),
    variance_range: Tuple[float, float] = (0.005, 0.01),
    batch_size: int = 1,
    n_channels: int = 1,
    device: Optional[device] = None,
) -> SpatialTensor["B C H ..."]

Generate random Gaussian blobs on the specified mesh.

Parameters:

Name Type Description Default
mesh Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]

The mesh on which to generate the Gaussian blob.

required
position_range Tuple[float, float]

The range of positions for the Gaussian blob. Default is (0.4, 0.6).

(0.4, 0.6)
variance_range Tuple[float, float]

The range of variances for the Gaussian blob. Default is (0.005, 0.01).

(0.005, 0.01)
batch_size int

The number of batches. Default is 1.

1
n_channels int

The number of channels. Default is 1.

1
device Optional[device]

The device on which to create the tensor. Default is None.

None

Returns:

Type Description
SpatialTensor['B C H ...']

SpatialTensor["B C H ..."]: The generated Gaussian blob field.

Source code in torchfsm/field/_gaussian_blobs.py
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
def random_gaussian_blobs(
    mesh: Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh],
    position_range: Tuple[float, float] = (0.4, 0.6),
    variance_range: Tuple[float, float] = (0.005, 0.01),
    batch_size: int = 1,
    n_channels: int = 1,
    device: Optional[torch.device] = None
) -> SpatialTensor["B C H ..."]:
    r"""
    Generate random Gaussian blobs on the specified mesh.

    Args:
        mesh (Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]):
            The mesh on which to generate the Gaussian blob.
        position_range (Tuple[float, float]): The range of positions for the Gaussian blob.
            Default is (0.4, 0.6).
        variance_range (Tuple[float, float]): The range of variances for the Gaussian blob.
            Default is (0.005, 0.01).
        batch_size (int): The number of batches. Default is 1.
        n_channels (int): The number of channels. Default is 1.
        device (Optional[torch.device]): The device on which to create the tensor. Default is None. 

    Returns:
        SpatialTensor["B C H ..."]: The generated Gaussian blob field.
    """
    if not isinstance(mesh, MeshGrid):
        mesh = MeshGrid(mesh.mesh_info if isinstance(mesh, FourierMesh) else mesh,device=device)
    if device is not None:
        if mesh.device != device:
            raise ValueError(f"Mesh device {mesh.device} does not match the specified device {device}.")
    mesh_grid= mesh.bc_mesh_grid()
    mesh_grid = [mesh_grid] if isinstance(mesh_grid, Tensor) else mesh_grid
    locations=torch.cat(mesh_grid,dim=1).squeeze(0)
    n_dim = locations.ndim-1 
    position = torch.empty(n_dim).uniform_(position_range[0],position_range[1]).to(mesh.device)
    variance = torch.empty(n_dim).uniform_(variance_range[0], variance_range[1]).to(mesh.device)
    locations = torch.stack([locations]*batch_size*n_channels, dim=0)
    position = position.unsqueeze(0).repeat(batch_size*n_channels, 1)
    variance = variance.unsqueeze(0).repeat(batch_size*n_channels, 1)
    blob=torch.exp(-0.5*_batched_mahalanobis_distance(locations, position, variance))
    return blob.view(batch_size, n_channels, *blob.shape[1:])

torchfsm.field.truncated_fourier_series ¤

truncated_fourier_series(
    mesh: Union[
        Sequence[tuple[float, float, int]],
        MeshGrid,
        FourierMesh,
    ],
    freq_threshold: int = 5,
    amplitude_range: tuple[int, int] = (-1.0, 1.0),
    angle_range: tuple[int, int] = (0.0, 2.0 * torch.pi),
    device: Optional[device] = None,
    dtype: Optional[dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode: Optional[
        Literal["normal_distribution", "-1_1", "0_1"]
    ] = None,
    normalized_freq: bool = True,
) -> SpatialTensor["B C H ..."]

Generate a truncated Fourier series noise field on a given mesh.

Parameters:

Name Type Description Default
mesh Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]

The mesh on which to generate the noise.

required
freq_threshold int

The frequency threshold for truncation.

5
amplitude_range tuple[int, int]

The range of amplitudes for the noise.

(-1.0, 1.0)
angle_range tuple[int, int]

The range of angles for the noise.

(0.0, 2.0 * pi)
device Optional[device]

The device on which to create the tensor.

None
dtype Optional[dtype]

The data type of the tensor.

None
batch_size int

The number of batches.

1
n_channel int

The number of channels.

1
normalize_mode Optional[Literal['normal_distribution', '-1_1', '0_1']]

The normalization mode for the generated noise. If None, no normalization is applied. Default is None.

None
normalized_freq bool

If True, wheather to set the frequency threshold as a normalized value. If the domain length is 1, setting this to True or False will not make a difference.

True

Returns:

Type Description
SpatialTensor['B C H ...']

SpatialTensor["B C H ..."]: The generated noise field.

Source code in torchfsm/field/_truncated_fourier.py
 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
def truncated_fourier_series(
    mesh: Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh],
    freq_threshold: int = 5,
    amplitude_range: tuple[int, int] = (-1.0, 1.0),
    angle_range: tuple[int, int] = (0.0, 2.0 * torch.pi),
    device: Optional[torch.device] = None,
    dtype: Optional[torch.dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode:Optional[Literal["normal_distribution","-1_1","0_1"]]=None,
    normalized_freq: bool = True
) -> SpatialTensor["B C H ..."]:
    r"""
    Generate a truncated Fourier series noise field on a given mesh.

    Args:
        mesh (Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]): The mesh on which to generate the noise.
        freq_threshold (int): The frequency threshold for truncation.
        amplitude_range (tuple[int, int]): The range of amplitudes for the noise.
        angle_range (tuple[int, int]): The range of angles for the noise.
        device (Optional[torch.device]): The device on which to create the tensor.
        dtype (Optional[torch.dtype]): The data type of the tensor.
        batch_size (int): The number of batches.
        n_channel (int): The number of channels.
        normalize_mode (Optional[Literal["normal_distribution","-1_1","0_1"]]): The normalization mode for the generated noise.
            If None, no normalization is applied. Default is None.
        normalized_freq (bool): If True, wheather to set the frequency threshold as a normalized value.
            If the domain length is 1, setting this to True or False will not make a difference. 

    Returns:
        SpatialTensor["B C H ..."]: The generated noise field.
    """
    mesh, device, dtype = _get_mesh_device_and_dtype(mesh, device, dtype)

    if normalized_freq:
        filter = mesh.normalized_low_pass_filter(freq_threshold)
        #mesh.normalized_low_pass_filter.cache_clear()
    else:
        filter =  mesh.abs_low_pass_filter(freq_threshold)
        #mesh.abs_low_pass_filter.cache_clear()

    return truncated_fourier_series_customed_filter(
        mesh=mesh,
        low_pass_filter=filter,
        amplitude_range=amplitude_range,
        angle_range=angle_range,
        device=device,
        dtype=dtype,
        batch_size=batch_size,  
        n_channel=n_channel,
        normalize_mode=normalize_mode,
    )

torchfsm.field.random_truncated_fourier_series ¤

random_truncated_fourier_series(
    mesh: Union[
        Sequence[tuple[float, float, int]],
        MeshGrid,
        FourierMesh,
    ],
    min_freq: int = 2,
    max_freq: int = 5,
    amplitude_range: tuple[int, int] = (-1.0, 1.0),
    angle_range: tuple[int, int] = (0.0, 2.0 * torch.pi),
    device: Optional[device] = None,
    dtype: Optional[dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode: Optional[
        Literal["normal_distribution", "-1_1", "0_1"]
    ] = None,
    normalized_freq: bool = True,
) -> SpatialTensor["B C H ..."]

Generate a truncated Fourier series noise field on a given mesh.

Parameters:

Name Type Description Default
mesh Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]

The mesh on which to generate the noise.

required
freq_threshold int

The frequency threshold for truncation.

required
amplitude_range tuple[int, int]

The range of amplitudes for the noise.

(-1.0, 1.0)
angle_range tuple[int, int]

The range of angles for the noise.

(0.0, 2.0 * pi)
device Optional[device]

The device on which to create the tensor.

None
dtype Optional[dtype]

The data type of the tensor.

None
batch_size int

The number of batches.

1
n_channel int

The number of channels.

1
normalize_mode Optional[Literal['normal_distribution', '-1_1', '0_1']]

The normalization mode for the generated noise. If None, no normalization is applied. Default is None.

None
normalized_freq bool

If True, wheather to set the frequency threshold as a normalized value. If the domain length is 1, setting this to True or False will not make a difference.

True

Returns:

Type Description
SpatialTensor['B C H ...']

SpatialTensor["B C H ..."]: The generated noise field.

Source code in torchfsm/field/_truncated_fourier.py
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
def random_truncated_fourier_series(
    mesh: Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh],
    min_freq: int = 2,
    max_freq: int = 5,
    amplitude_range: tuple[int, int] = (-1.0, 1.0),
    angle_range: tuple[int, int] = (0.0, 2.0 * torch.pi),
    device: Optional[torch.device] = None,
    dtype: Optional[torch.dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode:Optional[Literal["normal_distribution","-1_1","0_1"]]=None,
    normalized_freq: bool = True
) -> SpatialTensor["B C H ..."]:
    r"""
    Generate a truncated Fourier series noise field on a given mesh.

    Args:
        mesh (Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]): The mesh on which to generate the noise.
        freq_threshold (int): The frequency threshold for truncation.
        amplitude_range (tuple[int, int]): The range of amplitudes for the noise.
        angle_range (tuple[int, int]): The range of angles for the noise.
        device (Optional[torch.device]): The device on which to create the tensor.
        dtype (Optional[torch.dtype]): The data type of the tensor.
        batch_size (int): The number of batches.
        n_channel (int): The number of channels.
        normalize_mode (Optional[Literal["normal_distribution","-1_1","0_1"]]): The normalization mode for the generated noise.
            If None, no normalization is applied. Default is None.
        normalized_freq (bool): If True, wheather to set the frequency threshold as a normalized value.
            If the domain length is 1, setting this to True or False will not make a difference. 

    Returns:
        SpatialTensor["B C H ..."]: The generated noise field.
    """
    mesh, device, dtype = _get_mesh_device_and_dtype(mesh, device, dtype)

    if normalized_freq:
        filter = torch.cat([
            mesh.normalized_low_pass_filter(random.randint(min_freq, max_freq + 1))
            for _ in range(batch_size)
        ], dim=0)
        #mesh.normalized_low_pass_filter.cache_clear()
    else:
        filter = torch.cat([
            mesh.abs_low_pass_filter(random.randint(min_freq, max_freq + 1))
            for _ in range(batch_size)
        ], dim=0)
        #mesh.abs_low_pass_filter.cache_clear()

    return truncated_fourier_series_customed_filter(
        mesh=mesh,
        low_pass_filter=filter,
        amplitude_range=amplitude_range,
        angle_range=angle_range,
        device=device,
        dtype=dtype,
        batch_size=batch_size,  
        n_channel=n_channel,
        normalize_mode=normalize_mode,
    )

torchfsm.field.truncated_fourier_series_customed_filter ¤

truncated_fourier_series_customed_filter(
    mesh: Union[
        Sequence[tuple[float, float, int]],
        MeshGrid,
        FourierMesh,
    ],
    low_pass_filter: SpatialTensor["B C H ..."],
    amplitude_range: tuple[int, int] = (-1.0, 1.0),
    angle_range: tuple[int, int] = (0.0, 2.0 * torch.pi),
    device: Optional[device] = None,
    dtype: Optional[dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode: Optional[
        Literal["normal_distribution", "-1_1", "0_1"]
    ] = None,
) -> SpatialTensor["B C H ..."]

Generate a truncated Fourier series noise field on a given mesh.

Parameters:

Name Type Description Default
mesh Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]

The mesh on which to generate the noise.

required
low_pass_filter SpatialTensor['B C H ...']

The custom low-pass filter to apply to the Fourier noise.

required
amplitude_range tuple[int, int]

The range of amplitudes for the noise.

(-1.0, 1.0)
angle_range tuple[int, int]

The range of angles for the noise.

(0.0, 2.0 * pi)
device Optional[device]

The device on which to create the tensor.

None
dtype Optional[dtype]

The data type of the tensor.

None
batch_size int

The number of batches.

1
n_channel int

The number of channels.

1
normalize_mode Optional[Literal['normal_distribution', '-1_1', '0_1']]

The normalization mode for the generated noise. If None, no normalization is applied. Default is None.

None

Returns:

Type Description
SpatialTensor['B C H ...']

SpatialTensor["B C H ..."]: The generated noise field.

Source code in torchfsm/field/_truncated_fourier.py
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
def truncated_fourier_series_customed_filter(
    mesh: Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh],
    low_pass_filter: SpatialTensor["B C H ..."],
    amplitude_range: tuple[int, int] = (-1.0, 1.0),
    angle_range: tuple[int, int] = (0.0, 2.0 * torch.pi),
    device: Optional[torch.device] = None,
    dtype: Optional[torch.dtype] = None,
    batch_size: int = 1,
    n_channel: int = 1,
    normalize_mode:Optional[Literal["normal_distribution","-1_1","0_1"]]=None,

) -> SpatialTensor["B C H ..."]:
    r"""
    Generate a truncated Fourier series noise field on a given mesh.

    Args:
        mesh (Union[Sequence[tuple[float, float, int]], MeshGrid, FourierMesh]): The mesh on which to generate the noise.
        low_pass_filter (SpatialTensor["B C H ..."]): The custom low-pass filter to apply to the Fourier noise.
        amplitude_range (tuple[int, int]): The range of amplitudes for the noise.
        angle_range (tuple[int, int]): The range of angles for the noise.
        device (Optional[torch.device]): The device on which to create the tensor.
        dtype (Optional[torch.dtype]): The data type of the tensor.
        batch_size (int): The number of batches.
        n_channel (int): The number of channels.
        normalize_mode (Optional[Literal["normal_distribution","-1_1","0_1"]]): The normalization mode for the generated noise.
            If None, no normalization is applied. Default is None.

    Returns:
        SpatialTensor["B C H ..."]: The generated noise field.
    """
    mesh, device, dtype = _get_mesh_device_and_dtype(mesh, device, dtype)

    magnitude=torch.rand(
        *mesh_shape(mesh, batch_size=batch_size, n_channel=n_channel),
        device=device,
        dtype=dtype
    )* (amplitude_range[1] - amplitude_range[0]) + amplitude_range[0]   
    angle=torch.rand(
        *mesh_shape(mesh, batch_size=batch_size, n_channel=n_channel),
        device=device,
        dtype=dtype
    )* (angle_range[1] - angle_range[0]) + angle_range[0]
    fourier_noise = magnitude * torch.exp(1j * angle)
    fourier_noise = fourier_noise * low_pass_filter
    fourier_noise = mesh.ifft(fourier_noise).real
    if normalize_mode is not None:
        fourier_noise = normalize(fourier_noise, normalize_mode=normalize_mode)
    return fourier_noise