hasmalee/aero
0
1import requests
2import streamlit as st
3import base64
4import streamlit as st
5from pathlib import Path
6
7
8st.set_page_config(page_title="AeroPINN-X", layout="wide")
9
10# ---- sidebar controls ----
11BACKEND = st.sidebar.text_input("Backend URL", "http://127.0.0.1:8000", key="backend_url")
12
13page = st.sidebar.radio(
14 "Page",
15 ["Home", "Upload + Preview", "Train", "Results", "PREM Heatmaps", "optimize AOA"],
16 key="page_select"
17)
18
19alpha = st.sidebar.slider("AoA (deg)", -5.0, 15.0, 5.0, 0.5, key="alpha_slider")
20Re_phys = st.sidebar.selectbox("Re (physical)", [1e5, 5e5, 1e6, 3e6], index=2, key="re_select")
21steps = st.sidebar.slider("Training steps", 50, 2000, 100, 50, key="steps_slider")
22N_int = st.sidebar.selectbox("N_int", [2000, 4000, 8000, 20000], index=0, key="nint_select")
23N_near = st.sidebar.selectbox("N_near", [2000, 4000, 8000, 20000], index=0, key="nnear_select")
24lr = st.sidebar.selectbox("Learning rate", [1e-3, 5e-4, 1e-4, 5e-5], index=2, key="lr_select")
25checkpoint_path = st.sidebar.text_input("Checkpoint path (optional)", "", key="checkpoint_path_input")
26
27# ---- session state ----
28if "run_id" not in st.session_state:
29 st.session_state.run_id = None
30if "last_result" not in st.session_state:
31 st.session_state.last_result = None
32if "airfoil_bytes" not in st.session_state:
33 st.session_state.airfoil_bytes = None
34if "airfoil_name" not in st.session_state:
35 st.session_state.airfoil_name = None
36if "opt_id" not in st.session_state:
37 st.session_state.opt_id = None
38if "opt_result" not in st.session_state:
39 st.session_state.opt_result = None
40
41def api_get(path, timeout=30):
42 r = requests.get(f"{BACKEND}{path}", timeout=timeout)
43 r.raise_for_status()
44 return r.json()
45
46
47def api_post_run(file_bytes: bytes, file_name: str):
48 files = {
49 "airfoil_dat": (file_name, file_bytes, "text/plain")
50 }
51 data = {
52 "alpha_deg": str(alpha),
53 "Re_phys": str(Re_phys),
54 "steps": str(steps),
55 "N_int": str(N_int),
56 "N_near": str(N_near),
57 "lr": str(lr),
58 "checkpoint_path": checkpoint_path or "",
59 }
60
61 r = requests.post(f"{BACKEND}/run_async", files=files, data=data, timeout=3600)
62
63 # If it fails, show the real FastAPI error message (super important)
64 if r.status_code == 422:
65 st.error(r.text)
66 return None
67
68 r.raise_for_status()
69 return r.json()
70def api_post_optimize_aoa(
71 file_bytes: bytes,
72 file_name: str,
73 Re_phys: float,
74 alpha_min: float,
75 alpha_max: float,
76 n_alpha: int,
77 steps: int,
78 N_int: int,
79 N_near: int,
80 lr: float,
81):
82 files = {"file": (file_name, file_bytes, "text/plain")}
83 data = {
84 "Re_phys": str(Re_phys),
85 "alpha_min": str(alpha_min),
86 "alpha_max": str(alpha_max),
87 "n_alpha": str(n_alpha),
88 "steps": str(steps),
89 "N_int": str(N_int),
90 "N_near": str(N_near),
91 "lr": str(lr),
92 }
93
94 r = requests.post(f"{BACKEND}/optimize_aoa", files=files, data=data, timeout=3600)
95
96 if r.status_code == 422:
97 st.error(r.text)
98 return None
99
100 r.raise_for_status()
101 return r.json()
102def set_bg_image(path: str):
103 img_bytes = Path(path).read_bytes()
104 b64 = base64.b64encode(img_bytes).decode()
105
106 st.markdown(
107 f"""
108 <style>
109 .stApp {{
110 background: url("data:image/jpg;base64,{b64}") no-repeat center center fixed;
111 background-size: cover;
112 }}
113
114 /* Optional: make main content readable on top of image */
115 section[data-testid="stMain"] > div {{
116 background-color: rgba(255, 255, 255, 0.88);
117 border-radius: 16px;
118 padding: 24px;
119 }}
120 </style>
121 """,
122 unsafe_allow_html=True
123 )
124
125set_bg_image("app_frontend/assets/22.jpg")
126
127def render_home():
128 st.caption("Fast aerodynamic field prediction + AoA optimization (no CFD solver).")
129
130 # Simple hero box
131 st.markdown(
132 """
133 <div style="padding:16px;border-radius:12px;background:#f6f8ff;border:1px solid #e6e9ff;">
134 <h3 style="margin:0;">Your design tool for High-Re airfoil exploration</h3>
135 <p style="margin:6px 0 0 0;">
136 AeroPINN-X turns early airfoil design questions into fast, visual, physics-checked
137 insights letting you test ideas, compare conditions, and run quick AoA optimization
138 without waiting for full CFD.
139 </p>
140 </div>
141 """,
142 unsafe_allow_html=True,
143 )
144
145 st.write("")
146 col1, col2 = st.columns(2)
147
148 with col1:
149 st.subheader("About")
150 st.write(
151 """
152 AeroPINN-X is an engineering prototype built with a Physics-Informed Neural Network (PINN).
153 It learns to satisfy the governing equations and boundary conditions, so we can approximate
154 aerodynamic flow fields without a traditional CFD solver in the loop.
155 """
156 )
157
158 st.subheader("Designed for")
159 st.markdown(
160
161 """
162 - Conceptual / preliminary design where you need rapid iteration (low-Re scenarios, UAVs, small wings, research validation)
163 - Quick “what-if” studies across AoA and Re (compare trends, not days of simulation time)
164 - Demonstrating a complete product workflow: Upload → Train → Results → Residual QA → Optimization
165 - Students/researchers who want physics + software engineering in one deployable tool
166 """
167 )
168
169 with col2:
170 st.subheader("Key features")
171 st.markdown(
172 """
173 - Upload airfoil `.dat`
174 - Point cloud preview (interior + near-wall points)
175 - Prototype training (short runs with live status + saved artifacts)
176 - Output plots: **u, v, p, ν̃**
177 - PREM residual heatmaps (continuity + SA) to verify physics
178 - AoA optimization loop (objective vs iteration)
179 """
180 )
181
182 st.subheader("How to use (demo flow)")
183 st.markdown(
184 """
185 1) **Upload + Preview** → upload `.dat`, confirm shape / points
186 2) **Train** → start async run (prototype steps)
187 3) **Results** → view predicted fields and plots
188 4) **PREM Heatmaps** → validate physics residuals
189 5) **optimize AOA** → run AoA sweep → show objective curve → pick best AoA
190 """
191 )
192
193 st.write("")
194 st.info("Tip: For quick demos, use small steps and moderate N_int/N_near to keep runtime low.")
195
196
197def show_image(url_path: str, caption: str):
198 full = f"{BACKEND}{url_path}"
199 st.image(full, caption=caption, width="stretch")
200
201
202# ---- UI ----
203st.title("AeroPINN-X")
204def have_airfoil():
205 return st.session_state.airfoil_bytes is not None
206if page == "Home":
207 render_home()
208elif page == "Upload + Preview":
209 st.header("Upload + Preview")
210 file = st.file_uploader("Upload airfoil .dat", type=["dat"], key="airfoil_uploader")
211 st.write("Upload the airfoil file. Then run training to generate point cloud + plots.")
212 if file is not None:
213 st.success(f"Uploaded: {file.name}")
214 st.session_state.airfoil_bytes = file.getvalue()
215 st.session_state.airfoil_name = file.name
216
217 if have_airfoil():
218 st.write("Current airfoil:", st.session_state.airfoil_name)
219elif page == "Train":
220 st.header("Train")
221
222 if not have_airfoil():
223 st.warning("Upload a .dat airfoil first (Upload + Preview page).")
224 st.stop()
225 st.write("Airfoil:", st.session_state.airfoil_name)
226
227 if st.button("Run (async)", key="run_async_btn"):
228 resp = api_post_run(st.session_state.airfoil_bytes, st.session_state.airfoil_name)
229 st.session_state.run_id = resp["run_id"]
230 st.success(f"Started run: {st.session_state.run_id}")
231
232 col1, col2 = st.columns(2)
233 with col2:
234 st.subheader("Current run")
235 st.code(st.session_state.get("run_id") or "None")
236
237 if "last_status" not in st.session_state:
238 st.session_state.last_status = None
239
240 if st.button("Poll status now", key="poll_btn") and st.session_state.get("run_id"):
241 try:
242 status = api_get(f"/runs/{st.session_state.run_id}/status", timeout=10)
243 st.session_state.last_status = status
244 except Exception as e:
245 st.session_state.last_status = {"status": "error", "error": str(e)}
246
247 status = st.session_state.get("last_status")
248
249 if not status:
250 st.info("No status yet. Click **Poll status now**.")
251 else:
252 st.write("Run status:", status)
253 if status.get("status") == "error":
254 st.error(status.get("error", "Unknown error"))
255 elif status.get("status") == "done":
256 st.success("Run completed!")
257 elif status.get("status") == "running":
258 st.warning("Running...")
259 else:
260 st.info(status.get("status"))
261
262
263
264elif page == "Results":
265 st.header("Results")
266
267 run_id = st.session_state.run_id
268 if not run_id:
269 st.info("Run training first.")
270 else:
271 status = api_get(f"/runs/{run_id}/status")
272 st.write("Status:", status["status"])
273 if status["status"] != "done":
274 st.warning("Run not finished yet. Go to Train page and poll status.")
275 else:
276 result = api_get(f"/runs/{run_id}/result")
277 st.session_state.last_result = result
278
279 arts = result["artifacts"]
280 c1, c2 = st.columns(2)
281 with c1:
282 show_image(arts["speed"], "Speed magnitude √(u²+v²)")
283 with c2:
284 show_image(arts["pressure"], "Pressure field p")
285
286 st.subheader("Point cloud")
287 show_image(arts["point_cloud"], "Mesh-free point cloud")
288
289 st.subheader("PREM summary (numbers)")
290 st.json(result.get("prem", {}))
291
292elif page == "PREM Heatmaps":
293 st.header("PREM Residual Heatmaps")
294 run_id = st.session_state.run_id
295 if not run_id:
296 st.info("Run training first.")
297 else:
298 status = api_get(f"/runs/{run_id}/status")
299 if status["status"] != "done":
300 st.warning("Run not finished yet.")
301 else:
302 result = api_get(f"/runs/{run_id}/result")
303 arts = result["artifacts"]
304 c1, c2 = st.columns(2)
305 with c1:
306 show_image(arts["prem_cont"], "Continuity residual heatmap")
307 with c2:
308 show_image(arts["prem_sa"], "SA residual heatmap")
309
310elif page == "optimize AOA":
311 st.header("Optimize AoA")
312 if not have_airfoil():
313 st.warning("Upload a .dat airfoil first (Upload + Preview page).")
314 st.stop()
315
316 # Optimization controls
317 alpha_min = st.sidebar.slider("alpha_min (deg)", -5.0, 15.0, 0.0, 0.5, key="alpha_min")
318 alpha_max = st.sidebar.slider("alpha_max (deg)", -5.0, 15.0, 10.0, 0.5, key="alpha_max")
319 n_alpha = st.sidebar.selectbox("n_alpha (samples)", [3, 5, 7, 9], index=1, key="n_alpha")
320
321 steps_opt = st.sidebar.slider("steps per alpha", 10, 150, 50, 10, key="steps_opt")
322
323 st.write(f"**Airfoil:** {st.session_state.airfoil_name}")
324 st.write(f"**Re:** {Re_phys}")
325 st.write(f"**Search:** α in [{alpha_min}, {alpha_max}] with {n_alpha} samples")
326 st.write(f"**Per-run:** steps={steps_opt}, N_int={N_int}, N_near={N_near}, lr={lr}")
327
328 if st.button("Run AoA optimization", key="run_opt_btn"):
329 resp = api_post_optimize_aoa(
330 file_bytes=st.session_state.airfoil_bytes,
331 file_name=st.session_state.airfoil_name,
332 Re_phys=Re_phys,
333 alpha_min=alpha_min,
334 alpha_max=alpha_max,
335 n_alpha=n_alpha,
336 steps=steps_opt,
337 N_int=N_int,
338 N_near=N_near,
339 lr=lr,
340 )
341 if resp is None:
342 st.stop()
343 st.session_state.opt_id = resp["opt_id"]
344 st.success(f"Started optimization: {st.session_state.opt_id}")
345
346 # optional: polling section similar to Train page (we’ll add in later after backend is ready)
347
348 if st.session_state.opt_id:
349 st.subheader("Optimization status")
350 st.code(st.session_state.opt_id)
351
352 if st.button("Poll optimization status", key="poll_opt"):
353 st.session_state.opt_status = api_get(f"/optimize/{st.session_state.opt_id}/status", timeout=10)
354
355 status = st.session_state.get("opt_status")
356 if status:
357 st.write(status)
358
359 if status and status.get("status") == "done":
360 if st.button("Load optimization result", key="load_opt"):
361 st.session_state.opt_result = api_get(f"/optimize/{st.session_state.opt_id}/result", timeout=10)
362
363 result = st.session_state.get("opt_result")
364 if result:
365 table = result["table"]
366 best = result["best"]
367
368 import pandas as pd
369 df = pd.DataFrame([{"alpha": r["alpha"], "objective": r["objective"]} for r in table])
370
371 st.subheader("Objective vs AoA")
372 st.line_chart(df.set_index("alpha"))
373
374 st.subheader("Best AoA")
375 st.success(f"Best α = {best['alpha']} deg | objective = {best['objective']:.4f}")
376
377 # st.subheader("Best artifacts")
378 # art = best["artifacts"]
379 # st.image(f"{BACKEND}{art['pressure']}")
380 # st.image(f"{BACKEND}{art['speed']}")
381 # st.image(f"{BACKEND}{art['prem_cont']}")
382 # st.image(f"{BACKEND}{art['prem_sa']}")
383 