lehuaaa commited on
Commit
859fda5
ยท
verified ยท
1 Parent(s): ca740bf

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +1805 -34
src/streamlit_app.py CHANGED
@@ -1,40 +1,1811 @@
1
- import altair as alt
2
- import numpy as np
3
- import pandas as pd
4
  import streamlit as st
 
 
 
 
 
 
 
 
5
 
6
- """
7
- # Welcome to Streamlit!
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- Edit `/streamlit_app.py` to customize this app to your heart's desire :heart:.
10
- If you have any questions, checkout our [documentation](https://docs.streamlit.io) and [community
11
- forums](https://discuss.streamlit.io).
 
12
 
13
- In the meantime, below is an example of what you can do with just a few lines of code:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
15
 
16
- num_points = st.slider("Number of points in spiral", 1, 10000, 1100)
17
- num_turns = st.slider("Number of turns in spiral", 1, 300, 31)
18
-
19
- indices = np.linspace(0, 1, num_points)
20
- theta = 2 * np.pi * num_turns * indices
21
- radius = indices
22
-
23
- x = radius * np.cos(theta)
24
- y = radius * np.sin(theta)
25
-
26
- df = pd.DataFrame({
27
- "x": x,
28
- "y": y,
29
- "idx": indices,
30
- "rand": np.random.randn(num_points),
31
- })
32
-
33
- st.altair_chart(alt.Chart(df, height=700, width=700)
34
- .mark_point(filled=True)
35
- .encode(
36
- x=alt.X("x", axis=None),
37
- y=alt.Y("y", axis=None),
38
- color=alt.Color("idx", legend=None, scale=alt.Scale()),
39
- size=alt.Size("rand", legend=None, scale=alt.Scale(range=[1, 150])),
40
- ))
 
 
 
 
1
  import streamlit as st
2
+ import numpy as np
3
+ import matplotlib.pyplot as plt
4
+ from scipy.io import loadmat
5
+ from scipy.interpolate import PchipInterpolator, interp1d
6
+ import io
7
+ import time
8
+ import tempfile
9
+ import os
10
 
11
+ ###########################################################################
12
+ # Configure Streamlit page
13
+ st.set_page_config(
14
+ page_title="Bubble Dynamics Analysis",
15
+ page_icon="๐Ÿซง",
16
+ layout="wide",
17
+ initial_sidebar_state="expanded"
18
+ )
19
+
20
+ # Try importing TensorFlow
21
+ try:
22
+ import tensorflow as tf
23
+ from tensorflow import keras
24
+ from tensorflow.keras import layers
25
+
26
+ TENSORFLOW_AVAILABLE = True
27
+ except ImportError:
28
+ TENSORFLOW_AVAILABLE = False
29
+
30
+ # ULTRA-AGGRESSIVE CSS FIX - Complete stability with UT Austin background and Logo
31
+ st.markdown("""
32
+ <style>
33
+ /* BACKGROUND IMAGE STYLING */
34
+ .stApp {
35
+ background-image: url('data:image/jpeg;base64,BACKGROUND_IMAGE_BASE64_HERE');
36
+ background-size: cover;
37
+ background-position: center;
38
+ background-repeat: no-repeat;
39
+ background-attachment: fixed;
40
+ }
41
+
42
+ /* ALTERNATIVE: If you have the image in assets folder, use this instead:
43
+ .stApp {
44
+ background-image: url('./src/ut_yang_background.jpg');
45
+ background-size: cover;
46
+ background-position: center;
47
+ background-repeat: no-repeat;
48
+ background-attachment: fixed;
49
+ }
50
+ */
51
+
52
+ /* OVERLAY FOR BETTER TEXT READABILITY */
53
+ .stApp::before {
54
+ content: '';
55
+ position: fixed;
56
+ top: 0;
57
+ left: 0;
58
+ width: 100%;
59
+ height: 100%;
60
+ background: rgba(255, 255, 255, 0.85);
61
+ z-index: -1;
62
+ pointer-events: none;
63
+ }
64
+
65
+ /* FORCE HEADER TO BE COMPLETELY FIXED AND STABLE WITH LOGO */
66
+ .main-header {
67
+ position: fixed !important;
68
+ top: 0 !important;
69
+ left: 0 !important;
70
+ right: 0 !important;
71
+ width: 100% !important;
72
+ background: rgba(255, 255, 255, 0.95) !important;
73
+ backdrop-filter: blur(10px) !important;
74
+ z-index: 99999 !important;
75
+ padding: 1rem 2rem !important;
76
+ border-bottom: 3px solid #bf5700 !important;
77
+ box-shadow: 0 4px 8px rgba(0,0,0,0.15) !important;
78
+ transform: translateZ(0) !important;
79
+ will-change: auto !important;
80
+ display: flex !important;
81
+ align-items: center !important;
82
+ justify-content: center !important;
83
+ flex-direction: row !important;
84
+ gap: 20px !important;
85
+ }
86
+
87
+ /* LOGO STYLING */
88
+ .header-logo {
89
+ max-height: 70px !important;
90
+ max-width: 280px !important;
91
+ height: auto !important;
92
+ width: auto !important;
93
+ object-fit: contain !important;
94
+ border-radius: 8px !important;
95
+ box-shadow: 0 2px 8px rgba(0,0,0,0.1) !important;
96
+ flex-shrink: 0 !important;
97
+ }
98
+
99
+ /* HEADER TEXT STYLING */
100
+ .header-text {
101
+ font-size: 2.2rem !important;
102
+ color: #bf5700 !important; /* UT Austin burnt orange */
103
+ text-align: left !important;
104
+ margin: 0 !important;
105
+ font-weight: bold !important;
106
+ text-shadow: 1px 1px 2px rgba(0,0,0,0.1) !important;
107
+ white-space: nowrap !important;
108
+ }
109
+
110
+ /* ADD TOP MARGIN TO MAIN CONTENT TO AVOID OVERLAP - ADJUSTED FOR HORIZONTAL LOGO */
111
+ .main > .block-container {
112
+ margin-top: 120px !important; /* Reduced back to 120px for horizontal layout */
113
+ padding-top: 20px !important;
114
+ background: rgba(255, 255, 255, 0.9) !important;
115
+ border-radius: 10px !important;
116
+ box-shadow: 0 4px 12px rgba(0,0,0,0.1) !important;
117
+ backdrop-filter: blur(5px) !important;
118
+ }
119
+
120
+ /* RESPONSIVE DESIGN FOR MOBILE */
121
+ @media (max-width: 768px) {
122
+ .header-logo {
123
+ max-height: 50px !important;
124
+ max-width: 200px !important;
125
+ }
126
+
127
+ .header-text {
128
+ font-size: 1.2rem !important;
129
+ }
130
+
131
+ .main-header {
132
+ padding: 0.8rem 1rem !important;
133
+ gap: 15px !important;
134
+ }
135
+
136
+ .main > .block-container {
137
+ margin-top: 100px !important;
138
+ }
139
+ }
140
+
141
+ /* EXTRA SMALL MOBILE */
142
+ @media (max-width: 480px) {
143
+ .main-header {
144
+ flex-direction: column !important;
145
+ gap: 8px !important;
146
+ padding: 0.8rem 0.5rem !important;
147
+ }
148
+
149
+ .header-logo {
150
+ max-height: 40px !important;
151
+ max-width: 180px !important;
152
+ }
153
+
154
+ .header-text {
155
+ font-size: 1.1rem !important;
156
+ }
157
+
158
+ .main > .block-container {
159
+ margin-top: 110px !important;
160
+ }
161
+ }
162
+
163
+ /* UT AUSTIN THEMING */
164
+ .section-header {
165
+ font-size: 1.5rem;
166
+ color: #bf5700; /* UT Austin burnt orange */
167
+ margin-top: 2rem;
168
+ margin-bottom: 1rem;
169
+ font-weight: bold;
170
+ }
171
+
172
+ /* SIDEBAR STYLING WITH UT THEME */
173
+ .css-1d391kg {
174
+ background: rgba(255, 255, 255, 0.95) !important;
175
+ backdrop-filter: blur(10px) !important;
176
+ }
177
+
178
+ /* ENHANCED CONTAINERS WITH UT THEMING */
179
+ .metric-card {
180
+ background: linear-gradient(135deg, #bf5700, #d67700) !important;
181
+ color: white !important;
182
+ padding: 1rem;
183
+ border-radius: 0.5rem;
184
+ margin: 0.5rem 0;
185
+ }
186
+ .success-box {
187
+ background: linear-gradient(135deg, #28a745, #20c997) !important;
188
+ color: white !important;
189
+ border-left: 5px solid #155724;
190
+ padding: 1rem;
191
+ margin: 1rem 0;
192
+ border-radius: 8px;
193
+ }
194
+ .warning-box {
195
+ background: linear-gradient(135deg, #ffc107, #fd7e14) !important;
196
+ color: #212529 !important;
197
+ border-left: 5px solid #856404;
198
+ padding: 1rem;
199
+ margin: 1rem 0;
200
+ border-radius: 8px;
201
+ }
202
+
203
+ /* NUCLEAR OPTION: DISABLE ALL ANIMATIONS AND TRANSITIONS EVERYWHERE */
204
+ *, *::before, *::after {
205
+ transition: none !important;
206
+ animation: none !important;
207
+ animation-duration: 0s !important;
208
+ animation-delay: 0s !important;
209
+ transform: none !important;
210
+ }
211
+
212
+ /* FORCE STABILITY ON ALL STREAMLIT ELEMENTS */
213
+ .stProgress, .stProgress > div, .stProgress * {
214
+ transition: none !important;
215
+ animation: none !important;
216
+ transform: none !important;
217
+ }
218
+
219
+ .stSpinner, .stSpinner > div, .stSpinner * {
220
+ transition: none !important;
221
+ animation: none !important;
222
+ transform: none !important;
223
+ position: relative !important;
224
+ }
225
+
226
+ /* STABILIZE CONTAINERS */
227
+ .element-container, .stMarkdown, .stButton {
228
+ transition: none !important;
229
+ animation: none !important;
230
+ transform: none !important;
231
+ }
232
+
233
+ /* PREVENT LAYOUT SHIFTS */
234
+ .main .block-container .element-container {
235
+ transition: none !important;
236
+ animation: none !important;
237
+ }
238
+
239
+ /* FORCE GPU ACCELERATION FOR STABILITY */
240
+ .main-header {
241
+ transform: translate3d(0,0,0) !important;
242
+ backface-visibility: hidden !important;
243
+ perspective: 1000px !important;
244
+ }
245
+
246
+ /* HIDE STREAMLIT'S RERUN INDICATOR */
247
+ .stAppViewMain > .main > .block-container > div:first-child {
248
+ visibility: hidden !important;
249
+ height: 0 !important;
250
+ margin: 0 !important;
251
+ padding: 0 !important;
252
+ }
253
+ </style>
254
+ """, unsafe_allow_html=True)
255
+
256
+ # Initialize session state
257
+ if 'data_loaded' not in st.session_state:
258
+ st.session_state.data_loaded = False
259
+ if 'processed_data' not in st.session_state:
260
+ st.session_state.processed_data = False
261
+ if 'model_loaded' not in st.session_state:
262
+ st.session_state.model_loaded = False
263
+
264
+
265
+ # ULTRA-OPTIMIZED BubbleSimulation class - ZERO UI UPDATES during simulation
266
+ class OptimizedBubbleSimulation:
267
+ """
268
+ ULTRA-OPTIMIZED version - ZERO UI updates during simulation to prevent any trembling
269
+ """
270
+
271
+ def __init__(self):
272
+ self.setup_parameters()
273
+
274
+ def setup_parameters(self):
275
+ # Fixed parameters (same as original MATLAB)
276
+ self.R0 = 35e-6
277
+ self.P_inf = 101325
278
+ self.T_inf = 298.15
279
+ self.cav_type = 'LIC'
280
+
281
+ # Material parameters (same as original)
282
+ self.c_long = 1700
283
+ self.alpha = 0.0
284
+ self.rho = 1000
285
+ self.gamma = 0.0725
286
+
287
+ # Parameters for bubble contents (same as original)
288
+ self.D0 = 24.2e-6
289
+ self.kappa = 1.4
290
+ self.Ru = 8.3144598
291
+ self.Rv = self.Ru / (18.01528e-3)
292
+ self.Ra = self.Ru / (28.966e-3)
293
+ self.A = 5.28e-5
294
+ self.B = 1.17e-2
295
+ self.P_ref = 1.17e11
296
+ self.T_ref = 5200
297
+
298
+ # OPTIMIZED numerical parameters for speed
299
+ self.NT = 100 # Reduced from 500 to 100 (5x faster, still accurate)
300
+ self.RelTol = 1e-5 # Relaxed from 1e-7 to 1e-5 (faster convergence)
301
+
302
+ def run_optimized_simulation(self, G, mu, lambda_max_mean=None):
303
+ """ULTRA-OPTIMIZED simulation - ZERO UI updates to prevent trembling"""
304
+ from scipy.integrate import solve_ivp
305
+
306
+ # Set lambdamax from loaded data or use default
307
+ if lambda_max_mean is not None:
308
+ self.lambdamax = lambda_max_mean
309
+ print(f"Using lambda_max_mean = {self.lambdamax} from .mat file")
310
+ else:
311
+ self.lambdamax = 5.99 # Fallback default
312
+ print(f"Warning: Using default lambda_max = {self.lambdamax}")
313
+
314
+ print(f"Running ULTRA-OPTIMIZED simulation with predicted G={G:.2e} Pa, ฮผ={mu:.4f} Paยทs")
315
+
316
+ # Use predicted values
317
+ self.G = G
318
+ self.mu = mu
319
+
320
+ # Setup (same as original)
321
+ if self.cav_type == 'LIC':
322
+ self.Rmax = self.lambdamax * self.R0
323
+ self.PA = 0
324
+ self.omega = 0
325
+ self.delta = 0
326
+ self.n = 0
327
+
328
+ if self.cav_type == 'LIC':
329
+ self.Rc = self.Rmax
330
+ self.Uc = np.sqrt(self.P_inf / self.rho)
331
+ self.tc = self.Rmax / self.Uc
332
+ self.tspan = 3 * self.tc # Reduced for speed
333
+
334
+ # Calculate parameters (same as original)
335
+ self.Pv = self.P_ref * np.exp(-self.T_ref / self.T_inf)
336
+ self.K_inf = self.A * self.T_inf + self.B
337
+
338
+ # Non-dimensional variables (same as original)
339
+ self.C_star = self.c_long / self.Uc
340
+ self.We = self.P_inf * self.Rc / (2 * self.gamma)
341
+ self.Ca = self.P_inf / self.G
342
+ self.Re = self.P_inf * self.Rc / (self.mu * self.Uc)
343
+ self.fom = self.D0 / (self.Uc * self.Rc)
344
+ self.chi = self.T_inf * self.K_inf / (self.P_inf * self.Rc * self.Uc)
345
+ self.A_star = self.A * self.T_inf / self.K_inf
346
+ self.B_star = self.B / self.K_inf
347
+ self.Pv_star = self.Pv / self.P_inf
348
+
349
+ self.tspan_star = self.tspan / self.tc
350
+ self.Req = self.R0 / self.Rmax
351
+
352
+ self.PA_star = self.PA / self.P_inf
353
+ self.omega_star = self.omega * self.tc
354
+ self.delta_star = self.delta / self.tc
355
+
356
+ # Parameters vector (same as original)
357
+ self.params = [self.NT, self.C_star, self.We, self.Ca, self.alpha, self.Re,
358
+ self.Rv, self.Ra, self.kappa, self.fom, self.chi, self.A_star,
359
+ self.B_star, self.Pv_star, self.Req, self.PA_star,
360
+ self.omega_star, self.delta_star, self.n]
361
+
362
+ # Initial conditions (same as original)
363
+ R0_star = 1
364
+ U0_star = 0
365
+ Theta0 = np.zeros(self.NT)
366
+
367
+ if self.cav_type == 'LIC':
368
+ P0 = (self.Pv + (self.P_inf + 2 * self.gamma / self.R0 - self.Pv) *
369
+ ((self.R0 / self.Rmax) ** 3))
370
+ P0_star = P0 / self.P_inf
371
+ S0 = ((3 * self.alpha - 1) * (5 - 4 * self.Req - self.Req ** 4) / (2 * self.Ca) +
372
+ 2 * self.alpha * (27 / 40 + self.Req ** 8 / 8 + self.Req ** 5 / 5 +
373
+ self.Req ** 2 - 2 / self.Req) / self.Ca)
374
+ k0 = ((1 + (self.Rv / self.Ra) * (P0_star / self.Pv_star - 1)) ** (-1)) * np.ones(self.NT)
375
+
376
+ X0 = np.concatenate([[R0_star, U0_star, P0_star, S0], Theta0, k0])
377
+
378
+ print(f"State vector size: {len(X0)} (4 + {self.NT} + {self.NT})")
379
+ print(f"Time span: 0 to {self.tspan_star:.4f}")
380
+
381
+ # CRITICAL FIX: NO UI UPDATES AT ALL - store them for later
382
+ self.simulation_status = "Starting simulation..."
383
+
384
+ # ULTRA-OPTIMIZED ODE solving - NO UI updates during solving
385
+ try:
386
+ sol = solve_ivp(
387
+ self.bubble_optimized,
388
+ [0, self.tspan_star],
389
+ X0,
390
+ method='BDF',
391
+ rtol=self.RelTol,
392
+ atol=1e-8,
393
+ max_step=self.tspan_star / 200,
394
+ dense_output=False
395
+ )
396
+
397
+ self.simulation_status = "Processing results..."
398
+
399
+ except Exception as e:
400
+ print(f"BDF failed: {str(e)}, trying LSODA...")
401
+ self.simulation_status = "Trying backup solver..."
402
+ try:
403
+ sol = solve_ivp(
404
+ self.bubble_optimized,
405
+ [0, self.tspan_star],
406
+ X0,
407
+ method='LSODA',
408
+ rtol=1e-4,
409
+ atol=1e-7,
410
+ max_step=self.tspan_star / 100,
411
+ )
412
+ except Exception as e2:
413
+ print(f"All solvers failed: {str(e2)}")
414
+ return self.fast_fallback()
415
+
416
+ if not sol.success:
417
+ print(f"Solver failed: {sol.message}")
418
+ return self.fast_fallback()
419
+
420
+ # Extract solution
421
+ t_nondim = sol.t
422
+ X_nondim = sol.y.T
423
+ R_nondim = X_nondim[:, 0]
424
+
425
+ # Filter valid solutions
426
+ valid_mask = (R_nondim > 0.01) & (R_nondim < 20) & np.isfinite(R_nondim)
427
+ t_nondim = t_nondim[valid_mask]
428
+ R_nondim = R_nondim[valid_mask]
429
+
430
+ if len(t_nondim) < 10:
431
+ print("Too few valid points, using fast fallback")
432
+ return self.fast_fallback()
433
+
434
+ # Back to physical units
435
+ t = t_nondim * self.tc
436
+ R = R_nondim * self.Rc
437
+
438
+ # Change units
439
+ scale = 1e4
440
+ t_newunit = t * scale
441
+ R_newunit = R * scale
442
+
443
+ self.simulation_status = "Simulation complete!"
444
+
445
+ print(f"ULTRA-OPTIMIZED simulation completed in {len(t_newunit)} points!")
446
+ print(f"Time range: {t_newunit[0]:.3f} to {t_newunit[-1]:.3f} (0.1 ms)")
447
+ print(f"Radius range: {np.min(R_newunit):.3f} to {np.max(R_newunit):.3f} (0.1 mm)")
448
+
449
+ return t_newunit, R_newunit
450
+
451
+ def bubble_optimized(self, t, x):
452
+ """
453
+ OPTIMIZED bubble physics function - same physics, no UI updates
454
+ """
455
+ # Extract parameters (same as original)
456
+ NT = int(self.params[0])
457
+ C_star = self.params[1]
458
+ We = self.params[2]
459
+ Ca = self.params[3]
460
+ alpha = self.params[4]
461
+ Re = self.params[5]
462
+ Rv = self.params[6]
463
+ Ra = self.params[7]
464
+ kappa = self.params[8]
465
+ fom = self.params[9]
466
+ chi = self.params[10]
467
+ A_star = self.params[11]
468
+ B_star = self.params[12]
469
+ Pv_star = self.params[13]
470
+ Req = self.params[14]
471
+
472
+ # Extract state variables (same as original)
473
+ R = x[0]
474
+ U = x[1]
475
+ P = x[2]
476
+ S = x[3]
477
+ Theta = x[4:4 + NT]
478
+ k = x[4 + NT:4 + 2 * NT]
479
+
480
+ # Grid setup (same physics, fewer points)
481
+ deltaY = 1 / (NT - 1)
482
+ ii = np.arange(1, NT + 1)
483
+ yk = (ii - 1) * deltaY
484
+
485
+ # Boundary condition (same as original)
486
+ k = k.copy()
487
+ k[-1] = (1 + (Rv / Ra) * (P / Pv_star - 1)) ** (-1)
488
+
489
+ # Calculate mixture fields (same physics)
490
+ T = (A_star - 1 + np.sqrt(1 + 2 * A_star * Theta)) / A_star
491
+ K_star = A_star * T + B_star
492
+ Rmix = k * Rv + (1 - k) * Ra
493
+
494
+ # OPTIMIZATION: Vectorized spatial derivatives for speed
495
+ DTheta = np.zeros(NT)
496
+ DDTheta = np.zeros(NT)
497
+ Dk = np.zeros(NT)
498
+ DDk = np.zeros(NT)
499
+
500
+ # Neumann BC at origin
501
+ DTheta[0] = 0
502
+ Dk[0] = 0
503
+
504
+ # Vectorized central differences (faster than loops)
505
+ if NT >= 3:
506
+ DTheta[1:-1] = (Theta[2:] - Theta[:-2]) / (2 * deltaY)
507
+ Dk[1:-1] = (k[2:] - k[:-2]) / (2 * deltaY)
508
+
509
+ # Backward difference at wall
510
+ DTheta[-1] = (3 * Theta[-1] - 4 * Theta[-2] + Theta[-3]) / (2 * deltaY)
511
+ Dk[-1] = (3 * k[-1] - 4 * k[-2] + k[-3]) / (2 * deltaY)
512
+
513
+ # Laplacians (vectorized where possible)
514
+ DDTheta[0] = 6 * (Theta[1] - Theta[0]) / deltaY ** 2
515
+ DDk[0] = 6 * (k[1] - k[0]) / deltaY ** 2
516
+
517
+ if NT >= 3:
518
+ # Vectorized Laplacian calculation
519
+ for i in range(1, NT - 1):
520
+ DDTheta[i] = ((Theta[i + 1] - 2 * Theta[i] + Theta[i - 1]) / deltaY ** 2 +
521
+ (2 / yk[i]) * DTheta[i] if yk[i] > 1e-12 else
522
+ (Theta[i + 1] - 2 * Theta[i] + Theta[i - 1]) / deltaY ** 2)
523
+ DDk[i] = ((k[i + 1] - 2 * k[i] + k[i - 1]) / deltaY ** 2 +
524
+ (2 / yk[i]) * Dk[i] if yk[i] > 1e-12 else
525
+ (k[i + 1] - 2 * k[i] + k[i - 1]) / deltaY ** 2)
526
+
527
+ if NT >= 4:
528
+ DDTheta[-1] = ((2 * Theta[-1] - 5 * Theta[-2] + 4 * Theta[-3] - Theta[-4]) / deltaY ** 2 +
529
+ (2 / yk[-1]) * DTheta[-1] if yk[-1] > 1e-12 else
530
+ (2 * Theta[-1] - 5 * Theta[-2] + 4 * Theta[-3] - Theta[-4]) / deltaY ** 2)
531
+ DDk[-1] = ((2 * k[-1] - 5 * k[-2] + 4 * k[-3] - k[-4]) / deltaY ** 2 +
532
+ (2 / yk[-1]) * Dk[-1] if yk[-1] > 1e-12 else
533
+ (2 * k[-1] - 5 * k[-2] + 4 * k[-3] - k[-4]) / deltaY ** 2)
534
+
535
+ # Internal pressure evolution (same physics)
536
+ if Rmix[-1] > 1e-12 and (1 - k[-1]) > 1e-12 and R > 1e-12:
537
+ pdot = (3 / R * (-kappa * P * U + (kappa - 1) * chi * DTheta[-1] / R +
538
+ kappa * P * fom * Rv * Dk[-1] / (R * Rmix[-1] * (1 - k[-1]))))
539
+ else:
540
+ pdot = -3 * kappa * P * U / R if R > 1e-12 else 0
541
+
542
+ # OPTIMIZATION: Vectorized mixture velocity calculation
543
+ Umix = np.zeros(NT)
544
+ valid_indices = (Rmix > 1e-12) & (kappa * P > 1e-12)
545
+
546
+ if np.any(valid_indices):
547
+ idx = np.where(valid_indices)[0]
548
+ Umix[idx] = (((kappa - 1) * chi / R * DTheta[idx] - R * yk[idx] * pdot / 3) / (kappa * P) +
549
+ fom / R * (Rv - Ra) / Rmix[idx] * Dk[idx])
550
+
551
+ # Temperature evolution (vectorized where possible)
552
+ Theta_prime = np.zeros(NT)
553
+ valid_P = P > 1e-12
554
+ if valid_P:
555
+ for i in range(NT - 1): # Exclude wall point
556
+ if Rmix[i] > 1e-12:
557
+ Theta_prime[i] = (
558
+ (pdot + DDTheta[i] * chi / R ** 2) * (K_star[i] * T[i] / P * (kappa - 1) / kappa) -
559
+ DTheta[i] * (Umix[i] - yk[i] * U) / R +
560
+ fom / R ** 2 * (Rv - Ra) / Rmix[i] * Dk[i] * DTheta[i])
561
+ Theta_prime[-1] = 0 # Dirichlet BC
562
+
563
+ # Vapor concentration evolution (vectorized where possible)
564
+ k_prime = np.zeros(NT)
565
+ for i in range(NT - 1): # Exclude wall point
566
+ if Rmix[i] > 1e-12 and T[i] > 1e-12:
567
+ term1 = fom / R ** 2 * (DDk[i] + Dk[i] * (-((Rv - Ra) / Rmix[i]) * Dk[i] -
568
+ DTheta[i] / np.sqrt(1 + 2 * A_star * Theta[i]) / T[i]))
569
+ term2 = -(Umix[i] - U * yk[i]) / R * Dk[i]
570
+ k_prime[i] = term1 + term2
571
+ k_prime[-1] = 0 # Dirichlet BC
572
+
573
+ # Elastic stress evolution (same physics)
574
+ if self.cav_type == 'LIC':
575
+ if Req > 1e-12:
576
+ Rst = R / Req
577
+ if Rst > 1e-12:
578
+ Sdot = (2 * U / R * (3 * alpha - 1) * (1 / Rst + 1 / Rst ** 4) / Ca -
579
+ 2 * alpha * U / R * (1 / Rst ** 8 + 1 / Rst ** 5 + 2 / Rst ** 2 + 2 * Rst) / Ca)
580
+ else:
581
+ Sdot = 0
582
+ else:
583
+ Sdot = 0
584
+
585
+ # Keller-Miksis equations (same physics)
586
+ rdot = U
587
+
588
+ if R > 1e-12:
589
+ numerator = ((1 + U / C_star) * (P - 1 / (We * R) + S - 4 * U / (Re * R) - 1) +
590
+ R / C_star * (pdot + U / (We * R ** 2) + Sdot + 4 * U ** 2 / (Re * R ** 2)) -
591
+ (3 / 2) * (1 - U / (3 * C_star)) * U ** 2)
592
+ denominator = (1 - U / C_star) * R + 4 / (C_star * Re)
593
+
594
+ if abs(denominator) > 1e-12:
595
+ udot = numerator / denominator
596
+ else:
597
+ udot = 0
598
+ else:
599
+ udot = 0
600
+
601
+ # Return derivatives
602
+ dxdt = np.concatenate([[rdot, udot, pdot, Sdot], Theta_prime, k_prime])
603
+
604
+ return dxdt
605
+
606
+ def fast_fallback(self):
607
+ """Faster fallback for validation"""
608
+ print("Using fast analytical approximation for validation")
609
+
610
+ # Quick analytical approximation based on Rayleigh-Plesset equation
611
+ t_nondim = np.linspace(0, 3, 200) # Fewer points for speed
612
+
613
+ # Simple damped oscillation model using actual parameters
614
+ if hasattr(self, 'P_inf') and hasattr(self, 'rho') and hasattr(self, 'lambdamax'):
615
+ Rmax = self.lambdamax * self.R0 # Use dynamic lambda value
616
+ omega_natural = np.sqrt(3 * self.P_inf / (self.rho * Rmax ** 2))
617
+ damping = self.mu / (self.rho * Rmax ** 2) if hasattr(self, 'mu') else 0.1
618
+ else:
619
+ omega_natural = 1000
620
+ damping = 0.1
621
+
622
+ # Analytical solution approximation
623
+ omega_d = omega_natural * np.sqrt(1 - damping ** 2) if damping < 1 else omega_natural
624
+ decay = np.exp(-damping * omega_natural * t_nondim * self.tc)
625
+
626
+ R_nondim = self.Req + (1 - self.Req) * decay * np.cos(omega_d * t_nondim * self.tc)
627
+ R_nondim = np.maximum(R_nondim, 0.05) # Prevent negative values
628
+
629
+ # Convert to physical units
630
+ t = t_nondim * self.tc
631
+ R = R_nondim * self.Rc
632
+ scale = 1e4
633
+
634
+ return t * scale, R * scale
635
+
636
+
637
+ # Main Streamlit App
638
+ def main():
639
+ # Header - ULTRA-STABLE with fixed positioning and UT Austin branding + Logo
640
+ st.markdown("""
641
+ <div class="main-header">
642
+ <img src="https://huggingface.co/spaces/lehuaaa/Bubble/resolve/main/src/group_logo.JPG"
643
+ alt="YANG Research Group Logo"
644
+ class="header-logo">
645
+ <h1 class="header-text">Bubble Dynamics Analysis</h1>
646
+ </div>
647
+ """, unsafe_allow_html=True)
648
+
649
+ # Initialize current page in session state
650
+ if 'current_page' not in st.session_state:
651
+ st.session_state.current_page = "๐Ÿ  Home"
652
+
653
+ # Sidebar for navigation with clickable menu
654
+ st.sidebar.title("๐Ÿ“‹ Navigation")
655
+
656
+ # Create clickable menu buttons
657
+ menu_items = [
658
+ "๐Ÿ  Home",
659
+ "๐Ÿ“‚ Data Loading",
660
+ "โš™๏ธ Data Processing",
661
+ "๐Ÿค– ML Prediction",
662
+ "โœ… Validation",
663
+ "๐Ÿ“Š Results & Export"
664
+ ]
665
+
666
+ # Display menu buttons
667
+ for item in menu_items:
668
+ # Check if this is the current page to highlight it
669
+ if st.session_state.current_page == item:
670
+ # Use different styling for active page
671
+ if st.sidebar.button(f"โ–ถ๏ธ {item}", key=f"nav_{item}", use_container_width=True):
672
+ st.session_state.current_page = item
673
+ st.rerun()
674
+ else:
675
+ if st.sidebar.button(f" {item}", key=f"nav_{item}", use_container_width=True):
676
+ st.session_state.current_page = item
677
+ st.rerun()
678
+
679
+ # Add some spacing
680
+ st.sidebar.markdown("---")
681
+
682
+ # Show current status in sidebar
683
+ st.sidebar.markdown("### ๐Ÿ“Š Status")
684
+ if st.session_state.data_loaded:
685
+ st.sidebar.success("โœ… Data loaded")
686
+ else:
687
+ st.sidebar.info("๐Ÿ“‚ No data loaded")
688
+
689
+ if st.session_state.processed_data:
690
+ st.sidebar.success("โœ… Data processed")
691
+ else:
692
+ st.sidebar.info("โš™๏ธ Data not processed")
693
+
694
+ if st.session_state.model_loaded:
695
+ st.sidebar.success("โœ… Model loaded")
696
+ else:
697
+ st.sidebar.info("๐Ÿค– No model loaded")
698
+
699
+ # Display the selected page
700
+ page = st.session_state.current_page
701
+
702
+ if page == "๐Ÿ  Home":
703
+ show_home()
704
+ elif page == "๐Ÿ“‚ Data Loading":
705
+ show_data_loading()
706
+ elif page == "โš™๏ธ Data Processing":
707
+ show_data_processing()
708
+ elif page == "๐Ÿค– ML Prediction":
709
+ show_ml_prediction()
710
+ elif page == "โœ… Validation":
711
+ show_validation()
712
+ elif page == "๐Ÿ“Š Results & Export":
713
+ show_results()
714
+
715
+
716
+ def show_home():
717
+ """Home page with overview"""
718
+ col1, col2 = st.columns([2, 1])
719
+
720
+ with col1:
721
+ st.markdown("""
722
+ ### Welcome to the YANG Research Group Bubble Dynamics Analysis Platform
723
+
724
+ **The University of Texas at Austin - Aerospace Engineering and Engineering Mechanics**
725
+ **Cockrell School of Engineering**
726
+
727
+ This advanced web application provides comprehensive tools for analyzing bubble dynamics data:
728
+
729
+ **Features:**
730
+ - ๐Ÿ“‚ **Data Loading**: Upload and analyze .mat files containing bubble dynamics data
731
+ - โš™๏ธ **Data Processing**: Interpolate and process experimental data
732
+ - ๐Ÿค– **ML Prediction**: Use machine learning to predict material properties (G & ฮผ)
733
+ - โœ… **Validation**: Compare experimental vs simulated bubble behavior
734
+ - ๐Ÿ“Š **Export**: Download processed results and visualizations
735
+
736
+ **Getting Started:**
737
+ 1. Navigate to "Data Loading" to upload your .mat file
738
+ 2. Process your data in "Data Processing"
739
+ 3. Use ML models in "ML Prediction"
740
+ 4. Validate results in "Validation"
741
+ 5. Export your findings in "Results & Export"
742
+ """)
743
+
744
+ with col2:
745
+ if TENSORFLOW_AVAILABLE:
746
+ st.success("""
747
+ **โœ… System Status: Full Features Available**
748
+
749
+ โœ… Core Features: Ready
750
+ โœ… Data Processing: Ready
751
+ โœ… Visualization: Ready
752
+ โœ… ML Models: Ready
753
+ โœ… Simulations: Ready
754
+ """)
755
+ else:
756
+ st.warning("""
757
+ **โš ๏ธ System Status: Limited Features**
758
+
759
+ โœ… Core Features: Ready
760
+ โœ… Data Processing: Ready
761
+ โœ… Visualization: Ready
762
+ โŒ ML Models: TensorFlow not installed
763
+ โœ… Simulations: Ready
764
+
765
+ ๐Ÿ’ก Install TensorFlow to enable ML predictions:
766
+ `pip install tensorflow-cpu`
767
+ """)
768
+
769
+ # Show current session state
770
+ if st.session_state.data_loaded:
771
+ st.success("๐Ÿ“‚ Data loaded successfully")
772
+ if st.session_state.processed_data:
773
+ st.success("โš™๏ธ Data processed")
774
+ if st.session_state.model_loaded:
775
+ st.success("๐Ÿค– ML model loaded")
776
+
777
+
778
+ def show_data_loading():
779
+ """Data loading interface"""
780
+ st.markdown('<h2 class="section-header">๐Ÿ“‚ Data Loading</h2>', unsafe_allow_html=True)
781
+
782
+ uploaded_file = st.file_uploader(
783
+ "Upload your .mat file",
784
+ type=['mat'],
785
+ help="Upload a MATLAB .mat file containing 'R_nondim_All', 't_nondim_All', and 'lambda_max_mean'"
786
+ )
787
+
788
+ if uploaded_file is not None:
789
+ try:
790
+ # Save uploaded file temporarily
791
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.mat') as tmp_file:
792
+ tmp_file.write(uploaded_file.getvalue())
793
+ tmp_file_path = tmp_file.name
794
+
795
+ # Load the .mat file
796
+ data = loadmat(tmp_file_path)
797
+
798
+ # Check required variables
799
+ required_vars = ['R_nondim_All', 't_nondim_All']
800
+ missing_vars = [var for var in required_vars if var not in data]
801
+
802
+ if missing_vars:
803
+ st.error(f"Missing required variables: {missing_vars}")
804
+ return
805
+
806
+ # Extract data
807
+ R_nondim_all = data['R_nondim_All']
808
+ t_nondim_all = data['t_nondim_All']
809
+ num_datasets = R_nondim_all.shape[1]
810
+
811
+ # Extract lambda_max_mean
812
+ if 'lambda_max_mean' in data:
813
+ lambda_max_mean = float(data['lambda_max_mean'])
814
+ else:
815
+ st.warning("lambda_max_mean not found in file. Using default value 5.99")
816
+ lambda_max_mean = 5.99
817
+
818
+ # Store in session state
819
+ st.session_state.data = data
820
+ st.session_state.R_nondim_all = R_nondim_all
821
+ st.session_state.t_nondim_all = t_nondim_all
822
+ st.session_state.lambda_max_mean = lambda_max_mean
823
+ st.session_state.num_datasets = num_datasets
824
+ st.session_state.data_loaded = True
825
+
826
+ # Calculate physical parameters
827
+ R0_sim = 35e-6
828
+ P_inf_exp = 101325
829
+ rho_exp = 1000
830
+
831
+ Rmax_exp = lambda_max_mean * R0_sim
832
+ Rc_exp = Rmax_exp
833
+ Uc_exp = np.sqrt(P_inf_exp / rho_exp)
834
+ tc_exp = Rc_exp / Uc_exp
835
+
836
+ st.session_state.physical_params = {
837
+ 'Rmax_exp': Rmax_exp,
838
+ 'Rc_exp': Rc_exp,
839
+ 'Uc_exp': Uc_exp,
840
+ 'tc_exp': tc_exp
841
+ }
842
+
843
+ # Display success message
844
+ st.markdown(f"""
845
+ <div class="success-box">
846
+ <strong>โœ… Data loaded successfully!</strong><br>
847
+ ๐Ÿ“Š Datasets found: {num_datasets}<br>
848
+ ๐ŸŽฏ Lambda max: {lambda_max_mean:.3f}<br>
849
+ ๐Ÿ“ Physical parameters calculated
850
+ </div>
851
+ """, unsafe_allow_html=True)
852
+
853
+ # Show data preview
854
+ col1, col2 = st.columns(2)
855
+
856
+ with col1:
857
+ st.subheader("๐Ÿ“ˆ Data Overview")
858
+ st.write(f"**Number of datasets:** {num_datasets}")
859
+ st.write(f"**Lambda max mean:** {lambda_max_mean:.3f}")
860
+ st.write(f"**Data shape:** {R_nondim_all.shape}")
861
+
862
+ with col2:
863
+ st.subheader("๐Ÿ”ง Physical Parameters")
864
+ st.write(f"**R_max:** {Rmax_exp * 1e6:.1f} ฮผm")
865
+ st.write(f"**Time scale:** {tc_exp * 1e6:.1f} ฮผs")
866
+ st.write(f"**Velocity scale:** {Uc_exp:.1f} m/s")
867
+
868
+ # Clean up temporary file
869
+ os.unlink(tmp_file_path)
870
+
871
+ except Exception as e:
872
+ st.error(f"Error loading file: {str(e)}")
873
+
874
+
875
+ def show_data_processing():
876
+ """Data processing interface"""
877
+ st.markdown('<h2 class="section-header">โš™๏ธ Data Processing</h2>', unsafe_allow_html=True)
878
+
879
+ if not st.session_state.data_loaded:
880
+ st.warning("Please load data first in the 'Data Loading' section.")
881
+ return
882
+
883
+ # Dataset selection
884
+ dataset_idx = st.selectbox(
885
+ "Select dataset to process:",
886
+ range(st.session_state.num_datasets),
887
+ format_func=lambda x: f"Dataset {x + 1}"
888
+ )
889
+
890
+ # Processing parameters
891
+ col1, col2 = st.columns(2)
892
+ with col1:
893
+ interp_range = st.number_input(
894
+ "Interpolation Range",
895
+ min_value=0.1,
896
+ max_value=2.0,
897
+ value=0.8,
898
+ step=0.1,
899
+ help="Time range for interpolation"
900
+ )
901
+
902
+ with col2:
903
+ time_step = st.number_input(
904
+ "Time Step",
905
+ min_value=0.001,
906
+ max_value=0.1,
907
+ value=0.008,
908
+ step=0.001,
909
+ format="%.3f",
910
+ help="Time step for interpolation"
911
+ )
912
+
913
+ if st.button("๐Ÿ”„ Process Data", type="primary"):
914
+ with st.spinner("Processing data..."):
915
+ try:
916
+ # Extract data for selected dataset
917
+ R_nondim_exp = np.array(st.session_state.R_nondim_all[0, dataset_idx]).flatten()
918
+ t_nondim_exp = np.array(st.session_state.t_nondim_all[0, dataset_idx]).flatten()
919
+
920
+ # Find zero index
921
+ zero_candidates = np.where(np.abs(t_nondim_exp) < 1e-10)[0]
922
+ if len(zero_candidates) > 0:
923
+ zero_idx = zero_candidates[0]
924
+ else:
925
+ zero_idx = np.argmin(np.abs(t_nondim_exp))
926
+
927
+ # Convert to physical units
928
+ tc_exp = st.session_state.physical_params['tc_exp']
929
+ Rc_exp = st.session_state.physical_params['Rc_exp']
930
+
931
+ t_exp = t_nondim_exp * tc_exp
932
+ R_exp = R_nondim_exp * Rc_exp
933
+
934
+ # Process from zero point
935
+ t_fromzero = t_exp[zero_idx:].flatten()
936
+ R_frommax = R_exp[zero_idx:].flatten()
937
+
938
+ # Scale to new units
939
+ scale_exp = 1e4
940
+ t_newunit_exp = t_fromzero * scale_exp
941
+ R_newunit_exp = R_frommax * scale_exp
942
+
943
+ # Sort data
944
+ sort_indices = np.argsort(t_newunit_exp)
945
+ t_newunit_exp = t_newunit_exp[sort_indices]
946
+ R_newunit_exp = R_newunit_exp[sort_indices]
947
 
948
+ # Interpolate
949
+ t_interp_newunit = np.arange(0, interp_range + time_step, time_step)
950
+ pchip_interpolator = PchipInterpolator(t_newunit_exp, R_newunit_exp)
951
+ R_interp_newunit = pchip_interpolator(t_interp_newunit)
952
 
953
+ # Store results
954
+ st.session_state.t_interp_newunit = t_interp_newunit
955
+ st.session_state.R_interp_newunit = R_interp_newunit
956
+ st.session_state.t_original = t_newunit_exp
957
+ st.session_state.R_original = R_newunit_exp
958
+ st.session_state.processed_data = True
959
+ st.session_state.selected_dataset = dataset_idx
960
+
961
+ st.success(f"โœ… Data processed successfully! {len(R_interp_newunit)} interpolated points created.")
962
+
963
+ except Exception as e:
964
+ st.error(f"Processing failed: {str(e)}")
965
+
966
+ # Show results if data is processed
967
+ if st.session_state.processed_data:
968
+ st.subheader("๐Ÿ“Š Processing Results")
969
+
970
+ # Create plot
971
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5))
972
+
973
+ # Original vs interpolated
974
+ ax1.plot(st.session_state.t_original, st.session_state.R_original, 'b-', linewidth=2, label='Original Data')
975
+ ax1.plot(st.session_state.t_interp_newunit, st.session_state.R_interp_newunit, 'ro', markersize=3,
976
+ label='Interpolated Points')
977
+ ax1.set_xlabel('Time (0.1 ms)')
978
+ ax1.set_ylabel('Radius (0.1 mm)')
979
+ ax1.set_title('Original vs Interpolated Data')
980
+ ax1.grid(True, alpha=0.3)
981
+ ax1.legend()
982
+
983
+ # Interpolated data only
984
+ ax2.plot(st.session_state.t_interp_newunit, st.session_state.R_interp_newunit, 'ro', markersize=4)
985
+ ax2.set_xlabel('Time (0.1 ms)')
986
+ ax2.set_ylabel('Radius (0.1 mm)')
987
+ ax2.set_title('Interpolated R-t Curve')
988
+ ax2.grid(True, alpha=0.3)
989
+
990
+ plt.tight_layout()
991
+ st.pyplot(fig)
992
+
993
+ # Download processed data
994
+ if st.button("๐Ÿ’พ Download Processed Data"):
995
+ # Create download data
996
+ data_str = ' '.join([f'{val:.6f}' for val in st.session_state.R_interp_newunit[:-1]])
997
+ st.download_button(
998
+ label="๐Ÿ“ฅ Download as TXT",
999
+ data=data_str,
1000
+ file_name=f"interpolated_data_dataset_{dataset_idx + 1}.txt",
1001
+ mime="text/plain"
1002
+ )
1003
+
1004
+
1005
+ def show_ml_prediction():
1006
+ """ML prediction interface - MODIFIED FOR FILE UPLOAD"""
1007
+ st.markdown('<h2 class="section-header">๐Ÿค– ML Prediction</h2>', unsafe_allow_html=True)
1008
+
1009
+ if not TENSORFLOW_AVAILABLE:
1010
+ st.error("โŒ **TensorFlow not available.** ML prediction features are disabled.")
1011
+
1012
+ with st.expander("๐Ÿ”ง How to Enable ML Features", expanded=True):
1013
+ st.markdown("""
1014
+ **To enable ML predictions:**
1015
+
1016
+ 1. **Install TensorFlow:**
1017
+ ```bash
1018
+ pip install tensorflow-cpu # Recommended (smaller)
1019
+ # or
1020
+ pip install tensorflow # Full version
1021
+ ```
1022
+
1023
+ 2. **Restart the web app:**
1024
+ - Stop the app (Ctrl+C in terminal)
1025
+ - Run: `streamlit run streamlit_bubble_app.py`
1026
+ - Refresh your browser
1027
+ """)
1028
+ return
1029
+
1030
+ if not st.session_state.processed_data:
1031
+ st.warning("Please process data first in the 'Data Processing' section.")
1032
+ return
1033
+
1034
+ # Model file upload section (MODIFIED)
1035
+ st.subheader("๐Ÿ“ Upload ML Model")
1036
+
1037
+ col1, col2 = st.columns([2, 1])
1038
+
1039
+ with col1:
1040
+ st.markdown("**Upload your trained model files:**")
1041
+
1042
+ # Primary model file upload
1043
+ uploaded_model = st.file_uploader(
1044
+ "Upload model file (.h5, .keras, or .zip for SavedModel)",
1045
+ type=['h5', 'keras', 'zip'],
1046
+ help="Upload your trained model in H5, Keras, or ZIP format (for SavedModel)",
1047
+ key="model_file_upload"
1048
+ )
1049
+
1050
+ # Optional config file upload
1051
+ uploaded_config = st.file_uploader(
1052
+ "Upload model config (optional)",
1053
+ type=['npy'],
1054
+ help="Upload model_config.npy if available",
1055
+ key="config_file_upload"
1056
+ )
1057
+
1058
+ with col2:
1059
+ if st.button("๐Ÿ“– Model Format Help"):
1060
+ st.info("""
1061
+ **Supported model formats:**
1062
+
1063
+ **๐ŸŽฏ H5 Format (.h5)** - Recommended
1064
+ - Single file containing model architecture and weights
1065
+ - Most compatible format
1066
+
1067
+ **โšก Keras Format (.keras)**
1068
+ - Native Keras 3.0 format
1069
+ - Single file format
1070
+
1071
+ **๐Ÿ“ฆ SavedModel (.zip)**
1072
+ - Zip the entire SavedModel folder
1073
+ - Should contain: saved_model.pb, variables/, assets/
1074
+
1075
+ **โš™๏ธ Config File (.npy)** - Optional
1076
+ - Contains model configuration metadata
1077
+ - Helps with model information display
1078
+ """)
1079
+
1080
+ # Model loading with uploaded files (MODIFIED)
1081
+ if uploaded_model is not None and st.button("๐Ÿ”„ Load Uploaded Model", type="primary"):
1082
+ with st.spinner("Loading uploaded ML model..."):
1083
+ try:
1084
+ # Create temporary directory for uploaded files
1085
+ temp_dir = tempfile.mkdtemp()
1086
+ model_loaded = False
1087
+ loading_method = "Unknown"
1088
+ model = None
1089
+
1090
+ # Get file extension
1091
+ file_extension = uploaded_model.name.split('.')[-1].lower()
1092
+
1093
+ # Define custom layers exactly matching your training code
1094
+ class CustomMultiHeadAttention(layers.Layer):
1095
+ def __init__(self, embed_dim, num_heads=8, **kwargs):
1096
+ super(CustomMultiHeadAttention, self).__init__(**kwargs)
1097
+ self.embed_dim = embed_dim
1098
+ self.num_heads = num_heads
1099
+ self.projection_dim = embed_dim // num_heads
1100
+ self.query_dense = layers.Dense(embed_dim)
1101
+ self.key_dense = layers.Dense(embed_dim)
1102
+ self.value_dense = layers.Dense(embed_dim)
1103
+ self.combine_heads = layers.Dense(embed_dim)
1104
+
1105
+ def attention(self, query, key, value):
1106
+ score = tf.matmul(query, key, transpose_b=True)
1107
+ dim_key = tf.cast(tf.shape(key)[-1], tf.float32)
1108
+ scaled_score = score / tf.math.sqrt(dim_key)
1109
+ weights = tf.nn.softmax(scaled_score, axis=-1)
1110
+ output = tf.matmul(weights, value)
1111
+ return output, weights
1112
+
1113
+ def separate_heads(self, x, batch_size):
1114
+ x = tf.reshape(x, (batch_size, -1, self.num_heads, self.projection_dim))
1115
+ return tf.transpose(x, perm=[0, 2, 1, 3])
1116
+
1117
+ def call(self, inputs):
1118
+ batch_size = tf.shape(inputs)[0]
1119
+ query = self.query_dense(inputs)
1120
+ key = self.key_dense(inputs)
1121
+ value = self.value_dense(inputs)
1122
+ query = self.separate_heads(query, batch_size)
1123
+ key = self.separate_heads(key, batch_size)
1124
+ value = self.separate_heads(value, batch_size)
1125
+ attention, weights = self.attention(query, key, value)
1126
+ attention = tf.transpose(attention, perm=[0, 2, 1, 3])
1127
+ concat_attention = tf.reshape(attention, (batch_size, -1, self.embed_dim))
1128
+ output = self.combine_heads(concat_attention)
1129
+ return output
1130
+
1131
+ def get_config(self):
1132
+ config = super(CustomMultiHeadAttention, self).get_config()
1133
+ config.update({
1134
+ 'embed_dim': self.embed_dim,
1135
+ 'num_heads': self.num_heads,
1136
+ })
1137
+ return config
1138
+
1139
+ @classmethod
1140
+ def from_config(cls, config):
1141
+ return cls(**config)
1142
+
1143
+ class CustomTransformerEncoderLayer(layers.Layer):
1144
+ def __init__(self, embed_dim, num_heads, ff_dim, rate=0.1, **kwargs):
1145
+ super(CustomTransformerEncoderLayer, self).__init__(**kwargs)
1146
+ self.embed_dim = embed_dim
1147
+ self.num_heads = num_heads
1148
+ self.ff_dim = ff_dim
1149
+ self.rate = rate
1150
+ self.att = CustomMultiHeadAttention(embed_dim, num_heads)
1151
+ self.ffn = keras.Sequential(
1152
+ [layers.Dense(ff_dim, activation="softplus"), layers.Dense(embed_dim)])
1153
+ self.layernorm1 = layers.LayerNormalization(epsilon=1e-6)
1154
+ self.layernorm2 = layers.LayerNormalization(epsilon=1e-6)
1155
+ self.dropout1 = layers.Dropout(rate)
1156
+ self.dropout2 = layers.Dropout(rate)
1157
+
1158
+ def call(self, inputs, training):
1159
+ attn_output = self.att(inputs)
1160
+ attn_output = self.dropout1(attn_output, training=training)
1161
+ out1 = self.layernorm1(inputs + attn_output)
1162
+ ffn_output = self.ffn(out1)
1163
+ ffn_output = self.dropout2(ffn_output, training=training)
1164
+ return self.layernorm2(out1 + ffn_output)
1165
+
1166
+ def get_config(self):
1167
+ config = super(CustomTransformerEncoderLayer, self).get_config()
1168
+ config.update({
1169
+ 'embed_dim': self.embed_dim,
1170
+ 'num_heads': self.num_heads,
1171
+ 'ff_dim': self.ff_dim,
1172
+ 'rate': self.rate,
1173
+ })
1174
+ return config
1175
+
1176
+ @classmethod
1177
+ def from_config(cls, config):
1178
+ return cls(**config)
1179
+
1180
+ # Custom objects for model loading
1181
+ custom_objects = {
1182
+ 'CustomMultiHeadAttention': CustomMultiHeadAttention,
1183
+ 'CustomTransformerEncoderLayer': CustomTransformerEncoderLayer
1184
+ }
1185
+
1186
+ # Handle different file formats
1187
+ if file_extension == 'h5':
1188
+ # Handle H5 format
1189
+ model_path = os.path.join(temp_dir, uploaded_model.name)
1190
+ with open(model_path, 'wb') as f:
1191
+ f.write(uploaded_model.getvalue())
1192
+
1193
+ try:
1194
+ model = keras.models.load_model(model_path, custom_objects=custom_objects)
1195
+ loading_method = "H5 format (uploaded)"
1196
+ model_loaded = True
1197
+ st.success("โœ… Loaded H5 model successfully")
1198
+ except Exception as e:
1199
+ st.error(f"H5 loading failed: {str(e)}")
1200
+
1201
+ elif file_extension == 'keras':
1202
+ # Handle Keras format
1203
+ model_path = os.path.join(temp_dir, uploaded_model.name)
1204
+ with open(model_path, 'wb') as f:
1205
+ f.write(uploaded_model.getvalue())
1206
+
1207
+ try:
1208
+ model = keras.models.load_model(model_path, custom_objects=custom_objects)
1209
+ loading_method = "Keras format (uploaded)"
1210
+ model_loaded = True
1211
+ st.success("โœ… Loaded Keras model successfully")
1212
+ except Exception as e:
1213
+ st.error(f"Keras loading failed: {str(e)}")
1214
+
1215
+ elif file_extension == 'zip':
1216
+ # Handle SavedModel ZIP format
1217
+ import zipfile
1218
+
1219
+ zip_path = os.path.join(temp_dir, uploaded_model.name)
1220
+ with open(zip_path, 'wb') as f:
1221
+ f.write(uploaded_model.getvalue())
1222
+
1223
+ # Extract ZIP file
1224
+ extract_dir = os.path.join(temp_dir, 'extracted_model')
1225
+ with zipfile.ZipFile(zip_path, 'r') as zip_ref:
1226
+ zip_ref.extractall(extract_dir)
1227
+
1228
+ # Look for SavedModel directory
1229
+ savedmodel_dirs = []
1230
+ for root, dirs, files in os.walk(extract_dir):
1231
+ if 'saved_model.pb' in files:
1232
+ savedmodel_dirs.append(root)
1233
+
1234
+ if savedmodel_dirs:
1235
+ try:
1236
+ model = keras.models.load_model(savedmodel_dirs[0], custom_objects=custom_objects)
1237
+ loading_method = "SavedModel format (uploaded ZIP)"
1238
+ model_loaded = True
1239
+ st.success("โœ… Loaded SavedModel successfully")
1240
+ except Exception as e:
1241
+ # Try TFSMLayer as fallback
1242
+ try:
1243
+ model = layers.TFSMLayer(savedmodel_dirs[0], call_endpoint='serving_default')
1244
+ loading_method = "TFSMLayer (uploaded ZIP fallback)"
1245
+ model_loaded = True
1246
+ st.success("โœ… Loaded using TFSMLayer")
1247
+ except Exception as e2:
1248
+ st.error(f"SavedModel loading failed: {str(e)}, TFSMLayer failed: {str(e2)}")
1249
+ else:
1250
+ st.error("โŒ No SavedModel found in ZIP file. Ensure ZIP contains saved_model.pb")
1251
+
1252
+ if not model_loaded:
1253
+ st.error("โŒ Failed to load uploaded model")
1254
+
1255
+ # Show debug information
1256
+ st.subheader("๐Ÿ” Debug Information")
1257
+ st.write(f"**File name:** {uploaded_model.name}")
1258
+ st.write(f"**File size:** {len(uploaded_model.getvalue()):,} bytes")
1259
+ st.write(f"**File extension:** {file_extension}")
1260
+
1261
+ st.info("""
1262
+ **Troubleshooting:**
1263
+ - Ensure your model is saved in a compatible format
1264
+ - For H5: Use `model.save('model.h5', save_format='h5')`
1265
+ - For Keras: Use `model.save('model.keras')`
1266
+ - For SavedModel: ZIP the entire SavedModel folder
1267
+ - Verify custom layers are properly saved
1268
+ """)
1269
+ return
1270
+
1271
+ # Store in session state
1272
+ st.session_state.loaded_model = model
1273
+ st.session_state.model_name = uploaded_model.name
1274
+ st.session_state.model_loaded = True
1275
+ st.session_state.loading_method = loading_method
1276
+ st.session_state.temp_model_dir = temp_dir
1277
+
1278
+ # Load config file if provided
1279
+ model_config = None
1280
+ if uploaded_config is not None:
1281
+ try:
1282
+ config_path = os.path.join(temp_dir, uploaded_config.name)
1283
+ with open(config_path, 'wb') as f:
1284
+ f.write(uploaded_config.getvalue())
1285
+ model_config = np.load(config_path, allow_pickle=True).item()
1286
+ st.session_state.model_config = model_config
1287
+ st.success("โœ… Config file loaded successfully")
1288
+ except Exception as e:
1289
+ st.warning(f"Config file loading failed: {str(e)}")
1290
+
1291
+ # Display model info
1292
+ st.success(f"โœ… Model uploaded and loaded successfully!")
1293
+
1294
+ with st.expander("๐Ÿ“Š Model Information", expanded=False):
1295
+ col1, col2 = st.columns(2)
1296
+ with col1:
1297
+ st.write(f"**Model file:** {uploaded_model.name}")
1298
+ st.write(f"**Loading method:** {loading_method}")
1299
+ st.write(f"**Model type:** {type(model).__name__}")
1300
+ st.write(f"**File size:** {len(uploaded_model.getvalue()):,} bytes")
1301
+ with col2:
1302
+ try:
1303
+ if hasattr(model, 'count_params'):
1304
+ total_params = model.count_params()
1305
+ st.write(f"**Total parameters:** {total_params:,}")
1306
+ if hasattr(model, 'input_shape'):
1307
+ st.write(f"**Input shape:** {model.input_shape}")
1308
+ elif hasattr(model, 'input_spec'):
1309
+ st.write(f"**Input spec:** Available")
1310
+
1311
+ # Show config info if available
1312
+ if model_config:
1313
+ st.write(f"**Sequence length:** {model_config.get('sequence_length', 'Unknown')}")
1314
+ st.write(f"**Model type:** {model_config.get('model_type', 'Unknown')}")
1315
+
1316
+ except Exception as config_error:
1317
+ st.write("**Configuration:** Unable to read")
1318
+
1319
+ except Exception as e:
1320
+ st.error(f"โŒ Failed to load uploaded model: {str(e)}")
1321
+
1322
+ with st.expander("๐Ÿ” Error Details"):
1323
+ st.write(f"**Error type:** {type(e).__name__}")
1324
+ st.write(f"**Error message:** {str(e)}")
1325
+ st.write(f"**File name:** {uploaded_model.name if uploaded_model else 'None'}")
1326
+
1327
+ # Show current model status
1328
+ if st.session_state.model_loaded:
1329
+ method = st.session_state.get('loading_method', 'Unknown method')
1330
+ model_name = st.session_state.get('model_name', 'Unknown model')
1331
+ st.success(f"๐Ÿค– **Model Ready:** `{model_name}` ({method})")
1332
+
1333
+ # Input file selection and prediction (UNCHANGED)
1334
+ if st.session_state.model_loaded:
1335
+ st.subheader("๐Ÿ“ฅ Input Data")
1336
+
1337
+ col1, col2 = st.columns([2, 1])
1338
+
1339
+ # File uploader for input data
1340
+ uploaded_input = st.file_uploader(
1341
+ "Upload input data file",
1342
+ type=['txt'],
1343
+ help="Upload a text file with R-t curve data"
1344
+ )
1345
+
1346
+ # Use current data button
1347
+ if st.button("๐Ÿ“Š Use Current Processed Data"):
1348
+ if st.session_state.processed_data and 'R_interp_newunit' in st.session_state:
1349
+ temp_data = ' '.join([f'{val:.6f}' for val in st.session_state.R_interp_newunit[:-1]])
1350
+ st.session_state.temp_input_data = temp_data
1351
+ st.session_state.using_current_data = True
1352
+ st.success("โœ… Current interpolated data ready for prediction")
1353
+ else:
1354
+ st.error("No processed data available. Please process data first.")
1355
+
1356
+ # Prediction interface
1357
+ st.subheader("๐ŸŽฏ Make Predictions")
1358
+
1359
+ if st.button("๐Ÿš€ Predict G & ฮผ", type="primary"):
1360
+ # Determine input data source
1361
+ input_data = None
1362
+
1363
+ if st.session_state.get('using_current_data', False) and 'temp_input_data' in st.session_state:
1364
+ try:
1365
+ input_values = [float(x) for x in st.session_state.temp_input_data.split()]
1366
+ input_data = np.array(input_values)
1367
+ st.info("Using current processed data for prediction")
1368
+ except Exception as e:
1369
+ st.error(f"Error processing current data: {e}")
1370
+ return
1371
+
1372
+ elif uploaded_input is not None:
1373
+ try:
1374
+ input_data = np.loadtxt(io.StringIO(uploaded_input.getvalue().decode()))
1375
+ st.info("Using uploaded file for prediction")
1376
+ except Exception as e:
1377
+ st.error(f"Error reading uploaded file: {e}")
1378
+ return
1379
+ else:
1380
+ st.error("Please select input data: use current processed data or upload a file")
1381
+ return
1382
+
1383
+ # Run prediction (exactly matching your training code format) - UNCHANGED
1384
+ with st.spinner("Running ML prediction..."):
1385
+ try:
1386
+ # Process input data exactly as in training
1387
+ test_input_curves = input_data
1388
+
1389
+ if test_input_curves.ndim == 1:
1390
+ test_input_curves = test_input_curves.reshape(1, -1)
1391
+
1392
+ # Ensure input size is 100 (matching your training)
1393
+ if test_input_curves.shape[1] != 100:
1394
+ if test_input_curves.shape[1] > 100:
1395
+ test_input_curves = test_input_curves[:, :100]
1396
+ else:
1397
+ padding = np.zeros((test_input_curves.shape[0], 100 - test_input_curves.shape[1]))
1398
+ test_input_curves = np.concatenate([test_input_curves, padding], axis=1)
1399
+
1400
+ # Reshape for model input (matching training: sequence_length, 1)
1401
+ test_input_curves = test_input_curves.reshape(-1, 100, 1)
1402
+
1403
+ # Position inputs for transformer (exactly from training)
1404
+ position_inputs = np.arange(100)
1405
+ test_position_inputs = np.tile(position_inputs, (test_input_curves.shape[0], 1))
1406
+
1407
+ # Make prediction
1408
+ start_time = time.time()
1409
+
1410
+ # Handle different model types
1411
+ if st.session_state.loading_method == "TFSMLayer (uploaded ZIP fallback)":
1412
+ # For TFSMLayer, call directly
1413
+ predictions = st.session_state.loaded_model([test_input_curves, test_position_inputs])
1414
+ if isinstance(predictions, dict):
1415
+ # Extract from TFSMLayer output dictionary
1416
+ predictions_g = predictions.get('g_output',
1417
+ predictions.get('output_1', list(predictions.values())[0]))
1418
+ predictions_mu = predictions.get('mu_output',
1419
+ predictions.get('output_2', list(predictions.values())[1]))
1420
+ else:
1421
+ predictions_g, predictions_mu = predictions
1422
+ else:
1423
+ # Standard model prediction (matching training)
1424
+ predictions_g, predictions_mu = st.session_state.loaded_model.predict(
1425
+ [test_input_curves, test_position_inputs])
1426
+
1427
+ prediction_time = time.time() - start_time
1428
+
1429
+ # Process predictions (exactly from desktop GUI)
1430
+ num_samples = 1
1431
+ pred_G = predictions_g[:num_samples]
1432
+ pred_mu = predictions_mu[:num_samples]
1433
+
1434
+ # Apply scaling (exactly matching training scaling)
1435
+ pred_G_scaled = 10 ** (pred_G * (6 - 3) + 6 - 3)
1436
+ pred_mu_scaled = 10 ** (pred_mu * (0 + 3) - 3)
1437
+
1438
+ # Store results
1439
+ st.session_state.pred_G = pred_G_scaled
1440
+ st.session_state.pred_mu = pred_mu_scaled
1441
+
1442
+ # Extract values for display
1443
+ G_value = st.session_state.pred_G[0][0] if st.session_state.pred_G.ndim > 1 else \
1444
+ st.session_state.pred_G[0]
1445
+ mu_value = st.session_state.pred_mu[0][0] if st.session_state.pred_mu.ndim > 1 else \
1446
+ st.session_state.pred_mu[0]
1447
+
1448
+ # Display results
1449
+ col1, col2, col3 = st.columns(3)
1450
+
1451
+ with col1:
1452
+ st.metric(
1453
+ label="Shear Modulus (G)",
1454
+ value=f"{G_value:.2e} Pa",
1455
+ help="Predicted shear modulus of the material"
1456
+ )
1457
+
1458
+ with col2:
1459
+ st.metric(
1460
+ label="Viscosity (ฮผ)",
1461
+ value=f"{mu_value:.4f} Paยทs",
1462
+ help="Predicted viscosity of the material"
1463
+ )
1464
+
1465
+ with col3:
1466
+ st.metric(
1467
+ label="Prediction Time",
1468
+ value=f"{prediction_time:.3f} s",
1469
+ help="Time taken for ML inference"
1470
+ )
1471
+
1472
+ # Show detailed results
1473
+ result_text = f"G: {G_value:.2e} Pa, ฮผ: {mu_value:.4f}"
1474
+ st.success(f"๐ŸŽ‰ **Prediction Results:** {result_text}")
1475
+
1476
+ detailed_results = f"""**Prediction completed successfully!**
1477
+
1478
+ **Shear Modulus (G):** {G_value:.2e} Pa
1479
+ **Viscosity (ฮผ):** {mu_value:.4f} Paยทs
1480
+ **Prediction Time:** {prediction_time:.4f} seconds ({prediction_time * 1000:.2f} ms)
1481
+
1482
+ Ready for validation simulation!"""
1483
+
1484
+ st.info(detailed_results)
1485
+
1486
+ # Enable validation
1487
+ st.session_state.validation_ready = True
1488
+
1489
+ except Exception as e:
1490
+ st.error(f"โŒ Prediction failed: {str(e)}")
1491
+
1492
+ with st.expander("๐Ÿ” Debug Information"):
1493
+ st.write(f"**Error:** {str(e)}")
1494
+ st.write(f"**Model type:** {type(st.session_state.loaded_model).__name__}")
1495
+ st.write(f"**Loading method:** {st.session_state.get('loading_method', 'Unknown')}")
1496
+ if 'test_input_curves' in locals():
1497
+ st.write(f"**Input shape:** {test_input_curves.shape}")
1498
+ if 'test_position_inputs' in locals():
1499
+ st.write(f"**Position shape:** {test_position_inputs.shape}")
1500
+
1501
+ # Reset using_current_data flag when file is uploaded
1502
+ if uploaded_input is not None:
1503
+ st.session_state.using_current_data = False
1504
+
1505
+ # Cleanup temporary files when session ends
1506
+ if hasattr(st.session_state, 'temp_model_dir') and st.session_state.temp_model_dir:
1507
+ # Note: In a real deployment, you might want to implement proper cleanup
1508
+ # For now, the temporary directory will be cleaned up when the container restarts
1509
+ pass
1510
+
1511
+
1512
+ def show_validation():
1513
+ """ULTRA-STABLE Validation interface - ZERO trembling using session state control"""
1514
+ st.markdown('<h2 class="section-header">โœ… Validation</h2>', unsafe_allow_html=True)
1515
+
1516
+ if not st.session_state.processed_data:
1517
+ st.warning("Please process data first.")
1518
+ return
1519
+
1520
+ if not st.session_state.get('validation_ready', False) or 'pred_G' not in st.session_state or 'pred_mu' not in st.session_state:
1521
+ st.warning("Please run ML prediction first to get material properties for validation.")
1522
+ st.info("""
1523
+ **Validation Process:**
1524
+ 1. ๐Ÿ“‚ Load experimental data
1525
+ 2. โš™๏ธ Process and interpolate data
1526
+ 3. ๐Ÿค– Use ML model to predict G & ฮผ
1527
+ 4. โœ… **Run validation simulation** (you are here)
1528
+ 5. ๐Ÿ“Š Compare experimental vs simulated results
1529
+ """)
1530
+ return
1531
+
1532
+ st.subheader("๐Ÿ”ฌ IMR Simulation using predicted G and ฮผ vs Experimental R-t curve")
1533
+
1534
+ # REVOLUTIONARY FIX: Cache all static values in session state to prevent re-computation
1535
+ if 'validation_G_cached' not in st.session_state:
1536
+ st.session_state.validation_G_cached = st.session_state.pred_G[0][0] if st.session_state.pred_G.ndim > 1 else st.session_state.pred_G[0]
1537
+ st.session_state.validation_mu_cached = st.session_state.pred_mu[0][0] if st.session_state.pred_mu.ndim > 1 else st.session_state.pred_mu[0]
1538
+ st.session_state.validation_lambda_cached = st.session_state.lambda_max_mean
1539
+
1540
+ # ULTRA-STABLE LAYOUT: Single column layout to prevent trembling
1541
+ # Removed predicted values display to eliminate trembling
1542
+
1543
+ # ULTRA-CRITICAL FIX: Use session state to control button behavior
1544
+ if 'validation_button_clicked' not in st.session_state:
1545
+ st.session_state.validation_button_clicked = False
1546
+ if 'validation_running' not in st.session_state:
1547
+ st.session_state.validation_running = False
1548
+ if 'validation_complete' not in st.session_state:
1549
+ st.session_state.validation_complete = False
1550
+
1551
+ # STABLE BUTTON: Only show if not running
1552
+ if not st.session_state.validation_running:
1553
+ if st.button("๐Ÿš€ Run Validation Simulation", type="primary", key="validation_btn_stable"):
1554
+ st.session_state.validation_button_clicked = True
1555
+ st.session_state.validation_running = True
1556
+ st.session_state.validation_complete = False
1557
+ st.rerun()
1558
+
1559
+ # HANDLE SIMULATION EXECUTION
1560
+ if st.session_state.validation_running and not st.session_state.validation_complete:
1561
+ # Check required data
1562
+ if st.session_state.lambda_max_mean is None:
1563
+ st.error("No lambda_max_mean loaded. Please load data first.")
1564
+ st.session_state.validation_running = False
1565
+ return
1566
+
1567
+ # Show status in fixed container
1568
+ status_placeholder = st.empty()
1569
+ status_placeholder.info("๐Ÿ”„ Running simulation... Please wait")
1570
+
1571
+ try:
1572
+ # Use cached values - absolutely no re-computation
1573
+ G_value = st.session_state.validation_G_cached
1574
+ mu_value = st.session_state.validation_mu_cached
1575
+
1576
+ # Initialize and run simulation
1577
+ bubble_sim = OptimizedBubbleSimulation()
1578
+
1579
+ start_time = time.time()
1580
+ t_sim, R_sim = bubble_sim.run_optimized_simulation(
1581
+ G_value, mu_value, st.session_state.validation_lambda_cached
1582
+ )
1583
+ simulation_time = time.time() - start_time
1584
+
1585
+ # Store ALL results in session state
1586
+ st.session_state.validation_t_sim = t_sim
1587
+ st.session_state.validation_R_sim = R_sim
1588
+ st.session_state.validation_simulation_time = simulation_time
1589
+
1590
+ # Calculate and store error metrics
1591
+ rmse = mae = max_error = 0
1592
+ if len(t_sim) > 0 and len(st.session_state.t_interp_newunit) > 0:
1593
+ t_min = max(st.session_state.t_interp_newunit[0], t_sim[0])
1594
+ t_max = min(st.session_state.t_interp_newunit[-1], t_sim[-1])
1595
+
1596
+ if t_max > t_min:
1597
+ f_sim = interp1d(t_sim, R_sim, kind='linear', bounds_error=False, fill_value='extrapolate')
1598
+ mask = (st.session_state.t_interp_newunit >= t_min) & (st.session_state.t_interp_newunit <= t_max)
1599
+ t_common = st.session_state.t_interp_newunit[mask]
1600
+ R_exp_common = st.session_state.R_interp_newunit[mask]
1601
+ R_sim_common = f_sim(t_common)
1602
+
1603
+ if len(R_exp_common) > 0:
1604
+ rmse = np.sqrt(np.mean((R_exp_common - R_sim_common) ** 2))
1605
+ mae = np.mean(np.abs(R_exp_common - R_sim_common))
1606
+ max_error = np.max(np.abs(R_exp_common - R_sim_common))
1607
+
1608
+ # Store metrics in session state
1609
+ st.session_state.validation_rmse = rmse
1610
+ st.session_state.validation_mae = mae
1611
+ st.session_state.validation_max_error = max_error
1612
+
1613
+ # Clear status and mark complete
1614
+ status_placeholder.empty()
1615
+ st.session_state.validation_running = False
1616
+ st.session_state.validation_complete = True
1617
+
1618
+ # Rerun to show results
1619
+ st.rerun()
1620
+
1621
+ except Exception as e:
1622
+ st.session_state.validation_running = False
1623
+ st.session_state.validation_error = str(e)
1624
+ st.rerun()
1625
+
1626
+ # DISPLAY RESULTS (only if complete)
1627
+ if st.session_state.validation_complete and 'validation_t_sim' in st.session_state:
1628
+ # Create comparison plot
1629
+ fig, ax = plt.subplots(figsize=(10, 6))
1630
+
1631
+ ax.plot(st.session_state.t_interp_newunit, st.session_state.R_interp_newunit,
1632
+ 'ro', markersize=4, label='Interpolated (Experimental)', alpha=0.7)
1633
+ ax.plot(st.session_state.validation_t_sim, st.session_state.validation_R_sim,
1634
+ 'b-', linewidth=2, label='Simulated (Predicted G & ฮผ)')
1635
+
1636
+ ax.set_xlabel('Time (0.1 ms)')
1637
+ ax.set_ylabel('Radius (0.1 mm)')
1638
+ ax.set_title('Validation: Experimental vs Simulated R-t Curves')
1639
+ ax.grid(True, alpha=0.3)
1640
+ ax.legend()
1641
+
1642
+ # Removed error metrics from plot to prevent trembling
1643
+
1644
+ plt.tight_layout()
1645
+ st.pyplot(fig)
1646
+
1647
+ # Removed metrics display to eliminate trembling
1648
+
1649
+ # Results summary
1650
+ st.success("โœ… Validation simulation completed!")
1651
+
1652
+ st.info(f"""**Validation Results:**
1653
+
1654
+ **Predicted Values:**
1655
+ - Shear Modulus (G): {st.session_state.validation_G_cached:.2e} Pa
1656
+ - Viscosity (ฮผ): {st.session_state.validation_mu_cached:.4f} Paยทs
1657
+ - Lambda Max: {st.session_state.validation_lambda_cached:.3f}
1658
+
1659
+ **Performance:**
1660
+ - Simulation Time: {st.session_state.validation_simulation_time:.2f} seconds
1661
+ - Simulated Points: {len(st.session_state.validation_R_sim)}
1662
+ - Time Range: {st.session_state.validation_t_sim[0]:.3f} to {st.session_state.validation_t_sim[-1]:.3f} (0.1 ms)
1663
+ """)
1664
+
1665
+ # Reset button
1666
+ if st.button("๐Ÿ”„ Run New Simulation", key="reset_validation"):
1667
+ # Clear all validation session state
1668
+ for key in list(st.session_state.keys()):
1669
+ if key.startswith('validation_'):
1670
+ del st.session_state[key]
1671
+ st.rerun()
1672
+
1673
+ # Handle simulation error
1674
+ if hasattr(st.session_state, 'validation_error'):
1675
+ st.error(f"Simulation failed: {st.session_state.validation_error}")
1676
+ if st.button("๐Ÿ”„ Try Again", key="retry_validation"):
1677
+ del st.session_state.validation_error
1678
+ st.session_state.validation_running = False
1679
+ st.rerun()
1680
+
1681
+
1682
+ # Additional CSS to ensure zero movement
1683
+ st.markdown("""
1684
+ <style>
1685
+ /* ULTIMATE ANTI-TREMBLING CSS */
1686
+ .stButton > button {
1687
+ transition: none !important;
1688
+ animation: none !important;
1689
+ transform: none !important;
1690
+ }
1691
+
1692
+ .element-container {
1693
+ transition: none !important;
1694
+ animation: none !important;
1695
+ transform: none !important;
1696
+ position: relative !important;
1697
+ }
1698
+
1699
+ /* Force container stability */
1700
+ [data-testid="column"] {
1701
+ transition: none !important;
1702
+ animation: none !important;
1703
+ transform: none !important;
1704
+ }
1705
+
1706
+ /* Prevent any layout shifts */
1707
+ .main .block-container .element-container {
1708
+ will-change: auto !important;
1709
+ transform: translateZ(0) !important;
1710
+ backface-visibility: hidden !important;
1711
+ }
1712
+ </style>
1713
+ """, unsafe_allow_html=True)
1714
+
1715
+
1716
+ def show_results():
1717
+ """Results and export interface"""
1718
+ st.markdown('<h2 class="section-header">๐Ÿ“Š Results & Export</h2>', unsafe_allow_html=True)
1719
+
1720
+ if not st.session_state.processed_data:
1721
+ st.warning("No processed data available.")
1722
+ return
1723
+
1724
+ # Summary of all results
1725
+ st.subheader("๐Ÿ“‹ Analysis Summary")
1726
+
1727
+ col1, col2 = st.columns(2)
1728
+
1729
+ with col1:
1730
+ st.markdown("**Data Information:**")
1731
+ if st.session_state.data_loaded:
1732
+ st.write(f"โœ… Datasets loaded: {st.session_state.num_datasets}")
1733
+ st.write(f"โœ… Lambda max: {st.session_state.lambda_max_mean:.3f}")
1734
+ st.write(f"โœ… Selected dataset: {st.session_state.get('selected_dataset', 'N/A') + 1}")
1735
+
1736
+ if st.session_state.processed_data:
1737
+ st.write(f"โœ… Interpolated points: {len(st.session_state.R_interp_newunit)}")
1738
+
1739
+ with col2:
1740
+ st.markdown("**ML Predictions:**")
1741
+ if 'pred_G' in st.session_state:
1742
+ G_value = st.session_state.pred_G[0][0] if st.session_state.pred_G.ndim > 1 else st.session_state.pred_G[0]
1743
+ mu_value = st.session_state.pred_mu[0][0] if st.session_state.pred_mu.ndim > 1 else \
1744
+ st.session_state.pred_mu[0]
1745
+ st.write(f"๐ŸŽฏ Shear Modulus (G): {G_value:.2e} Pa")
1746
+ st.write(f"๐ŸŽฏ Viscosity (ฮผ): {mu_value:.4f} Paยทs")
1747
+ else:
1748
+ st.write("โŒ No predictions available")
1749
+
1750
+ # Export options
1751
+ st.subheader("๐Ÿ’พ Export Options")
1752
+
1753
+ export_col1, export_col2, export_col3 = st.columns(3)
1754
+
1755
+ with export_col1:
1756
+ if st.session_state.processed_data:
1757
+ # Export interpolated data
1758
+ data_str = ' '.join([f'{val:.6f}' for val in st.session_state.R_interp_newunit[:-1]])
1759
+ st.download_button(
1760
+ label="๐Ÿ“ฅ Download Interpolated Data",
1761
+ data=data_str,
1762
+ file_name="interpolated_bubble_data.txt",
1763
+ mime="text/plain"
1764
+ )
1765
+
1766
+ with export_col2:
1767
+ if 'pred_G' in st.session_state:
1768
+ # Export predictions
1769
+ G_value = st.session_state.pred_G[0][0] if st.session_state.pred_G.ndim > 1 else st.session_state.pred_G[0]
1770
+ mu_value = st.session_state.pred_mu[0][0] if st.session_state.pred_mu.ndim > 1 else \
1771
+ st.session_state.pred_mu[0]
1772
+ pred_summary = f"""Bubble Dynamics Analysis Results
1773
+
1774
+ Dataset: {st.session_state.get('selected_dataset', 'N/A') + 1}
1775
+ Lambda Max: {st.session_state.lambda_max_mean:.3f}
1776
+
1777
+ ML Predictions:
1778
+ Shear Modulus (G): {G_value:.2e} Pa
1779
+ Viscosity (ฮผ): {mu_value:.4f} Paยทs
1780
+
1781
+ Analysis completed on: {time.strftime('%Y-%m-%d %H:%M:%S')}
1782
  """
1783
+ st.download_button(
1784
+ label="๐Ÿ“‹ Download Results Summary",
1785
+ data=pred_summary,
1786
+ file_name="bubble_analysis_results.txt",
1787
+ mime="text/plain"
1788
+ )
1789
+
1790
+ with export_col3:
1791
+ if 'validation_t_sim' in st.session_state:
1792
+ # Export simulation data
1793
+ sim_data = np.column_stack([st.session_state.validation_t_sim, st.session_state.validation_R_sim])
1794
+ sim_str = '\n'.join([f'{t:.6f}\t{r:.6f}' for t, r in sim_data])
1795
+ st.download_button(
1796
+ label="๐Ÿ”ฌ Download Simulation Data",
1797
+ data=sim_str,
1798
+ file_name="simulation_results.txt",
1799
+ mime="text/plain"
1800
+ )
1801
+
1802
+ # Session reset
1803
+ st.subheader("๐Ÿ”„ Reset Session")
1804
+ if st.button("๐Ÿ—‘๏ธ Clear All Data", type="secondary"):
1805
+ for key in list(st.session_state.keys()):
1806
+ del st.session_state[key]
1807
+ st.success("โœ… Session cleared! Refresh the page to start over.")
1808
+
1809
 
1810
+ if __name__ == "__main__":
1811
+ main()