File size: 8,072 Bytes
7cc5ae2 |
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 197 198 199 200 201 202 203 204 205 206 |
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.')
|