Spaces:
Sleeping
Sleeping
| import os, shutil | |
| for cache_dir in ["/root/.cache/huggingface", "/root/.cache/torch", "/root/.cache/autogluon"]: | |
| if os.path.exists(cache_dir): | |
| shutil.rmtree(cache_dir, ignore_errors=True) | |
| import math | |
| import gradio | |
| import pandas as pd | |
| from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline | |
| # Model for explanations (small, CPU-friendly) | |
| MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct" | |
| # Load model and tokenizer once | |
| tokenizer = AutoTokenizer.from_pretrained(MODEL_ID) | |
| model = AutoModelForCausalLM.from_pretrained(MODEL_ID) | |
| pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer) | |
| def beam_calculation_engine(L_m: float, a_m: float, P_N: float, E_GPa: float, Sy_MPa: float) -> dict: | |
| if any(val <= 0 for val in [L_m, a_m, P_N, E_GPa, Sy_MPa]): | |
| raise ValueError("All input parameters must be positive values") | |
| E_Pa = E_GPa * 1e9 | |
| Sy_Pa = Sy_MPa * 1e6 | |
| I = a_m**4 / 12.0 | |
| c = a_m / 2.0 | |
| M_max = P_N * L_m / 4.0 | |
| sigma_max_Pa = M_max * c / I | |
| sigma_max_MPa = sigma_max_Pa / 1e6 | |
| delta_max_m = (P_N * L_m**3) / (48.0 * E_Pa * I) | |
| delta_max_mm = delta_max_m * 1000 | |
| delta_allowable_m = L_m / 360.0 | |
| delta_allowable_mm = delta_allowable_m * 1000 | |
| stress_ratio = sigma_max_Pa / Sy_Pa | |
| fos_yield = 1.0 / stress_ratio if stress_ratio > 0 else float("inf") | |
| deflection_ratio = delta_max_m / delta_allowable_m | |
| fos_deflection = 1.0 / deflection_ratio if deflection_ratio > 0 else float("inf") | |
| passes_strength = sigma_max_Pa <= Sy_Pa | |
| passes_serviceability = delta_max_m <= delta_allowable_m | |
| overall_safe = passes_strength and passes_serviceability | |
| calculation_summary = { | |
| "beam_properties": { | |
| "length_m": L_m, | |
| "cross_section_side_m": a_m, | |
| "load_N": P_N, | |
| "elastic_modulus_GPa": E_GPa, | |
| "yield_strength_MPa": Sy_MPa | |
| }, | |
| "calculated_values": { | |
| "maximum_stress_MPa": round(sigma_max_MPa, 3), | |
| "maximum_deflection_mm": round(delta_max_mm, 3), | |
| "allowable_deflection_mm": round(delta_allowable_mm, 3), | |
| "factor_of_safety_yield": round(fos_yield, 3), | |
| "factor_of_safety_deflection": round(fos_deflection, 3) | |
| }, | |
| "engineering_verdict": { | |
| "strength_check": "PASS" if passes_strength else "FAIL", | |
| "serviceability_check": "PASS" if passes_serviceability else "FAIL", | |
| "overall_safety": "SAFE" if overall_safe else "UNSAFE", | |
| "strength_message": f"Stress ({sigma_max_MPa:.1f} MPa) {'≤' if passes_strength else '>'} Yield ({Sy_MPa} MPa)", | |
| "deflection_message": f"Deflection ({delta_max_mm:.1f} mm) {'≤' if passes_serviceability else '>'} Allowable ({delta_allowable_mm:.1f} mm)" | |
| }, | |
| "human_readable_summary": ( | |
| f"Beam Analysis Results:\n" | |
| f"- Maximum bending stress: {sigma_max_MPa:.1f} MPa\n" | |
| f"- Maximum deflection: {delta_max_mm:.1f} mm\n" | |
| f"- Allowable deflection: {delta_allowable_mm:.1f} mm\n" | |
| f"- Factor of safety (yield): {fos_yield:.2f}\n" | |
| f"- Factor of safety (deflection): {fos_deflection:.2f}\n" | |
| f"- Strength check: {'PASS' if passes_strength else 'FAIL'}\n" | |
| f"- Serviceability check: {'PASS' if passes_serviceability else 'FAIL'}\n" | |
| f"- Overall verdict: {'SAFE' if overall_safe else 'UNSAFE'}" | |
| ) | |
| } | |
| return calculation_summary | |
| def format_llm_prompt(system_prompt: str, user_prompt: str) -> str: | |
| messages = [ | |
| {"role": "system", "content": system_prompt}, | |
| {"role": "user", "content": user_prompt}, | |
| ] | |
| if hasattr(tokenizer, "chat_template") and tokenizer.chat_template: | |
| return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True) | |
| return f"System: {system_prompt}\n\nUser: {user_prompt}\n\nAssistant:" | |
| def generate_explanation(structured_results: dict) -> str: | |
| props = structured_results["beam_properties"] | |
| calc = structured_results["calculated_values"] | |
| verdict = structured_results["engineering_verdict"] | |
| system_prompt = ( | |
| "You are an engineering educator explaining structural analysis results. " | |
| "Be accurate, concise, and ground the explanation only in provided numbers. " | |
| "No new assumptions or invented figures." | |
| ) | |
| user_prompt = ( | |
| f"Explain these beam results in simple terms:\n" | |
| f"Beam length: {props['length_m']} m, square side: {props['cross_section_side_m']*1000:.1f} mm, " | |
| f"load: {props['load_N']/1000:.1f} kN. Material: E={props['elastic_modulus_GPa']} GPa, Sy={props['yield_strength_MPa']} MPa.\n" | |
| f"Stress={calc['maximum_stress_MPa']} MPa, deflection={calc['maximum_deflection_mm']} mm, " | |
| f"allowable deflection={calc['allowable_deflection_mm']} mm. " | |
| f"FoS(yield)={calc['factor_of_safety_yield']}, FoS(deflection)={calc['factor_of_safety_deflection']}.\n" | |
| f"Verdict: Strength={verdict['strength_check']}, Serviceability={verdict['serviceability_check']}, Overall={verdict['overall_safety']}." | |
| ) | |
| prompt = format_llm_prompt(system_prompt, user_prompt) | |
| try: | |
| # Deterministic generation: greedy decoding (no sampling) | |
| out = pipe(prompt, max_new_tokens=150, do_sample=False, return_full_text=False) | |
| explanation = out[0]["generated_text"].strip() | |
| except Exception as e: | |
| explanation = f"Explanation unavailable: {e}. Raw results:\n{structured_results['human_readable_summary']}" | |
| return explanation | |
| def run_beam_analysis(L_m, a_m, P_kN, E_GPa, Sy_MPa): | |
| try: | |
| L_m = float(L_m) | |
| a_m = float(a_m) | |
| P_kN = float(P_kN) | |
| E_GPa = float(E_GPa) | |
| Sy_MPa = float(Sy_MPa) | |
| if not (0.5 <= L_m <= 10.0): | |
| return None, None, "Error: Beam length must be between 0.5 and 10.0 meters" | |
| if not (0.01 <= a_m <= 0.5): | |
| return None, None, "Error: Cross-section side must be between 0.01 and 0.5 meters" | |
| if not (0.1 <= P_kN <= 100.0): | |
| return None, None, "Error: Load must be between 0.1 and 100.0 kN" | |
| if not (50.0 <= E_GPa <= 300.0): | |
| return None, None, "Error: Elastic modulus must be between 50 and 300 GPa" | |
| if not (100.0 <= Sy_MPa <= 1000.0): | |
| return None, None, "Error: Yield strength must be between 100 and 1000 MPa" | |
| P_N = P_kN * 1000.0 | |
| results = beam_calculation_engine(L_m, a_m, P_N, E_GPa, Sy_MPa) | |
| results_df = pd.DataFrame([ | |
| {"Parameter": "Maximum Stress", "Value": f"{results['calculated_values']['maximum_stress_MPa']} MPa", "Limit": f"{Sy_MPa} MPa", "Status": results['engineering_verdict']['strength_check']}, | |
| {"Parameter": "Factor of Safety (Yield)", "Value": f"{results['calculated_values']['factor_of_safety_yield']}", "Limit": "> 1.0", "Status": "PASS" if results['calculated_values']['factor_of_safety_yield'] > 1.0 else "FAIL"}, | |
| {"Parameter": "Maximum Deflection", "Value": f"{results['calculated_values']['maximum_deflection_mm']} mm", "Limit": f"{results['calculated_values']['allowable_deflection_mm']} mm", "Status": results['engineering_verdict']['serviceability_check']}, | |
| {"Parameter": "Factor of Safety (Deflection)", "Value": f"{results['calculated_values']['factor_of_safety_deflection']}", "Limit": "> 1.0", "Status": "PASS" if results['calculated_values']['factor_of_safety_deflection'] > 1.0 else "FAIL"}, | |
| ]) | |
| explanation = generate_explanation(results) | |
| return results_df, results['human_readable_summary'], explanation | |
| except Exception as e: | |
| return None, None, f"Calculation error: {e}" | |
| with gradio.Blocks(title="Beam Analysis Calculator") as demo: | |
| gradio.Markdown("# Beam Analysis Calculator with Natural Language Explanations") | |
| gradio.Markdown("Deterministic simply-supported beam analysis with a central point load. See the numbers and the plain-language explanation.") | |
| with gradio.Row(): | |
| with gradio.Column(scale=1): | |
| gradio.Markdown("## Inputs") | |
| with gradio.Row(): | |
| L_m = gradio.Number(value=2.0, label="Beam Length (m)", minimum=0.5, maximum=10.0, info="Support-to-support length") | |
| a_m = gradio.Number(value=0.05, label="Square Side (m)", minimum=0.01, maximum=0.5, info="Side length of square cross-section") | |
| with gradio.Row(): | |
| P_kN = gradio.Number(value=5.0, label="Central Point Load (kN)", minimum=0.1, maximum=100.0, info="Load applied at midspan") | |
| with gradio.Row(): | |
| E_GPa = gradio.Number(value=200.0, label="Elastic Modulus (GPa)", minimum=50.0, maximum=300.0, info="Young's modulus of material") | |
| Sy_MPa = gradio.Number(value=250.0, label="Yield Strength (MPa)", minimum=100.0, maximum=1000.0, info="Material yield strength") | |
| calculate_btn = gradio.Button("Run Analysis") | |
| gradio.Markdown("### Example Cases") | |
| gradio.Examples( | |
| examples=[ | |
| [2.0, 0.05, 5.0, 200.0, 250.0], | |
| [3.0, 0.08, 10.0, 70.0, 200.0], | |
| [1.5, 0.03, 2.0, 210.0, 350.0], | |
| ], | |
| inputs=[L_m, a_m, P_kN, E_GPa, Sy_MPa], | |
| label="Click to load parameters" | |
| ) | |
| with gradio.Column(scale=1): | |
| gradio.Markdown("## Results") | |
| results_table = gradio.Dataframe(headers=["Parameter", "Value", "Limit", "Status"], interactive=False, label="Calculation Results") | |
| detailed_output = gradio.Textbox(label="Detailed Calculation Summary", interactive=False, lines=6) | |
| explanation_output = gradio.Textbox(label="Natural Language Explanation", interactive=False, lines=6) | |
| calculate_btn.click(fn=run_beam_analysis, inputs=[L_m, a_m, P_kN, E_GPa, Sy_MPa], outputs=[results_table, detailed_output, explanation_output]) | |
| if __name__ == "__main__": | |
| demo.launch() | |