from matplotlib import pyplot as plt
from matplotlib.animation import FuncAnimation
Tension modulated stiff membrane
The tension is given by \[ T(u) = T_0 + T_{NL}(u) \]
We consider, \[ T_{N L}(u)=C_{N L} \frac{S(u)-S_0}{S_0} \simeq \frac{1}{2} \frac{C_{N L}}{S_0} \int_{\mathcal{S}}\|\nabla u\|^2 d \mathbf{x} \] For the rectangular plate, the Berger approximation is used for the tension \[ \begin{aligned} T_{N L}(u) \simeq & \frac{Q h}{2 L_x L_y\left(1-\nu^2\right)} \\ & \cdot \int_0^{L_x} \int_0^{L_y}\left[\left(\frac{\partial u}{\partial x}\right)^2+\left(\frac{\partial u}{\partial y}\right)^2\right] d x d y . \end{aligned} \] And therefore \[ C_{N L}=\frac{Q h}{\left(1-\nu^2\right)} \]
\[ \boxed{ \begin{aligned} \rho h \ddot{\bar{u}}_{n, m}(t) + \left(d_3 \lambda_{n, m}+d_1\right) \dot{\bar{u}}_{n, m}( t) + \left(\lambda_{n, m} \left(\lambda_{n, m}D +T_0\right)\right) \bar{u}_{n, m}(t)-\bar{f}^{(tm)}_{n, m}(u, \bar{u}) = 0 \end{aligned} } \]
Where \[ \begin{aligned} f^{(tm)}_{n, m}(u, \bar{u}) & = -\lambda_{n, m} T_{N L} (u) \bar{u}_{n, m}(t) \\ & = -\lambda_{n, m} \frac{1}{2}\frac{C_{N L}}{S_0} \left[\sum_{\tilde{n}, \tilde{m}} \frac{\lambda_{\tilde{n}, \tilde{m}}\bar{u}_{\tilde{n}, \tilde{m}}^2 (t)}{\lVert K_{\tilde{n}, \tilde{m}} \rVert_{2}^{2}} \right] \bar{u}_{n, m}(t) \end{aligned} \]
For a rectangular plate, the norm of the modes is given by \[\lVert K_{n, m} \rVert_{2}^{2} = \langle K_{n, m} , K_{n, m} \rangle = \int _0^{L_x}\int _0^{L_y}\sin ^2\left(\frac{n \pi x}{L_x}\right) \sin ^2\left(\frac{m \pi y}{L_y}\right)dydx = \frac{L_x L_y}{4} \quad \forall n, m \in \mathbb{Z} \]
Write as a system of first order ODEs:
\[ \begin{aligned} \dot{\bar{u}}(\mu, t) &= \bar{v}(\mu, t) \\ \dot{\bar{v}}(\mu, t) &= \frac{-\left(d_3 \lambda_\mu+d_1\right)}{\rho h} \bar{v}(\mu, t) - \frac{\beta_\mu}{\rho h} \bar{u}(\mu, t) + \frac{f^{(tm)}_{\mu}(u, \bar{u})}{\rho h} \end{aligned} \]
Where \[ \begin{aligned} \beta_\mu &= \lambda_\mu \left(\lambda_\mu D + T_0\right) = \lambda_\mu^2 D + \lambda_\mu T_0 \\ \bar{b}(\mu, u, \bar{u}) &= \frac{f^{(tm)}_{\mu}(u, \bar{u})}{\rho h} \\ &= -\frac{1}{\rho h} \lambda_{\mu} \frac{1}{2}\frac{Q h}{\left(1-\nu^2\right)}\frac{1}{L_x L_y} \frac{4}{L_x L_y}\left[\sum_{\eta} \lambda_{\eta}\bar{u}_{\eta}^2 (t) \right] \bar{u}_{\mu}(t) \\ &= -\lambda_{\mu} \frac{Q}{\rho \left(1-\nu^2\right)}\frac{2}{L_x^2 L_y^2}\left[\sum_{\eta} \lambda_{\eta}\bar{u}_{\eta}^2 (t) \right] \bar{u}_{\mu}(t) \\ \end{aligned} \]
${u}{n, m}(t) = {u}{}(t) $ is a matrix, but to be able to feed it to solve_ivp, we will flatten it to a vector, and index it as $= n N_y + m $ where \(N_y\) is the number of modes in the y direction.
This is a system of first-order ODEs. In matrix form, we can write this as:
\[ \boxed{ \begin{aligned} \mathbf{\dot{\bar{u}}} &= \mathbf{\bar{v}} \\ \mathbf{\dot{\bar{v}}} &= -\mathbf{M_v} \mathbf{\bar{v}} - \mathbf{M_u} \mathbf{\bar{u}} + \mathbf{\bar{b}} \end{aligned} } \]
The Matrices \(\mathbf{M_v}\), \(\mathbf{M_y}\) are diagonal matrices whose order is equal to the number of modes \(M\).
\[ \begin{aligned} \mathbf{\Lambda} &= \text{diag}\left(\lambda_\mu\right) \\ \mathbf{M_v} &= \text{diag}\left(\frac{d_3 \lambda_\mu + d_1}{\rho h}\right) \\ \mathbf{M_y} &= \text{diag}\left(\frac{\beta_\mu}{\rho h}\right) \\ \mathbf{\bar{b}}(\mathbf{u}, \mathbf{\bar{u}}) &= - \frac{Q}{\rho \left(1-\nu^2\right)}\frac{2}{L_x^2 L_y^2}\mathbf{\Lambda} \left[ \mathbf{\bar{u}}^{T} \mathbf{\Lambda} \mathbf{\bar{u}} \right] \mathbf{\bar{u}} \\ \mathbf{\bar{b}}(\mathbf{u}, \mathbf{\bar{u}}) &= - \mathbf{C_b}\mathbf{\Lambda} \left[ \mathbf{\bar{u}}^{T} \mathbf{\Lambda} \mathbf{\bar{u}} \right] \mathbf{\bar{u}} \\ \end{aligned} \]
Wave2dSolverTensionModulated
Wave2dSolverTensionModulated (sampling_rate:int=16000, final_time:float=0.5, n_gridpoints_x:int=41, length_x:float=0.4, aspect_ratio:float=0.8, rho:float=1380, h:float=0.00019, E:int=3500000000.0, nu:float=0.3, d1:float=8e-05, d3:float=1.4e-05, Ts0:float=2620, n_max_modes:int=36, use_nonlinear:bool=True)
Tension modulated wave equation solver for a rectangular stiff membrane. The parameters were taken from (Fletcher, 1991, p.86) and adapted to a rectangular case.
Type | Default | Details | |
---|---|---|---|
sampling_rate | int | 16000 | 1/s Temporal sampling frequency |
final_time | float | 0.5 | s Duration of the simulation |
n_gridpoints_x | int | 41 | pts/m Spatial sampling grid |
length_x | float | 0.4 | m Length of x dimension |
aspect_ratio | float | 0.8 | Aspect ratio of the membrane, Ly/Lx |
rho | float | 1380 | kg/m**3 Density |
h | float | 0.00019 | m Thickness |
E | int | 3500000000.0 | Pa Young’s modulus |
nu | float | 0.3 | Poisson’s ratio |
d1 | float | 8e-05 | kg/(ms) Frequency independent loss |
d3 | float | 1.4e-05 | kg m/s Frequency dependent loss |
Ts0 | float | 2620 | N/m Tension per unit length |
n_max_modes | int | 36 | Number of modal coordinates |
use_nonlinear | bool | True | Use nonlinear wave equation |
# Initialize the solver
= Wave2dSolverTensionModulated(sampling_rate=16000, final_time=0.005, n_max_modes=625,n_gridpoints_x=161, use_nonlinear=True)
solver # Plot some of the modes
= 3
nx = 2
ny = plt.subplots(nx, ny, figsize=(5, 15))
fig, ax = solver.dx/solver.dy
aspect_ratio_dxdy # Print this aspect ratio and the one used in the solver
solver.print_solver_info()print(f"Aspect ratio: {aspect_ratio_dxdy}")
print(f"Solver aspect ratio: {solver.aspect_ratio}")
for i in range(nx):
for j in range(ny):
print(f"Mode max value: {np.max(solver.modes[i, j, :, :])}")
= aspect_ratio_dxdy, origin='lower')
ax[i, j].imshow(solver.modes[i, j, :, :], aspect f"Mode nx={i+1}, ny={j+1}")
ax[i, j].set_title("y")
ax[i, j].set_xlabel("x")
ax[i, j].set_ylabel(# ax[i, j].tight_layout()
dx: 0.0025 in meters
dy: 0.0025000000000000005 in meters
dt: 6.25e-05 in seconds
number of points in the x direction (n_gridpoints_x): 161
number of points in the y direction (n_gridpoints_y): 129
time in samples (nt): (80,)
number of modes in x direction (n_max_modes_x): 25
number of modes in y direction (n_max_modes_y): 25
number of modes (n_max_modes): 625
length in x direction (length_x): 0.4 in meters
length in y direction (length_y): 0.32000000000000006 in meters
grid_x shape: (161, 129)
grid_y shape: (161, 129)
wavenumbers_x shape: (25,)
wavenumbers_y shape: (25,)
modes shape: (25, 25, 161, 129)
lambdas shape: (625,)
Aspect ratio: 0.9999999999999998
Solver aspect ratio: 0.8
Mode max value: 1.0
Mode max value: 1.0
Mode max value: 1.0
Mode max value: 1.0
Mode max value: 0.9998072404820648
Mode max value: 1.0
# Create a simple initial condition, one the modes + additive noise
= 3
mode_n = 3
mode_m = mode_n - 1
indn = mode_m - 1
indm
= 0.0*solver.modes[indn, indm, :, :] + 0.001 * np.random.randn(solver.n_gridpoints_x, solver.n_gridpoints_y)
u0 = np.zeros_like(u0) v0
# This was code for checking the projection to modal coordinates depending on the integration method
= solver.modes[indn, indm, :, :]
u0 = np.zeros_like(u0)
v0
# Project the initial condition to modal coordinates
= solver.to_modal(u0,v0)
bar_u_simpson, bar_v_simpson # Print the modal coordinates
print("Difference")
# Print the difference between the two methods
# print(bar_z - bar_z2)
# f = lambda y, x: np.sin(solver.wavenumbers_x[indn] * x) * np.sin(solver.wavenumbers_y[indm] * y)*np.sin(solver.wavenumbers_x[indn] * x) * np.sin(solver.wavenumbers_y[indm] * y)
# for i, x in enumerate(solver.x):
# for j, y in enumerate(solver.y):
# f_val = f(y, x)
# difference = f_val-solver.modes[indn, indm, i, j]
# if difference > 1e-15:
# print(f"Error at ({x}, {y})")
# print(f"Exact: {f_val}")
# print(f"Approx: {solver.modes[indn, indm, i, j]}")
# print(f"Difference: {difference}")
# print(solver.length_x)
# print(solver.length_y)
# print(solver.wavenumbers_x[indn])
# print(solver.wavenumbers_y[indm])
# exact_int = dblquad(f, 0, 1.0, 0, 0.5)
# print((exact_int))
Difference
def gaussian_pulse(x_grid, y_grid, x0, y0, sigma):
return np.exp(-((x_grid-x0)**2 + (y_grid-y0)**2)/(2*sigma**2))
def noise(grid):
return np.random.randn(grid.shape)
# Solve the wave equation
# u0 = np.zeros((solver.n_gridpoints_x, solver.n_gridpoints_y))
= (solver.modes[0, 0, :, :]**2)
u0 # Create a gaussian impulse
= np.zeros((solver.n_gridpoints_x, solver.n_gridpoints_y))
u0 = np.random.randn(solver.n_gridpoints_x, solver.n_gridpoints_y)
u0 = (0.37*solver.length_x, 0.63*solver.length_y)
ctr = 0.005
std for i in range(solver.n_gridpoints_x):
for j in range(solver.n_gridpoints_y):
= np.exp(-((solver.grid_x[i, j] - ctr[0])**2 + (solver.grid_y[i, j] - ctr[1])**2)/(2*std**2))
u0[i, j]
= gaussian_pulse(solver.grid_x, solver.grid_y, ctr[0], ctr[1], std)
u0_alt # Compare the two initial conditions
print(np.allclose(u0, u0_alt))
= np.zeros_like(u0)
v0 = 0.005*u0
u0 # Add a delta impuse to the initial velocity, close to the center
# v0[solver.n_gridpoints_x//2, solver.n_gridpoints_y//2] = 20
= solver.solve(u0=u0, v0=v0)
t, u, v print(f"u shape: {u.shape}")
print(f"v shape: {v.shape}")
True
bar_u shape: (625, 80)
bar_v shape: (625, 80)
u shape: (80, 161, 129)
v shape: (80, 161, 129)
# Check that the boundary conditions are maintained
print(np.all(u[:, 0, :] == 0))
print(np.all(u[:, -1, :] == 0))
print(np.all(u[:, :, 0] == 0))
print(np.all(u[:, :, -1] == 0))
# Plot the sum error of the solution at the edges for all timesteps
plt.figure()sum(u[:, 0, :], axis=1), label="bottom")
plt.plot(t, np.sum(u[:, -1, :], axis=1), label="top")
plt.plot(t, np.sum(u[:, :, 0], axis=1), label="left")
plt.plot(t, np.sum(u[:, :, -1], axis=1), label="right")
plt.plot(t, np.
plt.legend()"Time")
plt.xlabel("Sum of displacement at the edges")
plt.ylabel("Boundary conditions")
plt.title( plt.show()
True
False
True
False
# Plot the solution as an animation usign matplotlib
# N_plots=100
= plt.subplots(1,2)
fig,ax def animate(i):
0].clear()
ax[1].clear()
ax[0].imshow(u[i,:,:], aspect = aspect_ratio_dxdy, origin='lower')
ax[1].imshow(v[i,:,:], aspect = aspect_ratio_dxdy, origin='lower')
ax[0].set_title(f"Displacement")
ax[0].set_xlabel("y")
ax[0].set_ylabel("x")
ax[1].set_title(f"Velocity")
ax[1].set_xlabel("y")
ax[1].set_ylabel("x")
ax[= FuncAnimation(fig, animate, frames=20,
ani =500, repeat=False)
interval
plt.close()from IPython.display import HTML
HTML(ani.to_jshtml())