File size: 7,321 Bytes
ecade39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
caab3f7
ecade39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
import math  # for infinity
import gradio  # UI
import pandas  # tables

from transformers import AutoTokenizer, AutoModelForCausalLM, pipeline  # tiny LLM

def vessel_calc(Do_m: float, t_m: float, L_m: float,
                Sy_MPa: float, E_GPa: float, nu: float) -> dict:
    """
    Simplified thin-walled closed-end cylinder.
    We compute maximum permissible internal pressure p_allow (yield, hoop governs).
    - Inputs: outer diameter Do, thickness t, length L, yield Sy, E, nu (all SI; Sy/E in MPa/GPa)
    - Early exit if thin-wall model not applicable.
    Returns a structured dict with metadata, inputs, validation, results, verdict.
    """

    # Precompute geometry checks
    Di_m = Do_m - 2.0 * t_m
    ratio = t_m / Do_m
    issues = []

    # Model applicability checks (hard stop)
    if Di_m <= 0.0:
        issues.append("Invalid geometry: inner diameter (Di) <= 0.")
    if ratio < 0.005:
        issues.append("Beyond model: wall too thin for this simplified thin-wall model (t/Do < 0.005).")
    if ratio > 0.10:
        issues.append("Beyond model: wall too thick for thin-wall theory (t/Do > 0.10).")

    # Prepare base envelope
    base = dict(
        metadata={"calc": "Thin-walled cylinder p_allow", "units": "SI", "version": "1.0.0"},
        inputs={"Do_m": Do_m, "t_m": t_m, "L_m": L_m, "Sy_MPa": Sy_MPa, "E_GPa": E_GPa, "nu": nu},
        validation={"ratio_t_over_Do": ratio, "Di_m": Di_m, "issues": issues},
    )

    if issues:
        return dict(
            **base,
            results={},
            verdict={"overall_ok": False, "message": " ; ".join(issues)}
        )

    # Allowable pressure (hoop reaches yield at p_allow)
    # sigma_theta = p * Di / (2 t) ; set sigma_theta = Sy  => p_allow = 2 t Sy / Di
    p_allow_MPa = (2.0 * t_m * Sy_MPa) / Di_m

    # Stresses at p_allow for transparency (not returned individually)
    sigma_theta_MPa = Sy_MPa
    sigma_long_MPa = p_allow_MPa * Di_m / (4.0 * t_m)

    # von Mises at p_allow (plane stress)
    sigma_vm_MPa = math.sqrt(
        sigma_theta_MPa**2 + sigma_long_MPa**2 - sigma_theta_MPa * sigma_long_MPa
    )

    # Optional elastic expansions (reported, but epsilons kept internal)
    E_MPa = E_GPa * 1e3  # convert GPa -> MPa
    eps_theta = (sigma_theta_MPa - nu * sigma_long_MPa) / E_MPa
    eps_long  = (sigma_long_MPa  - nu * sigma_theta_MPa) / E_MPa
    delta_d_mm = eps_theta * Do_m * 1e3
    delta_L_mm = eps_long  * L_m  * 1e3

    return dict(
        **base,
        results={
            "p_allow_MPa": p_allow_MPa,
            "sigma_vm_MPa_at_p_allow": sigma_vm_MPa,
            "delta_d_mm": delta_d_mm,
            "delta_L_mm": delta_L_mm
        },
        verdict={"overall_ok": True, "message": "Model valid; p_allow computed (hoop governs yield)."}
    )

def _format_chat(system_prompt: str, user_prompt: str) -> str:
    messages = [
        {"role": "system", "content": system_prompt},
        {"role": "user", "content": user_prompt},
    ]
    template = getattr(tokenizer, "chat_template", None)
    return tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )

def _llm_generate(prompt: str, max_tokens: int) -> str:
    out = pipe(
        prompt,
        max_new_tokens=max_tokens,
        do_sample=True,
        temperature=0.5,
        return_full_text=False,
    )
    return out[0]["generated_text"]

def llm_explain(d: dict) -> str:
    # If invalid, just echo the message
    if not d.get("verdict", {}).get("overall_ok", False):
        return f"Explanation: {d['verdict']['message']}"

    inp = d["inputs"]
    res = d["results"]
    val = d["validation"]

    system_prompt = (
        "You explain mechanics like a friendly TA. Be concise, clear, and stick to the provided data. Explain the data."
        "Use units and avoid speculation. 6-7 sentences max. Include Maximum internal pressure and some information about the cylinder input data."
    )

    user_prompt = (
        f"Cylinder (thin-wall, closed ends). Outer diameter Do={inp['Do_m']:.4g} m, thickness t={inp['t_m']:.4g} m. \n"
        f"length L={inp['L_m']:.4g} m, yield strength Sy={inp['Sy_MPa']:.4g} MPa, E={inp['E_GPa']:.4g} GPa, nu={inp['nu']:.3g}.\n"
        f"Results:\n"
        f"The maximum permissible internal pressure is {res['p_allow_MPa']:.4g} MPa.\n"
        f"von Mises at p_allow={res['sigma_vm_MPa_at_p_allow']:.4g} MPa; "
        f"diameter growth Δd={res['delta_d_mm']:.4g} mm; length growth ΔL={res['delta_L_mm']:.4g} mm.\n"
        f"State whether the design is within the model assumptions and what p_allow means."
    )

    formatted = _format_chat(system_prompt, user_prompt)
    return _llm_generate(formatted, max_tokens=160)

# ---------------------------
# Glue: one run for the UI
# ---------------------------
def run_once(Do_m, t_m, L_m, Sy_MPa, E_GPa, nu):
    d = vessel_calc(
        Do_m=float(Do_m),
        t_m=float(t_m),
        L_m=float(L_m),
        Sy_MPa=float(Sy_MPa),
        E_GPa=float(E_GPa),
        nu=float(nu),
    )

    if not d["verdict"]["overall_ok"]:
        df = pandas.DataFrame([{"Message": d["verdict"]["message"]}])
        narrative = d["verdict"]["message"]
        return df, narrative

    r = d["results"]
    df = pandas.DataFrame([{
        "Allowed  Pressure [MPa]": round(r["p_allow_MPa"], 4),
        "σ_vm at Allowed Pressure [MPa]": round(r["sigma_vm_MPa_at_p_allow"], 4),
        "Δd [mm]": round(r["delta_d_mm"], 4),
        "ΔL [mm]": round(r["delta_L_mm"], 4),
    }])

    narrative = llm_explain(d).split("\n")[0]
    return df, narrative

with gradio.Blocks() as demo:
    gradio.Markdown("# Allowable Pressure for Thin-Walled Cylinder (Simplified)")
    gradio.Markdown(
        "Computes the **maximum permissible internal pressure** for a thin-walled cylinder using hoop-stress yield. "
        "Stops early if the thin-wall model is not applicable."
    )

    with gradio.Row():
        Do_m   = gradio.Number(value=1.0,   label="Outer diameter Do [m]")
        t_m    = gradio.Number(value=0.01,  label="Wall thickness t [m]")
        L_m    = gradio.Number(value=3.0,   label="Length L [m] (for ΔL only)")
    with gradio.Row():
        Sy_MPa = gradio.Number(value=250.0, label="Yield strength Sy [MPa]")
        E_GPa  = gradio.Number(value=210.0, label="Elastic modulus E [GPa]")
        nu     = gradio.Number(value=0.30,  label="Poisson's ratio ν [-]")

    run_btn = gradio.Button("Compute")

    results_df = gradio.Dataframe(label="Numerical results (deterministic)", interactive=False)
    explain_md = gradio.Markdown(label="Explanation")

    run_btn.click(
        fn=run_once,
        inputs=[Do_m, t_m, L_m, Sy_MPa, E_GPa, nu],
        outputs=[results_df, explain_md]
    )

    gradio.Examples(
        examples=[
            # Valid thin-wall
            [1.0, 0.01, 3.0, 250.0, 210.0, 0.30],
            # Too thin (t/Do < 0.005) -> early stop
            [0.8, 0.002, 2.5, 350.0, 200.0, 0.30],
            # Too thick (t/Do > 0.10) -> early stop
            [0.5, 0.06, 2.0, 300.0, 200.0, 0.30],
        ],
        inputs=[Do_m, t_m, L_m, Sy_MPa, E_GPa, nu],
        label="Representative cases",
        examples_per_page=3,
        cache_examples=False,
    )

if __name__ == "__main__":
    demo.launch(debug=True)