# ========================= # Cantilever Rectangular Beam — Point Load (Free End or at Position a) # ========================= import math import json import gradio as gr import pandas as pd # ---- Optional LLM with safe fallback ---- _USE_LLM = True try: from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct" _tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) _pipe = pipeline( task="text-generation", model=AutoModelForCausalLM.from_pretrained(MODEL_ID), tokenizer=_tokenizer, ) except Exception: _USE_LLM = False _tokenizer = None _pipe = None SCOPE_MD = """ ### Scope & Assumptions - Problem: **Cantilever rectangular beam**, **point load** either at the **free end** or at **position a** from the fixed end. - Outputs: Maximum bending stress (σ_max), yield FoS, free-end deflection (δ), deflection FoS vs. **L/180** (typical cantilever service limit). - Method: Euler–Bernoulli beam theory (linear-elastic, small deflection). Shear deformation and local buckling not included. - Section: Rectangle with **width b** (in-plane) and **height h** (bends about the strong axis). - Units: SI (m, N, GPa, MPa). Results in MPa and mm. ### Valid ranges (hard checks) - 0.05 < L ≤ 10 m - 0 < P ≤ 1*10^6 N - 1 ≤ E ≤ 400 GPa - 10 ≤ Sy ≤ 3000 MPa - 0.005 < b ≤ 2 m - 0.005 < h ≤ 2 m - For load at position a: 0 < a ≤ L **Notes:** Service limit uses **L/180** for cantilevers (typical). """ # ---------- Validation & core ---------- def _validate_inputs(L_m, P_N, E_GPa, Sy_MPa, b_m, h_m): errs = [] def in_range(name, val, lo, hi): if not (lo < val <= hi): errs.append(f"{name} must be in ({lo}, {hi}] (got {val}).") in_range("Beam length L [m]", L_m, 0.05, 10.0) in_range("Point load P [N]", P_N, 0.0, 1_000_000.0) in_range("Elastic modulus E [GPa]", E_GPa, 1.0, 400.0) in_range("Yield strength Sy [MPa]", Sy_MPa, 10.0, 3000.0) in_range("Section width b [m]", b_m, 0.005, 2.0) in_range("Section height h [m]", h_m, 0.005, 2.0) if errs: raise ValueError("\n".join(errs)) def rect_I(b, h): # Strong-axis bending: I = b*h^3/12 return b * (h**3) / 12.0 def calc_cantilever_rect(L_m, P_N, E_GPa, Sy_MPa, b_m, h_m, mode, a_in): """ Cantilever rectangular beam with point load at: - Free end -> a = L - Position a -> 0 < a <= L """ _validate_inputs(L_m, P_N, E_GPa, Sy_MPa, b_m, h_m) if mode == "Free end (a = L)": a = L_m mode_note = "Free end load (a = L)" else: a = float(a_in) if not (0.0 < a <= L_m): raise ValueError(f"a must satisfy 0 < a ≤ L (got a={a}, L={L_m}).") mode_note = f"Load at position a (a = {a:g} m)" E_Pa = E_GPa * 1e9 Sy_Pa = Sy_MPa * 1e6 I = rect_I(b_m, h_m) c = h_m / 2.0 # Max moment at fixed end # M_max = P * a M = P_N * a # Max bending stress (outer fiber at fixed end) # sigma_max = M*c / I sigma_max_Pa = (M * c) / I sigma_max_MPa = sigma_max_Pa / 1e6 # Free-end deflection for cantilever with a point load at position a: # δ(L) = P * a^2 * (3L - a) / (6 E I) delta_m = (P_N * (a**2) * (3.0 * L_m - a)) / (6.0 * E_Pa * I) delta_mm = delta_m * 1e3 # Serviceability (typical cantilever): δ_allow = L / 180 delta_allow_m = L_m / 180.0 fos_deflection = (delta_allow_m / delta_m) if delta_m > 0 else math.inf deflection_ok = delta_m <= delta_allow_m utilization = sigma_max_Pa / Sy_Pa fos_yield = (1.0 / utilization) if utilization > 0 else math.inf passes_yield = sigma_max_Pa <= Sy_Pa overall_ok = bool(passes_yield and deflection_ok) structured = { "problem": "Cantilever rectangular beam with point load (free end or at position a)", "assumptions": [ "Linear-elastic, small deflection (Euler–Bernoulli)", "No shear deformation or local buckling", "Rectangular section, strong-axis bending" ], "mode": mode_note, "inputs_SI": { "L_m": L_m, "P_N": P_N, "E_GPa": E_GPa, "Sy_MPa": Sy_MPa, "section": {"b_m": b_m, "h_m": h_m}, "a_m": a }, "section_properties": {"I_m4": I, "c_m": c}, "formulas": { "I_rect": "I = b*h^3/12", "M_max": "M = P*a", "sigma_max": "sigma_max = M*c / I", "delta_tip": "delta(L) = P*a^2*(3L - a)/(6*E*I)", "service_limit": "delta_allow = L/180 (cantilever typical)" }, "results": { "sigma_max_MPa": sigma_max_MPa, "FoS_yield": fos_yield, "delta_mm": delta_mm, "FoS_deflection": fos_deflection }, "verdicts": { "strength_ok": passes_yield, "service_ok": deflection_ok, "overall_ok": overall_ok } } # ---- Show the math (explicit '*' multiplications) ---- def _fmt(x, d=6): try: return f"{x:.{d}g}" except Exception: return str(x) steps_md = "\n".join([ "## Show the math", f"Mode: {mode_note}", f"L = {_fmt(L_m)} m, P = {_fmt(P_N)} N, E = {_fmt(E_GPa)} GPa, Sy = {_fmt(Sy_MPa)} MPa", f"b = {_fmt(b_m)} m, h = {_fmt(h_m)} m, a = {_fmt(a)} m", "", "I = b*h^3/12", f" = {b_m} * {h_m}^3 / 12", f" = {I:.6e} m^4", f"c = h/2 = {h_m} / 2 = {h_m/2:.4f} m", "", "M_max = P * a", f" = {P_N} * {a} = {P_N*a:.6e} N·m", "sigma_max = M * c / I", f" = ({P_N*a:.6e}) * ({h_m}/2) / ({I:.6e})", f" = {sigma_max_MPa:.3f} MPa", "", "delta(L) = P * a^2 * (3*L - a) / (6*E*I)", f" = {P_N} * {a}^2 * (3*{L_m} - {a}) / (6 * ({E_GPa} * 10^9) * {I:.6e})", f" = {delta_mm:.3f} mm", f"delta_allow = L / 180 = {L_m} / 180 = {L_m/180.0:.6f} m = {L_m/180.0*1e3:.3f} mm", "", "FoS_yield = Sy / sigma_max", f" = {Sy_MPa} / {sigma_max_MPa:.3f} = {fos_yield:.3f}", "FoS_deflection = delta_allow / delta", f" = {L_m/180.0*1e3:.3f} / {delta_mm:.3f} = {fos_deflection:.3f}", ]) return { "results": { "sigma_max_MPa": sigma_max_MPa, "safety_factor_yield": fos_yield, "delta_m": delta_m, "delta_mm": delta_mm, "fos_deflection": fos_deflection, }, "verdict": { "passes_yield": bool(passes_yield), "passes_serviceability": bool(deflection_ok), "overall_ok": bool(overall_ok), "strength_message": "OK: stress < yield" if passes_yield else "Not OK: stress ≥ yield", "service_message": "OK: deflection < L/180" if deflection_ok else "Not OK: deflection ≥ L/180", }, "structured_message": json.dumps(structured, indent=2), "steps_markdown": steps_md } # ---------- LLM helper (safe) ---------- def _format_chat(system_prompt: str, user_prompt: str) -> str: if _tokenizer is None: return system_prompt + "\n\n" + user_prompt messages = [{"role":"system","content":system_prompt},{"role":"user","content":user_prompt}] return _tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) def llm_explain(structured_message: str) -> str: # Deterministic, number-rich fallback if (not _USE_LLM) or (_tokenizer is None) or (_pipe is None): try: d = json.loads(structured_message) r = d["results"] v = d["verdicts"] inp = d["inputs_SI"] L = float(inp["L_m"]) Sy = float(inp["Sy_MPa"]) sigma = float(r["sigma_max_MPa"]) delta = float(r["delta_mm"]) delta_allow = L/180.0*1e3 # mm s_msg = "OK" if v["strength_ok"] else "NOT OK" d_msg = "OK" if v["service_ok"] else "NOT OK" return ( f"Strength {s_msg} (σ_max={sigma:.2f} MPa vs Sy={Sy:.0f} MPa); " f"deflection {d_msg} (δ={delta:.2f} mm vs L/180={delta_allow:.2f} mm)." ) except Exception: return "Quick take: strength and deflection (L/180) checks computed; see the table and math." # LLM path (only if model actually loads) system_prompt = ( "You explain engineering to a smart 5-year-old using a quick food analogy. " "You ALWAYS respond in exactly ONE friendly sentence." ) user_prompt = ( "Here are the inputs, formulas, and results for a cantilever beam calculation.\n" "Summarize whether the beam is OK in strength and deflection, with one short food analogy.\n\n" + structured_message ) formatted = _format_chat(system_prompt, user_prompt) out = _pipe(formatted, max_new_tokens=120, do_sample=True, temperature=0.4, return_full_text=False) return out[0]["generated_text"].split("\n")[0] # ---------- Gradio runner ---------- def run_once(L_m, P_kN, E_GPa, Sy_MPa, b_m, h_m, mode, a_m): try: P_N = float(P_kN) * 1e3 d = calc_cantilever_rect( float(L_m), P_N, float(E_GPa), float(Sy_MPa), float(b_m), float(h_m), mode, float(a_m) if a_m is not None else float(L_m) ) df = pd.DataFrame([{ "σ_max [MPa]": round(d["results"]["sigma_max_MPa"], 3), "FoS (yield) [-]": round(d["results"]["safety_factor_yield"], 3), "δ [mm]": round(d["results"]["delta_mm"], 3), "FoS (deflection) [-]": round(d["results"]["fos_deflection"], 3), "Strength Verdict": d["verdict"]["strength_message"], "Deflection Verdict": d["verdict"]["service_message"], }]) narrative = llm_explain(d["structured_message"]) return df, narrative, d["steps_markdown"], "" except Exception as e: return pd.DataFrame(), "", "", f"Input error:\n{e}" with gr.Blocks(title="Cantilever Rectangular Beam — Point Load") as demo: gr.Markdown("# Cantilever Rectangular Beam — Point Load (Free End or at Position a)") gr.Markdown(SCOPE_MD) with gr.Row(): with gr.Column(): gr.Markdown("### Load & Material") L_m = gr.Number(value=2.0, label="Beam length L [m]") P_kN = gr.Number(value=5.0, label="Point load P [kN]") E_GPa = gr.Number(value=200., label="Elastic modulus E [GPa]") Sy_MPa= gr.Number(value=250., label="Yield strength Sy [MPa]") with gr.Column(): gr.Markdown("### Rectangular Section") b_m = gr.Number(value=0.05, label="Width b [m]") h_m = gr.Number(value=0.10, label="Height h [m]") # Load position controls mode = gr.Radio( ["Free end (a = L)", "At position a"], value="Free end (a = L)", label="Load position mode" ) a_m = gr.Number(value=2.0, label="a [m] (distance from fixed end)", visible=False) def _toggle_a(selected): return gr.update(visible=(selected == "At position a")) mode.change(_toggle_a, inputs=[mode], outputs=[a_m]) run_btn = gr.Button("Compute") gr.Markdown("### Results") results_df = gr.Dataframe(label="Numerical results", interactive=False) gr.Markdown("### Explain the results") explain_md = gr.Markdown() gr.Markdown("### Show the math") steps_md = gr.Markdown() err_box = gr.Textbox(label="Errors", interactive=False) run_btn.click( fn=run_once, inputs=[L_m, P_kN, E_GPa, Sy_MPa, b_m, h_m, mode, a_m], outputs=[results_df, explain_md, steps_md, err_box] ) if __name__ == "__main__": demo.launch(debug=False)