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.')