CoolFace
Apppublic

hasmalee/aero

sourceHugging Faceapache-2.0updated 8mo agoView on Hugging Face
0likes
app.py383 linesDownload Raw Back to root
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