140 lines
4.9 KiB
Python
140 lines
4.9 KiB
Python
import numpy as np
|
||
|
||
def solve_poisson_fft_tilde(f_tilde, Qx, Qy):
|
||
"""
|
||
Solve the Poisson equation in Fourier space directly.
|
||
"""
|
||
k2 = Qx**2 + Qy**2
|
||
k2[0, 0] = 1 # avoid division by zero
|
||
u_tilde = -f_tilde / k2 # Attention, here is for solving the Poisson equation with \Delta u + f = 0, so here the f_tilde could be right hand side of the Poisson equation
|
||
u_tilde[0, 0] = 0 # set the zero-frequency component to zero
|
||
return u_tilde
|
||
|
||
def fft_gradient(u_tilde, Qx, Qy):
|
||
"""
|
||
For a given u_tilde in Fourier space, compute its gradient in real space.
|
||
"""
|
||
ux_tilde = 1j * Qx * u_tilde
|
||
uy_tilde = 1j * Qy * u_tilde
|
||
ux = np.fft.ifft2(ux_tilde).real
|
||
uy = np.fft.ifft2(uy_tilde).real
|
||
return ux, uy
|
||
|
||
def divergence_to_tilde(fx, fy, Qx, Qy):
|
||
"""
|
||
Compute the divergence of a vector field (fx, fy) in real space, then return its Fourier transform.
|
||
"""
|
||
fx_tilde = np.fft.fft2(fx)
|
||
fy_tilde = np.fft.fft2(fy)
|
||
div_tilde = 1j * Qx * fx_tilde + 1j * Qy * fy_tilde
|
||
return div_tilde
|
||
|
||
def solve_microfluctuation_poisson(G, G_0, f, Qx, Qy, epsilon=1e-8, max_iter=5000):
|
||
"""
|
||
To solve the equation: ∇·(G∇N) + f = 0
|
||
With fixed point iteration: G_0 ∇²N + ∇·((G-G_0)∇N) + f = 0
|
||
Solve N^(0) with ∇²N = -f/G_0 as initial guess
|
||
"""
|
||
f_tilde = np.fft.fft2(f)
|
||
|
||
N_init_tilde = solve_poisson_fft_tilde(-f_tilde / G_0, Qx, Qy)
|
||
N_tilde = N_init_tilde.copy()
|
||
|
||
for _ in range(max_iter): # The loop simply repeats the loop body max_iter times, without requiring a count value in each loop iteration.
|
||
# Compute the gradient of N in real space
|
||
Nx, Ny = fft_gradient(N_tilde, Qx, Qy)
|
||
# Compute (G-G_0)*grad(u) in real space, then compute its divergence in Fourier space
|
||
temp_x = (G - G_0)*Nx
|
||
temp_y = (G - G_0)*Ny
|
||
f_n_tilde = divergence_to_tilde(temp_x, temp_y, Qx, Qy)
|
||
|
||
# Slove the Poisson equation in Fourier space
|
||
# G_0 ΔN = -f - f_n
|
||
# ΔN = -(f_n + f)/G_0
|
||
N_next_tilde = solve_poisson_fft_tilde(-(f_n_tilde + f_tilde)/G_0, Qx, Qy)
|
||
# Check the convergence
|
||
if np.linalg.norm(np.fft.ifft2(N_next_tilde - N_tilde)) < epsilon:
|
||
N_tilde = N_next_tilde
|
||
break
|
||
N_tilde = N_next_tilde
|
||
|
||
N = np.fft.ifft2(N_tilde).real
|
||
return N
|
||
|
||
|
||
def homogeneous_effective_modulus(G, G_0, Qx, Qy, n, m):
|
||
# For N1_1:
|
||
# f1 = -∇·(G(y)*e_1) = -∇·(G(y), 0)
|
||
# ∇·(G(y),0) = dG/dx
|
||
dG_dx = np.fft.ifft2(1j * Qx * np.fft.fft2(G)).real
|
||
f1 = dG_dx
|
||
|
||
N1_1 = solve_microfluctuation_poisson(G, G_0, f1, Qx, Qy)
|
||
|
||
# For N1_2:
|
||
# f2 = -∇·(G(y)*e_2) = -∇·(0, G(y))
|
||
# ∇·(0,G(y)) = dG/dy
|
||
dG_dy = np.fft.ifft2(1j * Qy * np.fft.fft2(G)).real
|
||
f2 = dG_dy
|
||
|
||
N1_2 = solve_microfluctuation_poisson(G, G_0, f2, Qx, Qy)
|
||
|
||
# C_0 = ∫ G(y) [∇N1(y) + I] dy
|
||
# N1 = [N1_1, N1_2]
|
||
|
||
N1_1_tilde = np.fft.fft2(N1_1)
|
||
N1_2_tilde = np.fft.fft2(N1_2)
|
||
|
||
N1_1x, N1_1y = fft_gradient(N1_1_tilde, Qx, Qy)
|
||
N1_2x, N1_2y = fft_gradient(N1_2_tilde, Qx, Qy)
|
||
|
||
# ∇N1 is [[N1_1x, N1_1y],
|
||
# [N1_2x, N1_2y]]
|
||
# ∇N1 + I is [[N1_1x+1, N1_1y ],
|
||
# [N1_2x , N1_2y+1]]
|
||
N1_grad_plus_I = np.zeros((n,m,2,2))
|
||
N1_grad_plus_I[:,:,0,0] = N1_1x + 1.0
|
||
N1_grad_plus_I[:,:,0,1] = N1_1y
|
||
N1_grad_plus_I[:,:,1,0] = N1_2x
|
||
N1_grad_plus_I[:,:,1,1] = N1_2y + 1.0
|
||
|
||
# Compute C_0 = ∫ G(y)*[∇N1+I] dy
|
||
C0 = np.zeros((2,2))
|
||
for i in range(2):
|
||
for j in range(2):
|
||
# Integral of unit cell, the area of the unit cell is 1
|
||
C0[i,j] = np.mean(G * N1_grad_plus_I[:,:,i,j])
|
||
|
||
return C0, N1_1, N1_2, N1_grad_plus_I
|
||
|
||
# Define gradient function to compute ∇v0
|
||
def fft_gradient_2d(u_tilde, Qx, Qy):
|
||
ux_tilde = (1j * Qx) * u_tilde
|
||
uy_tilde = (1j * Qy) * u_tilde
|
||
ux = np.fft.ifft2(ux_tilde).real
|
||
uy = np.fft.ifft2(uy_tilde).real
|
||
return ux, uy
|
||
|
||
|
||
def first_order_homogenization(F_macro, Qx_macro, Qy_macro, C0, N1_1, N1_2, eta):
|
||
F_macro_tilde = np.fft.fft2(F_macro)
|
||
denominator = (C0[0,0]*Qx_macro**2 + (C0[0,1]+C0[1,0])*Qx_macro*Qy_macro + C0[1,1]*Qy_macro**2) #Deducing process is in the appendix
|
||
|
||
denominator[0,0] = 1.0 # avoid division by zero
|
||
v0_tilde = F_macro_tilde / denominator # v0_tilde = -F_tilde / denominator !!! there should not be a minus sign here
|
||
v0_tilde[0,0] = 0.0
|
||
v0 = np.fft.ifft2(v0_tilde).real
|
||
|
||
v0_ux, v0_uy = fft_gradient_2d(np.fft.fft2(v0), Qx_macro, Qy_macro)
|
||
# u(x) ≈ v_0(x) + η N_1(x/η) · ∇v_0(x)
|
||
|
||
n = int(1/eta)
|
||
N1_1_global = N1_1[::n,::n]
|
||
N1_2_global = N1_2[::n,::n]
|
||
# tile
|
||
N1_1_global = np.tile(N1_1_global, (n,n))
|
||
N1_2_global = np.tile(N1_2_global, (n,n))
|
||
|
||
|
||
u = v0 + eta * (N1_1_global * v0_ux + N1_2_global * v0_uy)
|
||
return u, v0, v0_ux, v0_uy |