Spaces:
Sleeping
Sleeping
| """ | |
| FEA Surrogate β Hugging Face Spaces Entry Point | |
| This demo runs the analytical solvers (Euler-Bernoulli beam theory, | |
| Kirchhoff plate theory, Lame equations) that form the ground-truth | |
| training data for the neural surrogate model. | |
| **Analytical mode** β Fast, exact closed-form solutions for 10 classical | |
| structural problems. No training required, runs instantly on CPU. | |
| **Neural mode** β Uses a trained PI-ResMLP deep ensemble with uncertainty | |
| quantification. Requires GPU training. Coming soon once NYU Torch | |
| supercomputer access is activated. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import sys | |
| import time | |
| from typing import Any | |
| # Ensure the project root is on sys.path for absolute imports | |
| _PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) | |
| if _PROJECT_ROOT not in sys.path: | |
| sys.path.insert(0, _PROJECT_ROOT) | |
| import gradio as gr | |
| from src.app.materials import MATERIAL_NAMES, MATERIAL_PRESETS | |
| from src.app.visualizations import ( | |
| create_beam_deformation, | |
| create_comparison_chart, | |
| create_safety_gauge, | |
| ) | |
| from src.data.solvers.beam import BEAM_SOLVERS | |
| from src.data.solvers.plate import PLATE_SOLVERS | |
| from src.data.solvers.vessel import VESSEL_SOLVERS | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Configuration | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| PROBLEM_TYPES = { | |
| "Simply Supported Beam β Point Load": "beam_ss_point", | |
| "Simply Supported Beam β UDL": "beam_ss_udl", | |
| "Cantilever Beam β Point Load": "beam_cantilever_point", | |
| "Cantilever Beam β UDL": "beam_cantilever_udl", | |
| "Fixed-Fixed Beam β Point Load": "beam_fixed_point", | |
| "Fixed-Fixed Beam β UDL": "beam_fixed_udl", | |
| "Simply Supported Plate β Uniform Pressure": "plate_ss_uniform", | |
| "Clamped Plate β Uniform Pressure": "plate_fixed_uniform", | |
| "Thick-Walled Cylinder": "vessel_cylinder", | |
| "Thick-Walled Sphere": "vessel_sphere", | |
| } | |
| ALL_SOLVERS = {**BEAM_SOLVERS, **PLATE_SOLVERS, **VESSEL_SOLVERS} | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Core prediction function | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def predict( | |
| problem_type: str, | |
| length: float, | |
| width: float, | |
| height: float, | |
| inner_radius: float, | |
| outer_radius: float, | |
| thickness: float, | |
| material_name: str, | |
| elastic_modulus_gpa: float, | |
| poisson_ratio: float, | |
| yield_strength_mpa: float, | |
| density: float, | |
| load_value: float, | |
| pressure_value: float, | |
| ): | |
| """Run the analytical solver and return formatted results + plots.""" | |
| # Convert display units back to SI | |
| elastic_modulus = elastic_modulus_gpa * 1e9 | |
| yield_strength = yield_strength_mpa * 1e6 | |
| config_id = PROBLEM_TYPES.get(problem_type, "beam_ss_point") | |
| family = config_id.split("_")[0] | |
| # Build solver params based on problem family | |
| if family == "beam": | |
| load_key = "point_load" if "point" in config_id else "distributed_load" | |
| solver_params: dict[str, Any] = { | |
| "length": length, | |
| "width": width, | |
| "height": height, | |
| "elastic_modulus": elastic_modulus, | |
| "yield_strength": yield_strength, | |
| load_key: load_value, | |
| } | |
| elif family == "plate": | |
| solver_params = { | |
| "length_a": length, | |
| "length_b": width, | |
| "thickness": thickness, | |
| "elastic_modulus": elastic_modulus, | |
| "poisson_ratio": poisson_ratio, | |
| "yield_strength": yield_strength, | |
| "pressure": pressure_value, | |
| } | |
| else: # vessel | |
| solver_params = { | |
| "inner_radius": inner_radius, | |
| "outer_radius": outer_radius, | |
| "elastic_modulus": elastic_modulus, | |
| "poisson_ratio": poisson_ratio, | |
| "yield_strength": yield_strength, | |
| "internal_pressure": pressure_value, | |
| } | |
| # Run analytical solver | |
| start_time = time.perf_counter() | |
| try: | |
| solver = ALL_SOLVERS[config_id]() | |
| analytical = solver.solve(solver_params) | |
| except Exception as exc: | |
| error_md = f"### Error\n\nSolver failed: `{exc}`\n\nCheck input parameters and try again." | |
| return error_md, None, None | |
| latency_ms = (time.perf_counter() - start_time) * 1000 | |
| # Compute safety classification | |
| sf = analytical.safety_factor | |
| if sf >= 2.0: | |
| safety_badge = "SAFE" | |
| safety_color = "#66BB6A" | |
| elif sf >= 1.0: | |
| safety_badge = "MARGINAL" | |
| safety_color = "#FFA726" | |
| else: | |
| safety_badge = "FAILURE" | |
| safety_color = "#EF5350" | |
| # Results markdown | |
| results_md = f""" | |
| ### Analytical Solution | |
| | Metric | Value | | |
| |--------|-------| | |
| | **Max Stress** | {analytical.max_stress / 1e6:.3f} MPa | | |
| | **Max Deflection** | {analytical.max_deflection * 1e3:.4f} mm | | |
| | **Safety Factor** | {analytical.safety_factor:.3f} | | |
| | **Yield Strength** | {yield_strength / 1e6:.1f} MPa | | |
| **Status:** <span style="color:{safety_color};font-weight:bold;font-size:1.2em">{safety_badge}</span> | |
| | | |
| **Computed in {latency_ms:.2f} ms** | |
| --- | |
| *Using closed-form {family} solution with {material_name}. See the "Model Info" tab for the full theory reference.* | |
| """ | |
| # Generate plots (use analytical stress as the "neural" value so the | |
| # comparison chart shows equal bars; keeps the existing figure code | |
| # working without modifications) | |
| comparison_fig = create_comparison_chart( | |
| neural_stress=analytical.max_stress, | |
| analytical_stress=analytical.max_stress, | |
| neural_deflection=analytical.max_deflection, | |
| analytical_deflection=analytical.max_deflection, | |
| stress_ci=(analytical.max_stress * 0.999, analytical.max_stress * 1.001), | |
| deflection_ci=(analytical.max_deflection * 0.999, analytical.max_deflection * 1.001), | |
| ) | |
| if family == "beam": | |
| deform_fig = create_beam_deformation( | |
| length, height, analytical.max_deflection, config_id | |
| ) | |
| else: | |
| deform_fig = create_safety_gauge(sf) | |
| return results_md, comparison_fig, deform_fig | |
| def update_material(material_name: str): | |
| """Update material property fields when preset is selected.""" | |
| props = MATERIAL_PRESETS.get(material_name, MATERIAL_PRESETS.get("ASTM A36 Steel")) | |
| return ( | |
| props["elastic_modulus"] / 1e9, | |
| props["poisson_ratio"], | |
| props["yield_strength"] / 1e6, | |
| props["density"], | |
| ) | |
| def update_visibility(problem_type: str): | |
| """Show/hide input fields based on problem type.""" | |
| config_id = PROBLEM_TYPES.get(problem_type, "beam_ss_point") | |
| is_beam = config_id.startswith("beam") | |
| is_plate = config_id.startswith("plate") | |
| is_vessel = config_id.startswith("vessel") | |
| is_point = "point" in config_id | |
| return ( | |
| gr.Number(visible=is_beam or is_plate), # length | |
| gr.Number(visible=is_beam or is_plate), # width | |
| gr.Number(visible=is_beam), # height | |
| gr.Number(visible=is_vessel), # inner_radius | |
| gr.Number(visible=is_vessel), # outer_radius | |
| gr.Number(visible=is_plate), # thickness | |
| gr.Number( | |
| visible=is_beam, | |
| label="Point Load [N]" if is_point else "Distributed Load [N/m]", | |
| ), | |
| gr.Number(visible=is_plate or is_vessel), # pressure | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Gradio UI | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Blocks(title="FEA Surrogate β Structural Analysis", theme=gr.themes.Soft()) as demo: | |
| gr.Markdown( | |
| """ | |
| # FEA Surrogate β Structural Analysis Engine | |
| **Physics-informed neural surrogate** for fast structural analysis | |
| with uncertainty quantification. This demo runs the **analytical | |
| solvers** (Euler-Bernoulli, Kirchhoff, Lame) that generate the | |
| training data for the neural model. | |
| > **Neural mode coming soon** β once NYU Torch supercomputer access | |
| > is activated, the trained PI-ResMLP deep ensemble will replace | |
| > the analytical solver with a ~1000Γ faster ML prediction that | |
| > includes calibrated uncertainty intervals. | |
| """ | |
| ) | |
| with gr.Tabs(): | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Tab 1 β Predict | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Predict"): | |
| with gr.Row(): | |
| with gr.Column(scale=4): | |
| problem_type = gr.Dropdown( | |
| choices=list(PROBLEM_TYPES.keys()), | |
| value="Simply Supported Beam β Point Load", | |
| label="Problem Type", | |
| ) | |
| gr.Markdown("#### Geometry") | |
| with gr.Row(): | |
| length_input = gr.Number(value=2.0, label="Length [m]", minimum=0.01) | |
| width_input = gr.Number(value=0.05, label="Width [m]", minimum=0.001) | |
| height_input = gr.Number(value=0.10, label="Height [m]", minimum=0.001) | |
| with gr.Row(): | |
| inner_r = gr.Number(value=0.1, label="Inner Radius [m]", visible=False) | |
| outer_r = gr.Number(value=0.15, label="Outer Radius [m]", visible=False) | |
| thick = gr.Number(value=0.01, label="Thickness [m]", visible=False) | |
| gr.Markdown("#### Material") | |
| material_dropdown = gr.Dropdown( | |
| choices=MATERIAL_NAMES, | |
| value="ASTM A36 Steel", | |
| label="Material Preset", | |
| ) | |
| with gr.Row(): | |
| e_mod = gr.Number(value=200.0, label="E [GPa]") | |
| nu = gr.Number(value=0.26, label="Poisson's Ratio") | |
| with gr.Row(): | |
| sig_y = gr.Number(value=250.0, label="Yield Strength [MPa]") | |
| dens = gr.Number(value=7850.0, label="Density [kg/mΒ³]") | |
| gr.Markdown("#### Loads") | |
| with gr.Row(): | |
| load_value = gr.Number(value=10000.0, label="Point Load [N]") | |
| pressure_value = gr.Number(value=100000.0, label="Pressure [Pa]", visible=False) | |
| predict_btn = gr.Button("Run Analysis", variant="primary", size="lg") | |
| with gr.Column(scale=6): | |
| results_output = gr.Markdown() | |
| comparison_plot = gr.Plot(label="Stress & Deflection") | |
| deform_plot = gr.Plot(label="Deformation / Safety Gauge") | |
| # Wiring | |
| material_dropdown.change( | |
| update_material, | |
| inputs=[material_dropdown], | |
| outputs=[e_mod, nu, sig_y, dens], | |
| ) | |
| problem_type.change( | |
| update_visibility, | |
| inputs=[problem_type], | |
| outputs=[ | |
| length_input, width_input, height_input, | |
| inner_r, outer_r, thick, | |
| load_value, pressure_value, | |
| ], | |
| ) | |
| predict_btn.click( | |
| predict, | |
| inputs=[ | |
| problem_type, | |
| length_input, width_input, height_input, | |
| inner_r, outer_r, thick, | |
| material_dropdown, | |
| e_mod, nu, sig_y, dens, | |
| load_value, pressure_value, | |
| ], | |
| outputs=[results_output, comparison_plot, deform_plot], | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Tab 2 β Theory | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Theory"): | |
| gr.Markdown( | |
| """ | |
| ## Analytical Foundations | |
| The ground-truth dataset for the neural surrogate is | |
| generated from closed-form solutions of classical elasticity | |
| theory. These are the same equations used throughout | |
| undergraduate mechanical engineering. | |
| ### Beam Theory (Euler-Bernoulli) | |
| For a beam with elastic modulus $E$, moment of inertia $I$, | |
| and length $L$: | |
| **Simply supported, central point load $P$:** | |
| - Max deflection: $\\delta_{max} = \\frac{PL^3}{48EI}$ | |
| - Max stress: $\\sigma_{max} = \\frac{PL}{4S}$ where $S$ is section modulus | |
| **Cantilever, tip point load $P$:** | |
| - Max deflection: $\\delta_{max} = \\frac{PL^3}{3EI}$ | |
| - Max stress: $\\sigma_{max} = \\frac{PL}{S}$ | |
| **Simply supported, uniform load $w$:** | |
| - Max deflection: $\\delta_{max} = \\frac{5wL^4}{384EI}$ | |
| - Max stress: $\\sigma_{max} = \\frac{wL^2}{8S}$ | |
| Reference: Timoshenko & Gere, *Mechanics of Materials* | |
| ### Plate Theory (Kirchhoff) | |
| Thin plates under uniform pressure $q$: | |
| **Simply supported rectangular plate:** | |
| - Max deflection: $w_{max} = \\alpha \\frac{q a^4}{Eh^3}$ | |
| - Max stress: $\\sigma_{max} = \\beta \\frac{q a^2}{h^2}$ | |
| Where $\\alpha$ and $\\beta$ depend on the aspect ratio $a/b$ | |
| and are tabulated in Timoshenko's *Theory of Plates and Shells*. | |
| ### Thick-Walled Vessels (Lame) | |
| For a cylinder with internal pressure $p_i$, inner radius $r_i$, | |
| outer radius $r_o$: | |
| - Hoop stress: $\\sigma_{\\theta} = \\frac{p_i r_i^2}{r_o^2 - r_i^2}\\left(1 + \\frac{r_o^2}{r^2}\\right)$ | |
| - Radial stress: $\\sigma_r = \\frac{p_i r_i^2}{r_o^2 - r_i^2}\\left(1 - \\frac{r_o^2}{r^2}\\right)$ | |
| Max stress occurs at the inner wall ($r = r_i$). | |
| ### Von Mises Failure Criterion | |
| For a multi-axial stress state: | |
| $$\\sigma_{VM} = \\sqrt{\\frac{(\\sigma_1 - \\sigma_2)^2 + (\\sigma_2 - \\sigma_3)^2 + (\\sigma_3 - \\sigma_1)^2}{2}}$$ | |
| Yielding occurs when $\\sigma_{VM} \\geq \\sigma_Y$. | |
| """ | |
| ) | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| # Tab 3 β Model Info | |
| # βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ | |
| with gr.Tab("Model Info"): | |
| gr.Markdown( | |
| """ | |
| ## Neural Surrogate Architecture (coming soon) | |
| ### Current: Analytical Mode | |
| This HF Space runs the **exact analytical solvers** that | |
| form the training dataset. They are fast, exact, and | |
| require no ML β ideal for validating the pipeline end-to-end | |
| before neural training. | |
| ### Planned: PI-ResMLP Deep Ensemble | |
| Once **NYU Torch** supercomputer access is activated, the | |
| Space will upgrade to a trained neural surrogate with: | |
| - **Architecture:** Physics-Informed Residual MLP | |
| (PI-ResMLP) with skip connections and physics loss | |
| - **Ensemble:** 5 independently trained members for | |
| law-of-total-variance uncertainty quantification | |
| - **Dataset:** 100K analytically-generated samples | |
| (Latin Hypercube Sampling) | |
| - **Training:** Mixed precision (fp16), early stopping, | |
| gradient clipping, cosine learning rate schedule | |
| - **Evaluation:** RΒ², MAPE, calibration curves, | |
| physics constraint satisfaction | |
| ### Why Neural Surrogate? | |
| Analytical solvers are exact but limited to the idealized | |
| geometries they were derived for. A neural surrogate trained | |
| on the analytical solutions can: | |
| 1. **Generalize** to combinations not in the training set | |
| 2. **Provide uncertainty** estimates via ensemble variance | |
| 3. **Run 1000Γ faster** than traditional FEA for complex | |
| geometries (after training is done) | |
| 4. **Embed physics** via the loss function, improving | |
| extrapolation beyond the training distribution | |
| ### Tech Stack | |
| - PyTorch + torch.compile | |
| - Mixed precision training (fp16 autocast) | |
| - Latin Hypercube Sampling for dataset generation | |
| - Log-transform + standardization for numerical stability | |
| - Deep ensemble uncertainty quantification | |
| - Gradio for the UI | |
| - Hugging Face Spaces for deployment | |
| - NYU Torch supercomputer for training | |
| """ | |
| ) | |
| gr.Markdown( | |
| """ | |
| --- | |
| **Source:** [github.com/wolfwdavid/ai-tools-collection](https://github.com/wolfwdavid/ai-tools-collection) | |
| | | |
| **HF Profile:** [@WolfDavid](https://huggingface.co/WolfDavid) | |
| """ | |
| ) | |
| if __name__ == "__main__": | |
| demo.launch() | |