Tech-Meld commited on
Commit
19d55c2
·
verified ·
1 Parent(s): 7f63b81

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +94 -86
app.py CHANGED
@@ -1,19 +1,14 @@
1
- """
2
- HeatBee: Interactive Surrogate Explorer & Bioclimatic Dashboard
3
- """
4
-
5
  import json
6
  from pathlib import Path
7
- import streamlit as st
8
  import numpy as np
 
9
  import xgboost as xgb
10
-
11
- st.set_page_config(page_title="HeatBee - Vernacular Thermal Surrogate", page_icon="🏛️", layout="wide")
12
 
13
  MODEL_PATH = Path("surrogate_peak_temp.json")
14
  BENCHMARK_JSON = Path("benchmark_results.json")
15
 
16
- @st.cache_resource
17
  def load_surrogate():
18
  if not MODEL_PATH.exists():
19
  return None
@@ -23,88 +18,101 @@ def load_surrogate():
23
 
24
  model = load_surrogate()
25
 
26
- st.title("🏛️ HeatBee: Vernacular Thermal Physics & AI Surrogate")
27
- st.markdown("""
28
- **Surrogate Modeling of Moroccan Rammed Earth (*Pisé*) Architecture**
29
- *EnergyPlus 26.2 1D Conduction Finite Difference | Marrakesh Menara EPW (Hottest Outdoor Hour: 45.52°C)*
30
- """)
31
-
32
- if model is None:
33
- st.error("Surrogate model not found. Please run `python cli.py --train` first.")
34
- st.stop()
35
-
36
- col_ctrl, col_metrics = st.columns([1, 1], gap="large")
37
-
38
- with col_ctrl:
39
- st.subheader("🛠️ Design Envelope Controls")
40
- wall_t = st.slider("Pisé Wall Thickness [m]", 0.25, 0.60, 0.50, 0.05,
41
- help="Analytical diurnal penetration depth is ~11 cm. Beyond 45 cm, mass yields diminishing returns.")
42
- roof_alb = st.slider("Roof Solar Reflectance (Albedo)", 0.20, 0.85, 0.60, 0.05,
43
- help="Solar reflectance of horizontal roof finish. Dominant thermal parameter.")
44
- shading = st.slider("Mashrabiya Glazing SHGC", 0.10, 0.80, 0.40, 0.05,
45
- help="Solar Heat Gain Coefficient. Lower value represents denser shading.")
46
- wwr = st.slider("Courtyard Window-to-Wall Ratio", 0.20, 0.50, 0.25, 0.05)
47
- vent_ach = st.slider("Ventilation Rate [ACH]", 1.0, 12.0, 4.0, 0.5,
48
- help="Free cooling ventilation rate when outdoor air is cooler than indoors.")
49
-
50
- with col_metrics:
51
- st.subheader("⚡ Real-Time Physics Inference (< 1 ms)")
52
-
53
  x_in = np.array([[wall_t, vent_ach, roof_alb, shading, wwr]])
54
  pred_peak = float(model.predict(x_in)[0])
55
  peak_suppression = 45.52 - pred_peak
 
 
 
 
 
56
 
57
- m1, m2 = st.columns(2)
58
- m1.metric("Predicted Peak Operative Temp", f"{pred_peak:.2f} °C")
59
- m2.metric("Peak Suppression vs Ambient", f"{peak_suppression:.2f} °C",
60
- help="Difference between peak outdoor ambient (45.52°C) and indoor peak operative temperature.")
61
-
62
- st.divider()
63
- st.subheader("🔬 Benchmark Comparison")
64
-
65
- if BENCHMARK_JSON.exists():
66
- with open(BENCHMARK_JSON) as f:
67
- bench_data = json.load(f)
68
-
69
- mode_choice = st.radio("Benchmark Mode:", ["Mode A: As-Built Typologies (Archetype Partitions)", "Mode B: Shared Interior Mass (15cm Concrete Slab for All)"], horizontal=True)
70
- mode_key = "as_built" if "Mode A" in mode_choice else "shared_interior"
71
-
72
- table_rows = []
73
- # Ambient row
74
  table_rows.append({
75
- "Archetype": bench_data["ambient"]["label"],
76
- "Peak Temp [°C]": f"{bench_data['ambient']['peak']:.2f} °C",
77
- "Mean Temp [°C]": f"{bench_data['ambient']['mean']:.2f} °C",
78
- "DDH (>30°C)": f"{bench_data['ambient']['ddh_30']:.1f}",
79
- "Daily Decrement (f)": f"{bench_data['ambient']['decrement_f']:.2f}"
80
  })
81
- for k, v in bench_data[mode_key].items():
82
- table_rows.append({
83
- "Archetype": v["label"],
84
- "Peak Temp [°C]": f"{v['peak']:.2f} °C",
85
- "Mean Temp [°C]": f"{v['mean']:.2f} °C",
86
- "DDH (>30°C)": f"{v['ddh_30']:.1f}",
87
- "Daily Decrement (f)": f"{v['decrement_f']:.2f}"
88
- })
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
89
 
90
- st.dataframe(table_rows, width="stretch")
91
- st.caption("Shared boundary conditions: Roof albedo 0.60, external wall solar absorptance 0.50, glazing SHGC 0.40, 4.0 ACH free cooling, adiabatic floor and party walls.")
92
- else:
93
- st.info("Run `python cli.py --benchmark` to generate ablation results.")
94
-
95
- st.divider()
96
- st.subheader("📊 Visualizations")
97
- tab1, tab2, tab3 = st.tabs(["1-to-1 Heatwave Ablation Profile", "Direct Physical Sweeps", "Surrogate Parity"])
98
-
99
- with tab1:
100
- fig_ab = "rigorous_ablation_heatwave_profile.png"
101
- if Path(fig_ab).exists():
102
- st.image(str(fig_ab))
103
- with tab2:
104
- fig_sw = "direct_physics_sweeps.png"
105
- if Path(fig_sw).exists():
106
- st.image(str(fig_sw))
107
- with tab3:
108
- fig_par = "surrogate_parity_plot.png"
109
- if Path(fig_par).exists():
110
- st.image(str(fig_par))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import json
2
  from pathlib import Path
 
3
  import numpy as np
4
+ import pandas as pd
5
  import xgboost as xgb
6
+ import gradio as gr
7
+ import spaces
8
 
9
  MODEL_PATH = Path("surrogate_peak_temp.json")
10
  BENCHMARK_JSON = Path("benchmark_results.json")
11
 
 
12
  def load_surrogate():
13
  if not MODEL_PATH.exists():
14
  return None
 
18
 
19
  model = load_surrogate()
20
 
21
+ @spaces.GPU
22
+ def predict_thermal_performance(wall_t, roof_alb, shading, wwr, vent_ach):
23
+ if model is None:
24
+ return "Model missing", "Model missing"
25
+ # Order: [wall_t, vent_ach, roof_alb, shading, wwr]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
26
  x_in = np.array([[wall_t, vent_ach, roof_alb, shading, wwr]])
27
  pred_peak = float(model.predict(x_in)[0])
28
  peak_suppression = 45.52 - pred_peak
29
+ return f"{pred_peak:.2f} °C", f"{peak_suppression:.2f} °C"
30
+
31
+ def get_benchmark_table(mode_choice):
32
+ if not BENCHMARK_JSON.exists():
33
+ return pd.DataFrame([{"Message": "Run `python cli.py --benchmark` to generate ablation results."}])
34
 
35
+ with open(BENCHMARK_JSON) as f:
36
+ bench_data = json.load(f)
37
+
38
+ mode_key = "as_built" if "Mode A" in mode_choice else "shared_interior"
39
+ table_rows = []
40
+
41
+ # Ambient row
42
+ table_rows.append({
43
+ "Archetype": bench_data["ambient"]["label"],
44
+ "Peak Temp [°C]": f"{bench_data['ambient']['peak']:.2f} °C",
45
+ "Mean Temp [°C]": f"{bench_data['ambient']['mean']:.2f} °C",
46
+ "DDH (>30°C)": f"{bench_data['ambient']['ddh_30']:.1f}",
47
+ "Daily Decrement (f)": f"{bench_data['ambient']['decrement_f']:.2f}"
48
+ })
49
+
50
+ for k, v in bench_data[mode_key].items():
 
51
  table_rows.append({
52
+ "Archetype": v["label"],
53
+ "Peak Temp [°C]": f"{v['peak']:.2f} °C",
54
+ "Mean Temp [°C]": f"{v['mean']:.2f} °C",
55
+ "DDH (>30°C)": f"{v['ddh_30']:.1f}",
56
+ "Daily Decrement (f)": f"{v['decrement_f']:.2f}"
57
  })
58
+
59
+ return pd.DataFrame(table_rows)
60
+
61
+ def get_image_path(filename):
62
+ path = Path(filename)
63
+ return str(path) if path.exists() else None
64
+
65
+ with gr.Blocks(title="HeatBee - Vernacular Thermal Surrogate") as demo:
66
+ gr.Markdown("# 🏛️ HeatBee: Vernacular Thermal Physics & AI Surrogate")
67
+ gr.Markdown("**Surrogate Modeling of Moroccan Rammed Earth (*Pisé*) Architecture**\n*EnergyPlus 26.2 1D Conduction Finite Difference | Marrakesh Menara EPW (Hottest Outdoor Hour: 45.52°C)*")
68
+
69
+ if model is None:
70
+ gr.Warning("Surrogate model not found. Please run `python cli.py --train` first.")
71
+
72
+ with gr.Row():
73
+ with gr.Column():
74
+ gr.Markdown("### 🛠️ Design Envelope Controls")
75
+ wall_t = gr.Slider(0.25, 0.60, value=0.50, step=0.05, label="Pisé Wall Thickness [m]", info="Analytical diurnal penetration depth is ~11 cm. Beyond 45 cm, mass yields diminishing returns.")
76
+ roof_alb = gr.Slider(0.20, 0.85, value=0.60, step=0.05, label="Roof Solar Reflectance (Albedo)", info="Solar reflectance of horizontal roof finish. Dominant thermal parameter.")
77
+ shading = gr.Slider(0.10, 0.80, value=0.40, step=0.05, label="Mashrabiya Glazing SHGC", info="Solar Heat Gain Coefficient. Lower value represents denser shading.")
78
+ wwr = gr.Slider(0.20, 0.50, value=0.25, step=0.05, label="Courtyard Window-to-Wall Ratio")
79
+ vent_ach = gr.Slider(1.0, 12.0, value=4.0, step=0.5, label="Ventilation Rate [ACH]", info="Free cooling ventilation rate when outdoor air is cooler than indoors.")
80
+
81
+ with gr.Column():
82
+ gr.Markdown("### ⚡ Real-Time Physics Inference (< 1 ms)")
83
+ pred_out = gr.Textbox(label="Predicted Peak Operative Temp")
84
+ supp_out = gr.Textbox(label="Peak Suppression vs Ambient", info="Difference between peak outdoor ambient (45.52°C) and indoor peak operative temperature.")
85
 
86
+ gr.Markdown("---")
87
+ gr.Markdown("### 🔬 Benchmark Comparison")
88
+ mode_choice = gr.Radio(
89
+ choices=["Mode A: As-Built Typologies (Archetype Partitions)", "Mode B: Shared Interior Mass (15cm Concrete Slab for All)"],
90
+ value="Mode A: As-Built Typologies (Archetype Partitions)",
91
+ label="Benchmark Mode:"
92
+ )
93
+ bench_table = gr.Dataframe(interactive=False)
94
+ gr.Markdown("*Shared boundary conditions: Roof albedo 0.60, external wall solar absorptance 0.50, glazing SHGC 0.40, 4.0 ACH free cooling, adiabatic floor and party walls.*")
95
+
96
+ gr.Markdown("---")
97
+ gr.Markdown("### 📊 Visualizations")
98
+ with gr.Tabs():
99
+ with gr.Tab("1-to-1 Heatwave Ablation Profile"):
100
+ gr.Image(value=get_image_path("rigorous_ablation_heatwave_profile.png"), show_label=False)
101
+ with gr.Tab("Direct Physical Sweeps"):
102
+ gr.Image(value=get_image_path("direct_physics_sweeps.png"), show_label=False)
103
+ with gr.Tab("Surrogate Parity"):
104
+ gr.Image(value=get_image_path("surrogate_parity_plot.png"), show_label=False)
105
+
106
+ # Reactive Bindings
107
+ inputs = [wall_t, roof_alb, shading, wwr, vent_ach]
108
+ outputs = [pred_out, supp_out]
109
+
110
+ for component in inputs:
111
+ component.change(fn=predict_thermal_performance, inputs=inputs, outputs=outputs)
112
+
113
+ demo.load(fn=predict_thermal_performance, inputs=inputs, outputs=outputs)
114
+ mode_choice.change(fn=get_benchmark_table, inputs=[mode_choice], outputs=[bench_table])
115
+ demo.load(fn=get_benchmark_table, inputs=[mode_choice], outputs=[bench_table])
116
+
117
+ if __name__ == "__main__":
118
+ demo.launch()