"""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, }