Anyuhhh's picture
Update app.py
41441b0 verified
"""
Homework 3 - Natural Language Explainer
Rebar Stress-Strain Calculator with Natural Language Explanation
Author: Anyu Huang
"""
import gradio as gr
import numpy as np
import json
from dataclasses import dataclass
from typing import Dict
import matplotlib.pyplot as plt
# ============================================================================
# REBAR SPECIFICATIONS AND CONSTANTS
# ============================================================================
@dataclass
class RebarGrade:
"""Standard rebar grades and their properties"""
name: str
yield_strength: float # MPa
ultimate_strength: float # MPa
elastic_modulus: float # GPa
# Common rebar grades (US standards only)
REBAR_GRADES = {
"Grade 40 (US)": RebarGrade("Grade 40", 280, 420, 200),
"Grade 60 (US)": RebarGrade("Grade 60", 420, 620, 200),
"Grade 75 (US)": RebarGrade("Grade 75", 520, 690, 200),
"Grade 80 (US)": RebarGrade("Grade 80", 550, 725, 200),
}
# Standard rebar diameters (mm)
REBAR_DIAMETERS = {
"#3 (10mm)": 9.525,
"#4 (13mm)": 12.7,
"#5 (16mm)": 15.875,
"#6 (19mm)": 19.05,
"#7 (22mm)": 22.225,
"#8 (25mm)": 25.4,
"#9 (29mm)": 28.65,
"#10 (32mm)": 32.26,
"#11 (36mm)": 35.81,
"#14 (43mm)": 43.00,
"#18 (57mm)": 57.33,
}
# ============================================================================
# CALCULATION ENGINE
# ============================================================================
def calculate_rebar_stress_strain(
diameter_mm: float,
applied_force_kN: float,
gauge_length_mm: float,
elongation_mm: float,
rebar_grade: str,
calculation_type: str
) -> Dict:
"""Calculate stress and strain for reinforcing steel."""
# Get rebar properties
grade = REBAR_GRADES[rebar_grade]
# Calculate cross-sectional area
radius_mm = diameter_mm / 2
area_mm2 = np.pi * radius_mm**2
area_m2 = area_mm2 / 1e6
# Convert force to Newtons
force_N = applied_force_kN * 1000
# Calculate stress
stress_Pa = force_N / area_m2
stress_MPa = stress_Pa / 1e6
# Calculate strain
strain = elongation_mm / gauge_length_mm
strain_percent = strain * 100
# Calculate elastic modulus from test
if strain > 0:
measured_modulus_GPa = (stress_MPa / strain) / 1000
else:
measured_modulus_GPa = 0
# Determine if yielding has occurred
is_yielding = stress_MPa > grade.yield_strength
is_failed = stress_MPa > grade.ultimate_strength
# Calculate safety factors
yield_safety_factor = grade.yield_strength / stress_MPa if stress_MPa > 0 else float('inf')
ultimate_safety_factor = grade.ultimate_strength / stress_MPa if stress_MPa > 0 else float('inf')
# Calculate theoretical elongation at yield
yield_strain = grade.yield_strength / (grade.elastic_modulus * 1000)
theoretical_elongation_at_yield = yield_strain * gauge_length_mm
# Determine material behavior
if calculation_type == "Elastic":
if is_yielding:
behavior = "WARNING: Stress exceeds yield strength! Plastic deformation occurring."
else:
behavior = "Material is in elastic range (reversible deformation)"
else:
if is_failed:
behavior = "FAILURE: Stress exceeds ultimate strength! Rebar will fracture."
elif is_yielding:
behavior = "Material is in plastic range (permanent deformation)"
else:
behavior = "Material is still elastic, no permanent deformation yet"
# Compile results
results = {
"diameter_mm": diameter_mm,
"applied_force_kN": applied_force_kN,
"gauge_length_mm": gauge_length_mm,
"elongation_mm": elongation_mm,
"rebar_grade": rebar_grade,
"area_mm2": area_mm2,
"stress_MPa": stress_MPa,
"strain": strain,
"strain_percent": strain_percent,
"measured_modulus_GPa": measured_modulus_GPa,
"yield_strength_MPa": grade.yield_strength,
"ultimate_strength_MPa": grade.ultimate_strength,
"elastic_modulus_GPa": grade.elastic_modulus,
"is_yielding": is_yielding,
"is_failed": is_failed,
"yield_safety_factor": yield_safety_factor,
"ultimate_safety_factor": ultimate_safety_factor,
"theoretical_elongation_at_yield_mm": theoretical_elongation_at_yield,
"behavior": behavior,
"calculation_type": calculation_type
}
return results
# ============================================================================
# VISUALIZATION
# ============================================================================
def create_stress_strain_curve(results: Dict) -> plt.Figure:
"""Create a stress-strain curve visualization."""
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
grade = REBAR_GRADES[results["rebar_grade"]]
# Create theoretical curve
strain_values = np.linspace(0, 0.02, 100)
stress_values = []
for e in strain_values:
if e <= grade.yield_strength / (grade.elastic_modulus * 1000):
stress = e * grade.elastic_modulus * 1000
else:
stress = grade.yield_strength + (e - grade.yield_strength/(grade.elastic_modulus*1000)) * 1000
stress_values.append(min(stress, grade.ultimate_strength))
ax1.plot(strain_values * 100, stress_values, 'b-', label='Theoretical Curve', linewidth=2)
ax1.axhline(y=grade.yield_strength, color='y', linestyle='--', label=f'Yield: {grade.yield_strength} MPa')
ax1.axhline(y=grade.ultimate_strength, color='r', linestyle='--', label=f'Ultimate: {grade.ultimate_strength} MPa')
ax1.plot(results["strain_percent"], results["stress_MPa"], 'ro', markersize=10, label='Current State')
ax1.set_xlabel('Strain (%)')
ax1.set_ylabel('Stress (MPa)')
ax1.set_title('Stress-Strain Curve')
ax1.grid(True, alpha=0.3)
ax1.legend()
ax1.set_xlim(0, max(2, results["strain_percent"] * 1.2))
ax1.set_ylim(0, grade.ultimate_strength * 1.2)
# Safety Factor Visualization
categories = ['Yield\nSafety Factor', 'Ultimate\nSafety Factor']
values = [
min(results["yield_safety_factor"], 5),
min(results["ultimate_safety_factor"], 5)
]
colors = ['green' if v > 1.5 else 'yellow' if v > 1.0 else 'red' for v in values]
bars = ax2.bar(categories, values, color=colors)
ax2.axhline(y=1.0, color='r', linestyle='--', label='Minimum (SF=1.0)')
ax2.axhline(y=1.5, color='y', linestyle='--', label='Recommended (SF=1.5)')
for bar, val in zip(bars, values):
height = bar.get_height()
ax2.text(bar.get_x() + bar.get_width()/2., height + 0.05,
f'{val:.2f}', ha='center', va='bottom')
ax2.set_ylabel('Safety Factor')
ax2.set_title('Safety Analysis')
ax2.set_ylim(0, 5)
ax2.legend()
ax2.grid(True, alpha=0.3)
plt.tight_layout()
return fig
# ============================================================================
# GRADIO INTERFACE
# ============================================================================
def process_calculation(
diameter_selection,
custom_diameter,
force,
gauge_length,
elongation,
grade,
calc_type
):
"""Main processing function for Gradio interface."""
# Get diameter value
if diameter_selection == "Custom":
diameter = custom_diameter
else:
diameter = REBAR_DIAMETERS[diameter_selection]
# Validate inputs
if diameter <= 0 or diameter > 100:
return "Error: Invalid diameter. Please enter a valid diameter between 0 and 100mm", None
if force < 0 or force > 1000:
return "Error: Invalid force. Please enter a force between 0 and 1000 kN", None
if gauge_length <= 0:
return "Error: Invalid gauge length. Gauge length must be positive", None
if elongation < 0:
return "Error: Invalid elongation. Elongation cannot be negative", None
# Perform calculation
results = calculate_rebar_stress_strain(
diameter_mm=diameter,
applied_force_kN=force,
gauge_length_mm=gauge_length,
elongation_mm=elongation,
rebar_grade=grade,
calculation_type=calc_type
)
# Format numerical results
numerical_output = f"""
## Numerical Results
### Input Parameters
- **Rebar**: {grade}, Diameter: {diameter:.2f}mm
- **Applied Force**: {force:.2f} kN
- **Gauge Length**: {gauge_length:.1f}mm
- **Elongation**: {elongation:.3f}mm
### Calculated Values
- **Cross-sectional Area**: {results['area_mm2']:.2f} mm²
- **Stress**: {results['stress_MPa']:.2f} MPa
- **Strain**: {results['strain_percent']:.4f}%
- **Measured Elastic Modulus**: {results['measured_modulus_GPa']:.1f} GPa
### Safety Factors
- **Yield Safety Factor**: {results['yield_safety_factor']:.2f}
- **Ultimate Safety Factor**: {results['ultimate_safety_factor']:.2f}
### Material State
{results['behavior']}
"""
# Create visualization
fig = create_stress_strain_curve(results)
return numerical_output, fig
# Create Gradio Interface
with gr.Blocks(title="Rebar Stress-Strain Calculator", theme=gr.themes.Soft()) as demo:
gr.Markdown("""
# Rebar Stress-Strain Calculator with Natural Language Explanation
## Homework 3 - Engineering Calculator with Natural Language Explainer
This tool calculates stress and strain for reinforcing steel bars (rebar) under tensile load,
then provides a natural language explanation of the results.
""")
with gr.Tab("Calculator"):
with gr.Row():
# Input Column
with gr.Column(scale=1):
gr.Markdown("### Rebar Specifications")
grade = gr.Dropdown(
choices=list(REBAR_GRADES.keys()),
value="Grade 60 (US)",
label="Rebar Grade",
info="Select steel grade"
)
diameter_selection = gr.Dropdown(
choices=list(REBAR_DIAMETERS.keys()) + ["Custom"],
value="#6 (19mm)",
label="Standard Diameter",
info="Select standard size or choose custom"
)
custom_diameter = gr.Number(
value=19.05,
label="Custom Diameter (mm)",
info="Used only if 'Custom' is selected above",
minimum=6,
maximum=60
)
gr.Markdown("### Test Parameters")
force = gr.Slider(
minimum=0,
maximum=500,
step=1,
value=100,
label="Applied Force (kN)",
info="Tensile force applied to rebar"
)
gauge_length = gr.Number(
value=200,
label="Gauge Length (mm)",
info="Original measurement length",
minimum=50,
maximum=1000
)
elongation = gr.Slider(
minimum=0,
maximum=10,
step=0.01,
value=0.4,
label="Measured Elongation (mm)",
info="Extension measured during test"
)
calc_type = gr.Radio(
choices=["Elastic", "Plastic"],
value="Elastic",
label="Calculation Type",
info="Type of analysis to perform"
)
calculate_btn = gr.Button(
"Calculate Stress & Strain",
variant="primary",
size="lg"
)
# Output Column
with gr.Column(scale=2):
gr.Markdown("### Results")
numerical_output = gr.Markdown()
gr.Markdown("### Visualization")
plot_output = gr.Plot()
# Examples
gr.Markdown("### Example Scenarios")
gr.Examples(
examples=[
["#6 (19mm)", 19.05, 50, 200, 0.1, "Grade 60 (US)", "Elastic"],
["#8 (25mm)", 25.4, 150, 200, 0.5, "Grade 75 (US)", "Elastic"],
["#10 (32mm)", 32.26, 300, 300, 2.0, "Grade 60 (US)", "Plastic"],
["#5 (16mm)", 15.875, 80, 200, 0.3, "Grade 40 (US)", "Elastic"],
],
inputs=[diameter_selection, custom_diameter, force, gauge_length, elongation, grade, calc_type],
outputs=[numerical_output, plot_output],
fn=process_calculation,
cache_examples=False
)
# Connect button
calculate_btn.click(
fn=process_calculation,
inputs=[diameter_selection, custom_diameter, force, gauge_length,
elongation, grade, calc_type],
outputs=[numerical_output, plot_output]
)
with gr.Tab("Documentation"):
gr.Markdown("""
### Engineering Background
**Stress** = F/A
- F: Applied force (N)
- A: Cross-sectional area (m²)
- Units: Pascal (Pa) or MPa
**Strain** = ΔL/L₀
- ΔL: Change in length (elongation)
- L₀: Original gauge length
- Units: Dimensionless (often expressed as %)
**Elastic Modulus (E)** = σ/ε
- Slope of stress-strain curve in elastic region
- For steel: approximately 200 GPa
### Rebar Grades (ASTM A615)
- Grade 40: Yield strength 280 MPa (40 ksi)
- Grade 60: Yield strength 420 MPa (60 ksi)
- Grade 75: Yield strength 520 MPa (75 ksi)
- Grade 80: Yield strength 550 MPa (80 ksi)
### Safety Factors
- Yield SF > 1.5: Safe for service loads
- Yield SF 1.0-1.5: Caution, limited margin
- Yield SF < 1.0: Yielding occurred, permanent deformation
### Valid Input Ranges
- Diameter: 6-60 mm (typical rebar sizes)
- Force: 0-500 kN (typical test range)
- Gauge Length: 50-1000 mm
- Elongation: 0-10 mm (up to 5% strain)
""")
# Launch the app
if __name__ == "__main__":
demo.launch()