fea-surrogate / src /data /schema.py
WolfDavid's picture
Upload folder using huggingface_hub
8e5ba9e verified
"""Data schemas for structural mechanics dataset.
Pydantic models enforce type safety and validation across the entire pipeline.
All quantities use SI units (meters, Pascals, Newtons).
"""
from enum import Enum
from typing import Optional
from pydantic import BaseModel, Field, field_validator
class ProblemFamily(str, Enum):
BEAM = "beam"
PLATE = "plate"
VESSEL = "vessel"
class BeamConfig(str, Enum):
SS_POINT = "beam_ss_point"
SS_UDL = "beam_ss_udl"
CANTILEVER_POINT = "beam_cantilever_point"
CANTILEVER_UDL = "beam_cantilever_udl"
FIXED_POINT = "beam_fixed_point"
FIXED_UDL = "beam_fixed_udl"
class PlateConfig(str, Enum):
SS_UNIFORM = "plate_ss_uniform"
FIXED_UNIFORM = "plate_fixed_uniform"
class VesselConfig(str, Enum):
CYLINDER = "vessel_cylinder"
SPHERE = "vessel_sphere"
class SafetyCategory(str, Enum):
SAFE = "safe" # SF >= 2.0
MARGINAL = "marginal" # 1.0 <= SF < 2.0
FAILURE = "failure" # SF < 1.0
class MaterialProperties(BaseModel):
"""Material properties in SI units."""
elastic_modulus: float = Field(..., gt=0, description="Young's modulus E [Pa]")
poisson_ratio: float = Field(..., gt=0, lt=0.5, description="Poisson's ratio nu [-]")
yield_strength: float = Field(..., gt=0, description="Yield strength sigma_y [Pa]")
density: float = Field(..., gt=0, description="Density rho [kg/m^3]")
class BeamParameters(BaseModel):
"""Input parameters for Euler-Bernoulli beam analysis."""
length: float = Field(..., gt=0, description="Beam span L [m]")
width: float = Field(..., gt=0, description="Cross-section width b [m]")
height: float = Field(..., gt=0, description="Cross-section height h [m]")
material: MaterialProperties
point_load: float = Field(default=0.0, ge=0, description="Point load P [N]")
distributed_load: float = Field(default=0.0, ge=0, description="Distributed load w [N/m]")
@property
def moment_of_inertia(self) -> float:
"""Second moment of area I = bh^3/12 [m^4]."""
return self.width * self.height**3 / 12.0
@property
def section_modulus(self) -> float:
"""Section modulus S = bh^2/6 [m^3]."""
return self.width * self.height**2 / 6.0
@property
def cross_section_area(self) -> float:
"""Cross-section area A = bh [m^2]."""
return self.width * self.height
class PlateParameters(BaseModel):
"""Input parameters for Kirchhoff-Love plate analysis."""
length_a: float = Field(..., gt=0, description="Plate dimension a [m]")
length_b: float = Field(..., gt=0, description="Plate dimension b [m]")
thickness: float = Field(..., gt=0, description="Plate thickness t [m]")
material: MaterialProperties
pressure: float = Field(..., gt=0, description="Uniform pressure q [Pa]")
@property
def flexural_rigidity(self) -> float:
"""Flexural rigidity D = Et^3 / (12(1 - nu^2)) [N*m]."""
E = self.material.elastic_modulus
nu = self.material.poisson_ratio
t = self.thickness
return E * t**3 / (12.0 * (1.0 - nu**2))
@property
def aspect_ratio(self) -> float:
"""Aspect ratio a/b [-]."""
return self.length_a / self.length_b
class VesselParameters(BaseModel):
"""Input parameters for thick-walled pressure vessel analysis (Lame equations)."""
inner_radius: float = Field(..., gt=0, description="Inner radius r_i [m]")
outer_radius: float = Field(..., gt=0, description="Outer radius r_o [m]")
length: Optional[float] = Field(default=None, gt=0, description="Cylinder length L [m]")
material: MaterialProperties
internal_pressure: float = Field(..., gt=0, description="Internal pressure p [Pa]")
@field_validator("outer_radius")
@classmethod
def outer_must_exceed_inner(cls, v: float, info: dict) -> float:
if "inner_radius" in info.data and v <= info.data["inner_radius"]:
raise ValueError("outer_radius must be greater than inner_radius")
return v
@property
def radius_ratio(self) -> float:
"""Radius ratio r_o/r_i [-]."""
return self.outer_radius / self.inner_radius
@property
def wall_thickness(self) -> float:
"""Wall thickness t = r_o - r_i [m]."""
return self.outer_radius - self.inner_radius
class SolutionResult(BaseModel):
"""Output from an analytical solver."""
max_stress: float = Field(..., ge=0, description="Maximum stress [Pa]")
max_deflection: float = Field(..., ge=0, description="Maximum deflection [m]")
safety_factor: float = Field(..., ge=0, description="Safety factor = sigma_y / max_stress [-]")
safety_category: SafetyCategory
@classmethod
def from_stress(cls, max_stress: float, max_deflection: float, yield_strength: float) -> "SolutionResult":
"""Construct result and auto-classify safety category."""
sf = yield_strength / max_stress if max_stress > 0 else float("inf")
if sf >= 2.0:
category = SafetyCategory.SAFE
elif sf >= 1.0:
category = SafetyCategory.MARGINAL
else:
category = SafetyCategory.FAILURE
return cls(
max_stress=max_stress,
max_deflection=abs(max_deflection),
safety_factor=sf,
safety_category=category,
)
class DatasetSample(BaseModel):
"""A single row in the dataset. Flat structure for Parquet compatibility."""
sample_id: str
problem_family: ProblemFamily
config_id: str
# Geometry (SI: meters)
length: float
width: Optional[float] = None
height: Optional[float] = None
inner_radius: Optional[float] = None
outer_radius: Optional[float] = None
thickness: Optional[float] = None # plate thickness
# Material (SI)
elastic_modulus: float
poisson_ratio: float
yield_strength: float
density: float
# Loading (SI)
point_load: float = 0.0
distributed_load: float = 0.0
internal_pressure: float = 0.0
pressure: float = 0.0 # plate uniform pressure
# Derived geometry
moment_of_inertia: Optional[float] = None
section_modulus: Optional[float] = None
cross_section_area: Optional[float] = None
# Outputs
max_stress: float
max_deflection: float
safety_factor: float
safety_category: SafetyCategory