Spaces:
Sleeping
Sleeping
Upload folder using huggingface_hub
Browse files- README.md +31 -5
- app.py +191 -0
- requirements.txt +4 -0
README.md
CHANGED
|
@@ -1,12 +1,38 @@
|
|
| 1 |
---
|
| 2 |
-
title: Beam
|
| 3 |
-
|
| 4 |
-
colorFrom: green
|
| 5 |
colorTo: blue
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version:
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
---
|
| 11 |
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
+
title: Beam Analysis Calculator
|
| 3 |
+
colorFrom: gray
|
|
|
|
| 4 |
colorTo: blue
|
| 5 |
sdk: gradio
|
| 6 |
+
sdk_version: "4.31.5"
|
| 7 |
app_file: app.py
|
| 8 |
pinned: false
|
| 9 |
---
|
| 10 |
|
| 11 |
+
# Beam Analysis Calculator with Natural Language Explanations
|
| 12 |
+
|
| 13 |
+
This Space implements a deterministic engineering calculator for a simply-supported beam with a central point load.
|
| 14 |
+
It shows (i) numerical results and (ii) a natural-language explanation grounded in the computed values.
|
| 15 |
+
|
| 16 |
+
## Scope and assumptions
|
| 17 |
+
- Simply-supported beam, central point load
|
| 18 |
+
- Square cross-section, linear elastic material
|
| 19 |
+
- Strength check against yield; serviceability check against L/360 deflection
|
| 20 |
+
|
| 21 |
+
## Inputs and ranges
|
| 22 |
+
- Beam length L: 0.5–10.0 m
|
| 23 |
+
- Square side a: 0.01–0.5 m
|
| 24 |
+
- Load P: 0.1–100 kN
|
| 25 |
+
- Elastic modulus E: 50–300 GPa
|
| 26 |
+
- Yield strength Sy: 100–1000 MPa
|
| 27 |
+
|
| 28 |
+
## Outputs
|
| 29 |
+
- Max stress, max deflection, allowable deflection
|
| 30 |
+
- Factors of safety (yield, deflection)
|
| 31 |
+
- Pass/fail checks and overall verdict
|
| 32 |
+
|
| 33 |
+
## Explanation pipeline
|
| 34 |
+
A compact language model (SmolLM2-135M-Instruct) consumes the structured record from the calculator and produces a concise explanation.
|
| 35 |
+
The prompt enforces grounding and avoids introducing numbers beyond what was computed.
|
| 36 |
+
|
| 37 |
+
## Notes
|
| 38 |
+
This tool is for educational purposes only. For safety-critical design, consult a licensed professional engineer.
|
app.py
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import math
|
| 3 |
+
import gradio
|
| 4 |
+
import pandas as pd
|
| 5 |
+
from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline
|
| 6 |
+
|
| 7 |
+
# Model for explanations (small, CPU-friendly)
|
| 8 |
+
MODEL_ID = "HuggingFaceTB/SmolLM2-135M-Instruct"
|
| 9 |
+
|
| 10 |
+
# Load model and tokenizer once
|
| 11 |
+
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)
|
| 12 |
+
model = AutoModelForCausalLM.from_pretrained(MODEL_ID)
|
| 13 |
+
pipe = pipeline(task="text-generation", model=model, tokenizer=tokenizer)
|
| 14 |
+
|
| 15 |
+
def beam_calculation_engine(L_m: float, a_m: float, P_N: float, E_GPa: float, Sy_MPa: float) -> dict:
|
| 16 |
+
if any(val <= 0 for val in [L_m, a_m, P_N, E_GPa, Sy_MPa]):
|
| 17 |
+
raise ValueError("All input parameters must be positive values")
|
| 18 |
+
|
| 19 |
+
E_Pa = E_GPa * 1e9
|
| 20 |
+
Sy_Pa = Sy_MPa * 1e6
|
| 21 |
+
|
| 22 |
+
I = a_m**4 / 12.0
|
| 23 |
+
c = a_m / 2.0
|
| 24 |
+
|
| 25 |
+
M_max = P_N * L_m / 4.0
|
| 26 |
+
|
| 27 |
+
sigma_max_Pa = M_max * c / I
|
| 28 |
+
sigma_max_MPa = sigma_max_Pa / 1e6
|
| 29 |
+
|
| 30 |
+
delta_max_m = (P_N * L_m**3) / (48.0 * E_Pa * I)
|
| 31 |
+
delta_max_mm = delta_max_m * 1000
|
| 32 |
+
|
| 33 |
+
delta_allowable_m = L_m / 360.0
|
| 34 |
+
delta_allowable_mm = delta_allowable_m * 1000
|
| 35 |
+
|
| 36 |
+
stress_ratio = sigma_max_Pa / Sy_Pa
|
| 37 |
+
fos_yield = 1.0 / stress_ratio if stress_ratio > 0 else float("inf")
|
| 38 |
+
|
| 39 |
+
deflection_ratio = delta_max_m / delta_allowable_m
|
| 40 |
+
fos_deflection = 1.0 / deflection_ratio if deflection_ratio > 0 else float("inf")
|
| 41 |
+
|
| 42 |
+
passes_strength = sigma_max_Pa <= Sy_Pa
|
| 43 |
+
passes_serviceability = delta_max_m <= delta_allowable_m
|
| 44 |
+
overall_safe = passes_strength and passes_serviceability
|
| 45 |
+
|
| 46 |
+
calculation_summary = {
|
| 47 |
+
"beam_properties": {
|
| 48 |
+
"length_m": L_m,
|
| 49 |
+
"cross_section_side_m": a_m,
|
| 50 |
+
"load_N": P_N,
|
| 51 |
+
"elastic_modulus_GPa": E_GPa,
|
| 52 |
+
"yield_strength_MPa": Sy_MPa
|
| 53 |
+
},
|
| 54 |
+
"calculated_values": {
|
| 55 |
+
"maximum_stress_MPa": round(sigma_max_MPa, 3),
|
| 56 |
+
"maximum_deflection_mm": round(delta_max_mm, 3),
|
| 57 |
+
"allowable_deflection_mm": round(delta_allowable_mm, 3),
|
| 58 |
+
"factor_of_safety_yield": round(fos_yield, 3),
|
| 59 |
+
"factor_of_safety_deflection": round(fos_deflection, 3)
|
| 60 |
+
},
|
| 61 |
+
"engineering_verdict": {
|
| 62 |
+
"strength_check": "PASS" if passes_strength else "FAIL",
|
| 63 |
+
"serviceability_check": "PASS" if passes_serviceability else "FAIL",
|
| 64 |
+
"overall_safety": "SAFE" if overall_safe else "UNSAFE",
|
| 65 |
+
"strength_message": f"Stress ({sigma_max_MPa:.1f} MPa) {'≤' if passes_strength else '>'} Yield ({Sy_MPa} MPa)",
|
| 66 |
+
"deflection_message": f"Deflection ({delta_max_mm:.1f} mm) {'≤' if passes_serviceability else '>'} Allowable ({delta_allowable_mm:.1f} mm)"
|
| 67 |
+
},
|
| 68 |
+
"human_readable_summary": (
|
| 69 |
+
f"Beam Analysis Results:\n"
|
| 70 |
+
f"- Maximum bending stress: {sigma_max_MPa:.1f} MPa\n"
|
| 71 |
+
f"- Maximum deflection: {delta_max_mm:.1f} mm\n"
|
| 72 |
+
f"- Allowable deflection: {delta_allowable_mm:.1f} mm\n"
|
| 73 |
+
f"- Factor of safety (yield): {fos_yield:.2f}\n"
|
| 74 |
+
f"- Factor of safety (deflection): {fos_deflection:.2f}\n"
|
| 75 |
+
f"- Strength check: {'PASS' if passes_strength else 'FAIL'}\n"
|
| 76 |
+
f"- Serviceability check: {'PASS' if passes_serviceability else 'FAIL'}\n"
|
| 77 |
+
f"- Overall verdict: {'SAFE' if overall_safe else 'UNSAFE'}"
|
| 78 |
+
)
|
| 79 |
+
}
|
| 80 |
+
return calculation_summary
|
| 81 |
+
|
| 82 |
+
def format_llm_prompt(system_prompt: str, user_prompt: str) -> str:
|
| 83 |
+
messages = [
|
| 84 |
+
{"role": "system", "content": system_prompt},
|
| 85 |
+
{"role": "user", "content": user_prompt},
|
| 86 |
+
]
|
| 87 |
+
if hasattr(tokenizer, "chat_template") and tokenizer.chat_template:
|
| 88 |
+
return tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
| 89 |
+
return f"System: {system_prompt}\n\nUser: {user_prompt}\n\nAssistant:"
|
| 90 |
+
|
| 91 |
+
def generate_explanation(structured_results: dict) -> str:
|
| 92 |
+
props = structured_results["beam_properties"]
|
| 93 |
+
calc = structured_results["calculated_values"]
|
| 94 |
+
verdict = structured_results["engineering_verdict"]
|
| 95 |
+
|
| 96 |
+
system_prompt = (
|
| 97 |
+
"You are an engineering educator explaining structural analysis results. "
|
| 98 |
+
"Be accurate, concise, and ground the explanation only in provided numbers. "
|
| 99 |
+
"No new assumptions or invented figures."
|
| 100 |
+
)
|
| 101 |
+
user_prompt = (
|
| 102 |
+
f"Explain these beam results in simple terms:\n"
|
| 103 |
+
f"Beam length: {props['length_m']} m, square side: {props['cross_section_side_m']*1000:.1f} mm, "
|
| 104 |
+
f"load: {props['load_N']/1000:.1f} kN. Material: E={props['elastic_modulus_GPa']} GPa, Sy={props['yield_strength_MPa']} MPa.\n"
|
| 105 |
+
f"Stress={calc['maximum_stress_MPa']} MPa, deflection={calc['maximum_deflection_mm']} mm, "
|
| 106 |
+
f"allowable deflection={calc['allowable_deflection_mm']} mm. "
|
| 107 |
+
f"FoS(yield)={calc['factor_of_safety_yield']}, FoS(deflection)={calc['factor_of_safety_deflection']}.\n"
|
| 108 |
+
f"Verdict: Strength={verdict['strength_check']}, Serviceability={verdict['serviceability_check']}, Overall={verdict['overall_safety']}."
|
| 109 |
+
)
|
| 110 |
+
prompt = format_llm_prompt(system_prompt, user_prompt)
|
| 111 |
+
try:
|
| 112 |
+
# Deterministic generation: greedy decoding (no sampling)
|
| 113 |
+
out = pipe(prompt, max_new_tokens=150, do_sample=False, return_full_text=False)
|
| 114 |
+
explanation = out[0]["generated_text"].strip()
|
| 115 |
+
except Exception as e:
|
| 116 |
+
explanation = f"Explanation unavailable: {e}. Raw results:\n{structured_results['human_readable_summary']}"
|
| 117 |
+
return explanation
|
| 118 |
+
|
| 119 |
+
def run_beam_analysis(L_m, a_m, P_kN, E_GPa, Sy_MPa):
|
| 120 |
+
try:
|
| 121 |
+
L_m = float(L_m)
|
| 122 |
+
a_m = float(a_m)
|
| 123 |
+
P_kN = float(P_kN)
|
| 124 |
+
E_GPa = float(E_GPa)
|
| 125 |
+
Sy_MPa = float(Sy_MPa)
|
| 126 |
+
|
| 127 |
+
if not (0.5 <= L_m <= 10.0):
|
| 128 |
+
return None, None, "Error: Beam length must be between 0.5 and 10.0 meters"
|
| 129 |
+
if not (0.01 <= a_m <= 0.5):
|
| 130 |
+
return None, None, "Error: Cross-section side must be between 0.01 and 0.5 meters"
|
| 131 |
+
if not (0.1 <= P_kN <= 100.0):
|
| 132 |
+
return None, None, "Error: Load must be between 0.1 and 100.0 kN"
|
| 133 |
+
if not (50.0 <= E_GPa <= 300.0):
|
| 134 |
+
return None, None, "Error: Elastic modulus must be between 50 and 300 GPa"
|
| 135 |
+
if not (100.0 <= Sy_MPa <= 1000.0):
|
| 136 |
+
return None, None, "Error: Yield strength must be between 100 and 1000 MPa"
|
| 137 |
+
|
| 138 |
+
P_N = P_kN * 1000.0
|
| 139 |
+
results = beam_calculation_engine(L_m, a_m, P_N, E_GPa, Sy_MPa)
|
| 140 |
+
|
| 141 |
+
results_df = pd.DataFrame([
|
| 142 |
+
{"Parameter": "Maximum Stress", "Value": f"{results['calculated_values']['maximum_stress_MPa']} MPa", "Limit": f"{Sy_MPa} MPa", "Status": results['engineering_verdict']['strength_check']},
|
| 143 |
+
{"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"},
|
| 144 |
+
{"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']},
|
| 145 |
+
{"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"},
|
| 146 |
+
])
|
| 147 |
+
|
| 148 |
+
explanation = generate_explanation(results)
|
| 149 |
+
return results_df, results['human_readable_summary'], explanation
|
| 150 |
+
except Exception as e:
|
| 151 |
+
return None, None, f"Calculation error: {e}"
|
| 152 |
+
|
| 153 |
+
with gradio.Blocks(title="Beam Analysis Calculator") as demo:
|
| 154 |
+
gradio.Markdown("# Beam Analysis Calculator with Natural Language Explanations")
|
| 155 |
+
gradio.Markdown("Deterministic simply-supported beam analysis with a central point load. See the numbers and the plain-language explanation.")
|
| 156 |
+
|
| 157 |
+
with gradio.Row():
|
| 158 |
+
with gradio.Column(scale=1):
|
| 159 |
+
gradio.Markdown("## Inputs")
|
| 160 |
+
with gradio.Row():
|
| 161 |
+
L_m = gradio.Number(value=2.0, label="Beam Length (m)", minimum=0.5, maximum=10.0, info="Support-to-support length")
|
| 162 |
+
a_m = gradio.Number(value=0.05, label="Square Side (m)", minimum=0.01, maximum=0.5, info="Side length of square cross-section")
|
| 163 |
+
with gradio.Row():
|
| 164 |
+
P_kN = gradio.Number(value=5.0, label="Central Point Load (kN)", minimum=0.1, maximum=100.0, info="Load applied at midspan")
|
| 165 |
+
with gradio.Row():
|
| 166 |
+
E_GPa = gradio.Number(value=200.0, label="Elastic Modulus (GPa)", minimum=50.0, maximum=300.0, info="Young's modulus of material")
|
| 167 |
+
Sy_MPa = gradio.Number(value=250.0, label="Yield Strength (MPa)", minimum=100.0, maximum=1000.0, info="Material yield strength")
|
| 168 |
+
|
| 169 |
+
calculate_btn = gradio.Button("Run Analysis")
|
| 170 |
+
|
| 171 |
+
gradio.Markdown("### Example Cases")
|
| 172 |
+
gradio.Examples(
|
| 173 |
+
examples=[
|
| 174 |
+
[2.0, 0.05, 5.0, 200.0, 250.0],
|
| 175 |
+
[3.0, 0.08, 10.0, 70.0, 200.0],
|
| 176 |
+
[1.5, 0.03, 2.0, 210.0, 350.0],
|
| 177 |
+
],
|
| 178 |
+
inputs=[L_m, a_m, P_kN, E_GPa, Sy_MPa],
|
| 179 |
+
label="Click to load parameters"
|
| 180 |
+
)
|
| 181 |
+
|
| 182 |
+
with gradio.Column(scale=1):
|
| 183 |
+
gradio.Markdown("## Results")
|
| 184 |
+
results_table = gradio.Dataframe(headers=["Parameter", "Value", "Limit", "Status"], interactive=False, label="Calculation Results")
|
| 185 |
+
detailed_output = gradio.Textbox(label="Detailed Calculation Summary", interactive=False, lines=6)
|
| 186 |
+
explanation_output = gradio.Textbox(label="Natural Language Explanation", interactive=False, lines=6)
|
| 187 |
+
|
| 188 |
+
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])
|
| 189 |
+
|
| 190 |
+
if __name__ == "__main__":
|
| 191 |
+
demo.launch()
|
requirements.txt
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
gradio
|
| 2 |
+
transformers
|
| 3 |
+
torch
|
| 4 |
+
pandas
|