UG-37 / app.py
Umar4321's picture
Create app.py
7cc5ae2 verified
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.")
# ---------------------------
# Helpers
# ---------------------------
def to_si_length(val, unit):
# convert inches to mm if needed
if unit == 'in':
return val * 25.4
return val
def to_si_pressure(val, unit):
# convert psi to MPa
if unit == 'psi':
return val * 0.00689476
return val
# Placeholder: conservative demo required-area formula for internal pressure
# *** DO NOT USE FOR DESIGN. Replace with UG-37(c) exact formula. ***
def required_area_internal_demo(P, d, S, F=1.0):
# very conservative demo: required area proportional to pressure * diameter / allowable stress
# Units: P in MPa, d in mm, S in MPa -> result in mm^2
# Formula (demo): A_req = F * (P * d) / S * 1000
return F * (P * d) / S * 1000.0
# Placeholder: demo external pressure formula
def required_area_external_demo(P_ext, d, S, F=1.0):
# external pressure often more critical; demo uses same shape
return F * (abs(P_ext) * d) / S * 1200.0
# Simple available-area calculators (geometric approximations)
def calc_A1(Do, tn, d):
# A1 — area of shell material around the opening (approx):
# approximate annulus area associated with thickness tn over an effective ring width = tn
# This is a placeholder conservative estimate: area = circumference * tn * tn
ring_width = tn
circumference = math.pi * Do
return circumference * ring_width
def calc_A2_pad(d, pad_OD, pad_t):
# A2 — pad area contribution (approx) = pad ring area (projected radial area)
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
# approximate weld throat area = perimeter * leg
return perimeter * leg
# ---------------------------
# Sidebar inputs
# ---------------------------
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')
# ---------------------------
# Main inputs
# ---------------------------
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)
# Unit conversions to internal SI mm/MPa for calculation convenience
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
# ---------------------------
# Calculations
# ---------------------------
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 placeholder: pad projection/tangential contribution
A5 = A2
if split_pad:
A5 = 0.75 * A5
A_avail = A1 + A2 + A_weld
# Governing required area
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))'
# ---------------------------
# Results
# ---------------------------
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}%)')
# Detailed table
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)
# Download results
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.')