Skip to content

Commit 4d5bf9f

Browse files
committed
first attempt
1 parent 4f2cc25 commit 4d5bf9f

File tree

3 files changed

+1385
-1131
lines changed

3 files changed

+1385
-1131
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
from time import time
2+
import numpy as np
3+
from typing import Tuple, List, Union
4+
from scipy.fft import fftn, ifftn, fftfreq
5+
from MSUtils.general.MicrostructureImage import MicrostructureImage
6+
from MSUtils.general.h52xdmf import write_xdmf
7+
8+
9+
def generate_spinodal_microstructure(
10+
shape: Union[Tuple[int, int, int], List[int]],
11+
volume_fraction: float = 0.5,
12+
k0: Union[Tuple[float, float, float], List[float]] = (6.0, 6.0, 6.0),
13+
bandwidth: Union[Tuple[float, float, float], List[float]] = (0.6, 0.6, 0.6),
14+
exponent: float = 2.0,
15+
seed: int = 42,
16+
L: Union[Tuple[float, float, float], List[float]] = (1.0, 1.0, 1.0),
17+
anisotropy_axes: Union[Tuple[np.ndarray, np.ndarray], List[np.ndarray]] = (
18+
np.array([1.0, 0.0, 0.0]),
19+
np.array([0.0, 1.0, 0.0]),
20+
),
21+
):
22+
"""Generate a binary spinodal microstructure via spectral filtering.
23+
24+
Parameters
25+
----------
26+
shape : tuple or list
27+
Grid size (nz, ny, nx).
28+
k0 : array-like, default [6.0, 6.0, 6.0]
29+
Center wavenumber in cycles per domain (not angular) for each axis [k0_v3, k0_v2, k0_v1].
30+
bandwidth : array-like, default [0.6, 0.6, 0.6]
31+
Relative Gaussian width (fraction of k0) for each axis [bw_v3, bw_v2, bw_v1].
32+
Use same values for isotropic bandwidth, different values for anisotropic.
33+
exponent : float
34+
High-k rolloff exponent (larger -> stronger suppression)
35+
seed : int, optional
36+
RNG seed for reproducibility
37+
L : array-like, default [1.0, 1.0, 1.0]
38+
Physical dimensions [Lz, Ly, Lx].
39+
anisotropy_axes : 2-tuple or list of vectors, default [(1,0,0), (0,1,0)]
40+
Define a preferred coordinate system (v1, v2) for oblique orientations.
41+
v1, v2 should be orthogonal unit vectors defining preferred directions.
42+
"""
43+
np.random.seed(seed)
44+
ndim = len(shape)
45+
L = np.array(L, dtype=np.float32)
46+
dx_arr = L / np.array(shape, dtype=np.float32)
47+
k0_arr = np.array(k0, dtype=np.float32)
48+
bw_arr = np.array(bandwidth, dtype=np.float32)
49+
50+
np.random.seed(seed)
51+
noise = np.random.randn(*shape).astype(
52+
np.float32, copy=False
53+
) # Generate noise (resolution dependent)
54+
noise_hat = fftn(noise, workers=-1, overwrite_x=True)
55+
56+
# Build k-space grids
57+
grids = [
58+
fftfreq(shape[i], d=dx_arr[i]).astype(np.float32) * (2.0 * np.pi)
59+
for i in range(ndim)
60+
]
61+
grids = np.meshgrid(*grids, indexing="ij")
62+
63+
# Build rotation matrix: R = [v1, v2, v3]^T
64+
v1, v2 = anisotropy_axes
65+
v1 = np.array(v1, dtype=np.float32)
66+
v2 = np.array(v2, dtype=np.float32)
67+
v3 = np.cross(v1, v2)
68+
R = np.array([v1, v2, v3], dtype=np.float32)
69+
70+
# Rotate k-space coordinates: k_rotated = R @ k_original
71+
grids = list(np.einsum("ij,jklm->iklm", R, np.array(grids)))
72+
73+
# Compute effective radial wavenumber using ellipsoidal normalization
74+
k2_normalized = sum(
75+
(grids[i] / ((np.float32(2.0 * np.pi) * k0_arr[i]) / L[i])) ** 2
76+
for i in range(ndim)
77+
)
78+
k = np.sqrt(k2_normalized)
79+
k0_ref = np.float32(1.0) # Reference wavenumber is 1 after normalization
80+
81+
del grids
82+
83+
# Bandpass filter
84+
# Use mean bandwidth for the Gaussian envelope
85+
sigma = np.float32(np.mean(bw_arr) * k0_ref)
86+
k_diff = k - k0_ref
87+
filter_amp = np.exp(-0.5 * (k_diff / sigma) ** 2)
88+
89+
# High-k rolloff (in-place multiplication)
90+
k_ratio = k / k0_ref
91+
filter_amp *= 1.0 / (1.0 + k_ratio**exponent)
92+
93+
del k, k_diff, k_ratio
94+
95+
# Apply filter in-place
96+
noise_hat *= filter_amp
97+
del filter_amp
98+
99+
# Inverse FFT
100+
field = np.real(ifftn(noise_hat, workers=-1, overwrite_x=True))
101+
102+
field = (field - field.mean()) / field.std()
103+
104+
# Threshold
105+
thr = np.quantile(field, 1.0 - volume_fraction)
106+
image = (field >= thr).astype(np.uint8)
107+
return image
108+
109+
110+
if __name__ == "__main__":
111+
112+
N = [256, 256, 256]
113+
L = [1.0, 1.0, 1.0]
114+
k0 = [5.0, 20.0, 20.0]
115+
bandwidth = [0.1, 0.1, 0.1]
116+
exponent = 10.0
117+
seed = 42
118+
volume_fraction = 0.5
119+
120+
v1 = np.array([1.0, 1.0, 1.0])
121+
v1 /= np.linalg.norm(v1)
122+
v2 = np.array([1.0, -1.0, 0.0])
123+
v2 /= np.linalg.norm(v2)
124+
anisotropy_axes = (v1, v2)
125+
126+
start_time = time()
127+
img = generate_spinodal_microstructure(
128+
N,
129+
volume_fraction=volume_fraction,
130+
k0=k0,
131+
bandwidth=bandwidth,
132+
exponent=exponent,
133+
seed=seed,
134+
L=L,
135+
)
136+
end_time = time()
137+
print(
138+
f"3D spinodal microstructure generation took {end_time - start_time:.4f} seconds"
139+
)
140+
141+
MS = MicrostructureImage(image=img, L=L)
142+
MS.write(
143+
h5_filename="data/spinodal_spectral.h5",
144+
dset_name="/spinodal_spectral",
145+
order="zyx",
146+
)
147+
write_xdmf(
148+
h5_filepath="data/spinodal_spectral.h5",
149+
xdmf_filepath="data/spinodal_spectral.xdmf",
150+
microstructure_length=L[::-1],
151+
)

0 commit comments

Comments
 (0)