|
|
import streamlit as st |
|
|
import pandas as pd |
|
|
import numpy as np |
|
|
import math |
|
|
from io import BytesIO |
|
|
|
|
|
st.set_page_config(page_title="UG-37 Nozzle Reinforcement Calculator", layout="wide") |
|
|
|
|
|
st.title("UG-37 Nozzle Reinforcement Calculator — Streamlit (Hugging Face)") |
|
|
st.caption("This app is a developer-ready skeleton: calculation hooks are provided.\nIMPORTANT: replace the placeholder formulas with the official UG-37 formulas from the ASME Code before using for design.") |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def to_si_length(val, unit): |
|
|
|
|
|
if unit == 'in': |
|
|
return val * 25.4 |
|
|
return val |
|
|
|
|
|
def to_si_pressure(val, unit): |
|
|
|
|
|
if unit == 'psi': |
|
|
return val * 0.00689476 |
|
|
return val |
|
|
|
|
|
|
|
|
|
|
|
def required_area_internal_demo(P, d, S, F=1.0): |
|
|
|
|
|
|
|
|
|
|
|
return F * (P * d) / S * 1000.0 |
|
|
|
|
|
|
|
|
def required_area_external_demo(P_ext, d, S, F=1.0): |
|
|
|
|
|
return F * (abs(P_ext) * d) / S * 1200.0 |
|
|
|
|
|
|
|
|
def calc_A1(Do, tn, d): |
|
|
|
|
|
|
|
|
|
|
|
ring_width = tn |
|
|
circumference = math.pi * Do |
|
|
return circumference * ring_width |
|
|
|
|
|
def calc_A2_pad(d, pad_OD, pad_t): |
|
|
|
|
|
if pad_OD is None: |
|
|
return 0.0 |
|
|
return math.pi * ( (pad_OD/2)**2 - (d/2)**2 ) * (pad_t / (pad_t + 1e-9)) |
|
|
|
|
|
def calc_A_welds(perimeter, leg, include_welds): |
|
|
if not include_welds: |
|
|
return 0.0 |
|
|
|
|
|
return perimeter * leg |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
with st.sidebar.form('settings'): |
|
|
st.header('Global settings') |
|
|
units = st.selectbox('Units system', ['SI (mm, MPa)', 'Imperial (in, psi)']) |
|
|
unit_len = 'mm' if units.startswith('SI') else 'in' |
|
|
unit_pres = 'MPa' if units.startswith('SI') else 'psi' |
|
|
include_welds = st.checkbox('Include weld areas in available reinforcement', value=False) |
|
|
f_mode = st.selectbox('F-factor mode', ['Figure UG-37 lookup (manual entry)', 'Force F = 1.0']) |
|
|
if f_mode.startswith('Figure'): |
|
|
F_val = st.number_input('F factor (enter value from Figure UG-37)', min_value=0.01, value=1.0, format='%.3f') |
|
|
else: |
|
|
F_val = 1.0 |
|
|
demo_mode = st.checkbox('Run demo (placeholder formulas) — DO NOT USE FOR DESIGN', value=True) |
|
|
st.form_submit_button('Apply') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.header('Inputs') |
|
|
col1, col2 = st.columns(2) |
|
|
with col1: |
|
|
st.subheader('Vessel / Shell') |
|
|
Do = st.number_input(f'Vessel diameter (Do) [{unit_len}]', value=1000.0) |
|
|
tn = st.number_input(f'Shell thickness (tn) [{unit_len}]', value=10.0) |
|
|
vessel_type = st.selectbox('Vessel type', ['cylinder', 'cone', 'sphere']) |
|
|
P_internal = st.number_input(f'Design internal pressure [{unit_pres}]', value=0.5) |
|
|
P_external = st.number_input(f'Design external pressure (use negative for vacuum) [{unit_pres}]', value=0.0) |
|
|
S_allow = st.number_input('Allowable stress S [MPa]', value=138.0) |
|
|
|
|
|
with col2: |
|
|
st.subheader('Nozzle / Opening') |
|
|
d = st.number_input(f'Nozzle inside diameter (d) [{unit_len}]', value=150.0) |
|
|
tn_nozzle = st.number_input(f'Nozzle neck thickness [{unit_len}]', value=6.0) |
|
|
projection = st.number_input(f'Projection (length) [{unit_len}]', value=30.0) |
|
|
pad_yes = st.checkbox('Reinforcing pad present', value=False) |
|
|
pad_OD = None |
|
|
pad_t = None |
|
|
pad_weld_leg = 0.0 |
|
|
if pad_yes: |
|
|
pad_OD = st.number_input(f'Pad OD [{unit_len}]', value=250.0) |
|
|
pad_t = st.number_input(f'Pad thickness [{unit_len}]', value=8.0) |
|
|
pad_weld_leg = st.number_input('Pad weld leg length (effective) [mm or in]', value=4.0) |
|
|
split_pad = st.checkbox('Split pad (apply 0.75 multiplier to A5)', value=False) |
|
|
orientation = st.selectbox('Location / orientation', ['axial (longitudinal)', 'circumferential', 'oblique']) |
|
|
orientation_angle = None |
|
|
if orientation == 'oblique': |
|
|
orientation_angle = st.number_input('Orientation angle (deg)', min_value=0.0, max_value=90.0, value=45.0) |
|
|
|
|
|
|
|
|
if units.startswith('Imperial'): |
|
|
Do_si = to_si_length(Do, 'in') |
|
|
d_si = to_si_length(d, 'in') |
|
|
tn_si = to_si_length(tn, 'in') |
|
|
tn_nozzle_si = to_si_length(tn_nozzle, 'in') |
|
|
pad_OD_si = to_si_length(pad_OD, 'in') if pad_OD is not None else None |
|
|
pad_t_si = to_si_length(pad_t, 'in') if pad_t is not None else None |
|
|
P_internal_si = to_si_pressure(P_internal, 'psi') |
|
|
P_external_si = to_si_pressure(P_external, 'psi') |
|
|
pad_weld_leg_si = to_si_length(pad_weld_leg, 'in') if pad_weld_leg is not None else 0.0 |
|
|
else: |
|
|
Do_si = Do |
|
|
d_si = d |
|
|
tn_si = tn |
|
|
tn_nozzle_si = tn_nozzle |
|
|
pad_OD_si = pad_OD |
|
|
pad_t_si = pad_t |
|
|
P_internal_si = P_internal |
|
|
P_external_si = P_external |
|
|
pad_weld_leg_si = pad_weld_leg |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.header('Calculations') |
|
|
if demo_mode: |
|
|
A_req_int = required_area_internal_demo(P_internal_si, d_si, S_allow, F=F_val) |
|
|
A_req_ext = required_area_external_demo(P_external_si, d_si, S_allow, F=F_val) if P_external_si != 0 else 0.0 |
|
|
else: |
|
|
A_req_int = None |
|
|
A_req_ext = None |
|
|
|
|
|
A1 = calc_A1(Do_si, tn_si, d_si) |
|
|
A2 = calc_A2_pad(d_si, pad_OD_si, pad_t_si) if pad_yes else 0.0 |
|
|
perimeter = math.pi * d_si |
|
|
A_weld = calc_A_welds(perimeter, pad_weld_leg_si, include_welds) |
|
|
|
|
|
|
|
|
A5 = A2 |
|
|
if split_pad: |
|
|
A5 = 0.75 * A5 |
|
|
|
|
|
A_avail = A1 + A2 + A_weld |
|
|
|
|
|
|
|
|
if A_req_ext and A_req_ext > A_req_int: |
|
|
A_req = A_req_ext |
|
|
governing = 'External pressure (UG-37(d))' |
|
|
else: |
|
|
A_req = A_req_int |
|
|
governing = 'Internal pressure (UG-37(c))' |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
st.subheader('Summary') |
|
|
colr1, colr2 = st.columns([1,2]) |
|
|
with colr1: |
|
|
if A_req is None: |
|
|
st.warning('Required-area formula not implemented. App is running in demo mode — results are NOT code-validated.') |
|
|
else: |
|
|
pass |
|
|
|
|
|
with colr2: |
|
|
st.markdown(f"**Governing case:** {governing}") |
|
|
st.markdown(f"**Required reinforcement area (A_req)**: {A_req:,.1f} mm²") |
|
|
st.markdown(f"**Available reinforcement area (A_avail)**: {A_avail:,.1f} mm²") |
|
|
|
|
|
margin = None |
|
|
if A_req and A_avail is not None: |
|
|
margin = A_avail - A_req |
|
|
pct = (margin / A_req * 100.0) if A_req != 0 else 0.0 |
|
|
if margin >= 0: |
|
|
st.success(f'PASS — Margin = {margin:,.1f} mm² ({pct:.1f}%)') |
|
|
else: |
|
|
st.error(f'FAIL — Shortfall = {abs(margin):,.1f} mm² ({pct:.1f}%)') |
|
|
|
|
|
|
|
|
st.subheader('Component breakdown (approximate / demo values)') |
|
|
comp = pd.DataFrame({ |
|
|
'Component':['A1 - Shell material', 'A2 - Pad ring', 'A_weld - Weld contribution', 'A5 - Pad effective'], |
|
|
'Area_mm2':[A1, A2, A_weld, A5] |
|
|
}) |
|
|
st.dataframe(comp) |
|
|
|
|
|
|
|
|
st.subheader('Export') |
|
|
buf = BytesIO() |
|
|
comp.to_csv(buf, index=False) |
|
|
buf.seek(0) |
|
|
st.download_button('Download components CSV', buf, file_name='ug37_components.csv', mime='text/csv') |
|
|
|
|
|
st.info('Reminder: This app is a skeleton/demo. Replace required-area functions with the official UG-37 formulas and verify against the ASME Code before using for design.') |
|
|
|