Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| # ============================================================================ | |
| 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() |