LLM_explanation / app.py
EricCRX's picture
Upload 2 files
451dbc3 verified
raw
history blame
8.94 kB
# -*- coding: utf-8 -*-
"""Untitled3.ipynb
Automatically generated by Colab.
Original file is located at
https://colab.research.google.com/drive/11sS74P6WlrbXCzSh9_b8ELPTtcfdsz8s
"""
# -*- coding: utf-8 -*-
# HW3: Deterministic calculator + LLM explanation (Gradio)
# Topic: Simply supported beam with center point load (max bending stress & midspan deflection)
import json
import math
from dataclasses import dataclass, asdict
import gradio as gr
import numpy as np
import pandas as pd
LLM_MODEL_ID = "google/flan-t5-small"
try:
from transformers import pipeline
_llm = pipeline("text2text-generation", model=LLM_MODEL_ID)
except Exception:
_llm = None # no transformers
# -----------------------------
# -----------------------------
@dataclass
class BeamInputs:
L_m: float # 跨度(m)
P_kN: float # 中点集中力(kN)
E_GPa: float # 弹性模量(GPa)
I_cm4: float # 截面惯性矩(cm^4)
c_cm: float # 中性轴到受拉/受压边缘距离(cm)- 极纤维距离
allowable_MPa: float | None = None # 允许应力(MPa,可选)
yield_MPa: float | None = None # 屈服强度(MPa,可选)
safety_factor: float | None = None # 安全系数 n(可选)
def _validate(x, lo, hi, name):
if not (lo <= x <= hi):
raise gr.Error(f"{name} 超出有效范围 [{lo}, {hi}]")
def validate_inputs(inp: BeamInputs):
_validate(inp.L_m, 0.5, 50.0, "跨度 L (m)")
_validate(inp.P_kN, 0.1, 2000.0, "荷载 P (kN)")
_validate(inp.E_GPa, 1.0, 300.0, "弹性模量 E (GPa)")
_validate(inp.I_cm4, 1.0, 1e9, "惯性矩 I (cm^4)")
_validate(inp.c_cm, 0.01, 200.0, "极纤维距离 c (cm)")
if inp.safety_factor is not None:
_validate(inp.safety_factor, 1.0, 5.0, "安全系数 n")
if inp.allowable_MPa is not None:
_validate(inp.allowable_MPa, 1.0, 5000.0, "允许应力 (MPa)")
if inp.yield_MPa is not None:
_validate(inp.yield_MPa, 10.0, 10000.0, "屈服强度 (MPa)")
def beam_calc(inp: BeamInputs):
"""
Simply supported beam with a centered point load P.
M_max = P*L/4
sigma_max = M_max * c / I
delta_mid = P * L^3 / (48 * E * I)
单位处理见下。
"""
validate_inputs(inp)
P_N = inp.P_kN * 1_000 # kN -> N
L = inp.L_m # m
E_Pa = inp.E_GPa * 1e9 # GPa -> Pa
I_m4 = inp.I_cm4 * 1e-8 # cm^4 -> m^4 (1 cm = 0.01 m → (0.01)^4 = 1e-8)
c_m = inp.c_cm * 0.01 # cm -> m
M_max_Nm = P_N * L / 4.0
sigma_Pa = M_max_Nm * c_m / I_m4
sigma_MPa = sigma_Pa / 1e6
delta_m = P_N * (L**3) / (48 * E_Pa * I_m4)
delta_mm = delta_m * 1e3
allow = None
if inp.allowable_MPa is not None:
allow = float(inp.allowable_MPa)
elif inp.yield_MPa is not None and inp.safety_factor is not None:
allow = float(inp.yield_MPa) / float(inp.safety_factor)
utilization = (sigma_MPa / allow) if (allow and allow > 0) else None
pass_check = (utilization <= 1.0) if utilization is not None else None
record = {
"scope": "Simply supported beam, centered point load; small deflection; linear elastic; prismatic section.",
"inputs": {
"L_m": inp.L_m,
"P_kN": inp.P_kN,
"E_GPa": inp.E_GPa,
"I_cm4": inp.I_cm4,
"c_cm": inp.c_cm,
"allowable_MPa": inp.allowable_MPa,
"yield_MPa": inp.yield_MPa,
"safety_factor": inp.safety_factor,
},
"units": {
"L_m": "m", "P_kN": "kN", "E_GPa": "GPa",
"I_cm4": "cm^4", "c_cm": "cm",
"sigma_MPa": "MPa", "M_max_kN_m": "kN·m", "delta_mm": "mm",
},
"formulas": {
"M_max": "P*L/4",
"sigma_max": "M_max * c / I",
"delta_mid": "P * L^3 / (48 * E * I)"
},
"results": {
"M_max_kN_m": M_max_Nm / 1_000.0, # N·m -> kN·m
"sigma_MPa": sigma_MPa,
"delta_mm": delta_mm,
"allowable_MPa": allow,
"utilization": utilization,
"pass_check": pass_check
},
"notes": [
"If allowable stress is not provided, pass/fail is left as None.",
"For non-rectangular/I-beam sections, user provides I and c (or catalogs)."
]
}
table = pd.DataFrame(
{
"Quantity": ["M_max", "sigma_max", "delta_mid", "allowable", "utilization", "pass"],
"Value": [
round(record["results"]["M_max_kN_m"], 4),
round(record["results"]["sigma_MPa"], 4),
round(record["results"]["delta_mm"], 4),
(None if allow is None else round(allow, 4)),
(None if utilization is None else round(utilization, 4)),
str(pass_check)
],
"Unit": ["kN·m", "MPa", "mm", "MPa", "-", "-"]
}
)
return record, table
# -----------------------------
# 2) LLM
# -----------------------------
def explain_record(record: dict) -> str:
compact = json.dumps(record, ensure_ascii=False)
prompt = (
"You are an engineering assistant. Explain the beam calculation results clearly for a non-expert.\n"
"Use the JSON below. Restate the inputs (with units), show the formulas, plug in numbers, and summarize.\n"
"If allowable stress is present, state pass/fail and utilization. Avoid hallucinations; only use given data.\n"
f"JSON:\n{compact}\n"
"Write 120-200 words. Use bullet points for steps. Keep units."
)
if _llm is not None:
try:
out = _llm(prompt, max_new_tokens=220, do_sample=False)[0]["generated_text"]
return out
except Exception:
pass
r = record["results"]
i = record["inputs"]
lines = [
"### Explanation",
f"- Span L = {i['L_m']} m; Center load P = {i['P_kN']} kN.",
f"- E = {i['E_GPa']} GPa; I = {i['I_cm4']} cm^4; c = {i['c_cm']} cm.",
"- Formulas: M=P·L/4; σ=M·c/I; δ=P·L³/(48·E·I).",
f"- Computed: M_max = {r['M_max_kN_m']:.3f} kN·m; σ_max = {r['sigma_MPa']:.3f} MPa; δ_mid = {r['delta_mm']:.3f} mm.",
]
if r["allowable_MPa"] is not None:
lines.append(f"- Allowable = {r['allowable_MPa']:.3f} MPa; utilization = {r['utilization']:.3f}.")
lines.append(f"- Pass/Fail: {r['pass_check']}.")
else:
lines.append("- No allowable stress provided → pass/fail not evaluated.")
return "\n".join(lines)
# -----------------------------
# 3) Gradio
# -----------------------------
def run_calc(L_m, P_kN, E_GPa, I_cm4, c_cm, allowable_MPa, yield_MPa, safety_factor):
allowable = None if (allowable_MPa is None or allowable_MPa == "") else float(allowable_MPa)
fy = None if (yield_MPa is None or yield_MPa == "") else float(yield_MPa)
n = None if (safety_factor is None or safety_factor == "") else float(safety_factor)
inp = BeamInputs(
L_m=float(L_m), P_kN=float(P_kN), E_GPa=float(E_GPa),
I_cm4=float(I_cm4), c_cm=float(c_cm),
allowable_MPa=allowable, yield_MPa=fy, safety_factor=n
)
rec, table = beam_calc(inp)
expl = explain_record(rec)
return table, expl, rec
with gr.Blocks() as demo:
gr.Markdown("# Simply Supported Beam — Center Load\nDeterministic calculator + LLM explanation (first principles).")
with gr.Row():
with gr.Column():
L_m = gr.Slider(0.5, 30.0, value=6.0, step=0.1, label="Span L (m)")
P_kN = gr.Slider(0.1, 500.0, value=20.0, step=0.1, label="Center load P (kN)")
E_GPa = gr.Slider(10.0, 300.0, value=200.0, step=1.0, label="Elastic modulus E (GPa)")
I_cm4 = gr.Number(value=8000.0, label="Second moment of area I (cm^4)") # 例如 IPE/I型钢量级
c_cm = gr.Number(value=15.0, label="Extreme fiber distance c (cm)")
with gr.Accordion("Optional: Allowables", open=False):
allowable_MPa = gr.Number(value=None, label="Allowable stress σ_allow (MPa)")
yield_MPa = gr.Number(value=None, label="Yield strength fy (MPa)")
safety_factor = gr.Number(value=1.5, label="Safety factor n")
run_btn = gr.Button("Compute")
with gr.Column():
results = gr.Dataframe(headers=["Quantity", "Value", "Unit"], label="Numerical results", wrap=True)
explanation = gr.Markdown(label="Explain the results")
record_json = gr.JSON(label="Structured record (for LLM)")
run_btn.click(
fn=run_calc,
inputs=[L_m, P_kN, E_GPa, I_cm4, c_cm, allowable_MPa, yield_MPa, safety_factor],
outputs=[results, explanation, record_json],
api_name="run_calc",
)
if __name__ == "__main__":
demo.launch()