fea-surrogate / src /data /solvers /plate.py
WolfDavid's picture
Upload folder using huggingface_hub
8e5ba9e verified
"""Kirchhoff-Love rectangular plate solvers with Navier series solutions.
Two configurations:
- Simply supported on all edges + uniform pressure
- Clamped on all edges + uniform pressure
The simply supported case uses the exact Navier double Fourier series.
The clamped case uses tabulated coefficients from Timoshenko & Woinowsky-Krieger.
Assumptions (Kirchhoff-Love theory):
- Thin plate (t << a, b)
- Small deflections (w << t)
- Linear elasticity
- Homogeneous, isotropic material
Reference: Timoshenko, S.P. & Woinowsky-Krieger, S.,
"Theory of Plates and Shells", 2nd Ed.
"""
import math
from typing import Any
import numpy as np
from src.data.schema import SolutionResult
from src.data.solvers.base import AnalyticalSolver
class SimplySupported_UniformPressure(AnalyticalSolver):
"""Simply supported rectangular plate under uniform pressure q.
Uses Navier's double Fourier series solution (exact):
w(x,y) = sum_m sum_n [a_mn * sin(m*pi*x/a) * sin(n*pi*y/b)]
where a_mn = 16*q / (pi^6 * D * m*n * (m^2/a^2 + n^2/b^2)^2)
for odd m, n only.
Max deflection at center (a/2, b/2):
w_max = sum of a_mn * sin(m*pi/2) * sin(n*pi/2)
Max bending stress from max moment:
M_x_max at center, sigma = 6*M_x / t^2
"""
N_TERMS = 50 # Fourier series terms (converges well by ~20)
@property
def config_id(self) -> str:
return "plate_ss_uniform"
@property
def problem_family(self) -> str:
return "plate"
def solve(self, params: dict[str, Any]) -> SolutionResult:
a = params["length_a"]
b = params["length_b"]
t = params["thickness"]
E = params["elastic_modulus"]
nu = params["poisson_ratio"]
sigma_y = params["yield_strength"]
q = params["pressure"]
D = E * t**3 / (12.0 * (1.0 - nu**2))
w_max = 0.0
Mx_max = 0.0
My_max = 0.0
for m in range(1, self.N_TERMS + 1, 2): # odd m only
for n in range(1, self.N_TERMS + 1, 2): # odd n only
denom = (m**2 / a**2 + n**2 / b**2) ** 2
# Fourier coefficient
a_mn = 16.0 * q / (math.pi**6 * D * m * n * denom)
# Deflection at center: sin(m*pi/2) * sin(n*pi/2)
sign_w = math.sin(m * math.pi / 2) * math.sin(n * math.pi / 2)
w_max += a_mn * sign_w
# Bending moments at center
# M_x = -D * (d2w/dx2 + nu * d2w/dy2)
factor_x = (m * math.pi / a) ** 2 + nu * (n * math.pi / b) ** 2
factor_y = nu * (m * math.pi / a) ** 2 + (n * math.pi / b) ** 2
Mx_max += a_mn * factor_x * sign_w
My_max += a_mn * factor_y * sign_w
# Convert moment to stress: sigma = 6*M / t^2 (plate bending formula)
Mx_max *= D # a_mn already divided by D, so Mx = D * sum(a_mn * factor * sign)
My_max *= D
sigma_x = 6.0 * abs(Mx_max) / t**2
sigma_y_stress = 6.0 * abs(My_max) / t**2
max_stress = max(sigma_x, sigma_y_stress)
return SolutionResult.from_stress(max_stress, abs(w_max), sigma_y)
class Clamped_UniformPressure(AnalyticalSolver):
"""Clamped rectangular plate under uniform pressure q.
No exact Fourier series exists for clamped edges. Uses interpolated
coefficients from Timoshenko Table 35 (pp. 202):
w_max = alpha * q * a^4 / D
M_max = beta * q * a^2
where alpha and beta depend on the aspect ratio b/a.
For b/a >= 1 (a is the shorter side), standard tabulated values apply.
Reference values (b/a -> alpha, beta_center, beta_edge):
1.0 -> 0.00126, 0.0231, 0.0513
1.2 -> 0.00172, 0.0299, 0.0639
1.4 -> 0.00207, 0.0349, 0.0726
1.6 -> 0.00230, 0.0381, 0.0780
1.8 -> 0.00245, 0.0401, 0.0812
2.0 -> 0.00254, 0.0412, 0.0829
inf -> 0.00260, 0.0417, 0.0833
"""
# Tabulated coefficients: (b/a, alpha, beta_edge)
# beta_edge gives max stress at mid-edge (clamped), which exceeds center stress
_TABLE = np.array([
[1.0, 0.00126, 0.0513],
[1.2, 0.00172, 0.0639],
[1.4, 0.00207, 0.0726],
[1.6, 0.00230, 0.0780],
[1.8, 0.00245, 0.0812],
[2.0, 0.00254, 0.0829],
[3.0, 0.00260, 0.0833], # approaches infinite strip
[5.0, 0.00260, 0.0833],
])
@property
def config_id(self) -> str:
return "plate_fixed_uniform"
@property
def problem_family(self) -> str:
return "plate"
def solve(self, params: dict[str, Any]) -> SolutionResult:
a_dim = params["length_a"]
b_dim = params["length_b"]
t = params["thickness"]
E = params["elastic_modulus"]
nu = params["poisson_ratio"]
sigma_y = params["yield_strength"]
q = params["pressure"]
# Ensure a <= b (a is the shorter side for table lookup)
if a_dim > b_dim:
a, b = b_dim, a_dim
else:
a, b = a_dim, b_dim
D = E * t**3 / (12.0 * (1.0 - nu**2))
ratio = b / a
# Interpolate coefficients from table
alpha = float(np.interp(ratio, self._TABLE[:, 0], self._TABLE[:, 1]))
beta = float(np.interp(ratio, self._TABLE[:, 0], self._TABLE[:, 2]))
max_deflection = alpha * q * a**4 / D
# beta is the moment coefficient: M_max = beta * q * a^2
# Bending stress: sigma = 6*M / t^2
max_stress = 6.0 * beta * q * a**2 / t**2
return SolutionResult.from_stress(max_stress, abs(max_deflection), sigma_y)
PLATE_SOLVERS: dict[str, type[AnalyticalSolver]] = {
"plate_ss_uniform": SimplySupported_UniformPressure,
"plate_fixed_uniform": Clamped_UniformPressure,
}