File size: 10,050 Bytes
3ca1fe0
 
 
 
 
bddffd0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
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()