File size: 5,665 Bytes
3284726
 
 
19d55c2
3284726
19d55c2
 
3284726
6fed30b
 
3284726
 
 
 
 
 
 
 
 
 
19d55c2
 
 
 
 
3284726
 
 
19d55c2
 
 
 
 
3284726
19d55c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3284726
19d55c2
 
 
 
 
3284726
19d55c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3284726
19d55c2
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
import json
from pathlib import Path
import numpy as np
import pandas as pd
import xgboost as xgb
import gradio as gr
import spaces

MODEL_PATH = Path("surrogate_peak_temp.json")
BENCHMARK_JSON = Path("benchmark_results.json")

def load_surrogate():
    if not MODEL_PATH.exists():
        return None
    model = xgb.XGBRegressor()
    model.load_model(str(MODEL_PATH))
    return model

model = load_surrogate()

@spaces.GPU
def predict_thermal_performance(wall_t, roof_alb, shading, wwr, vent_ach):
    if model is None:
        return "Model missing", "Model missing"
    # Order: [wall_t, vent_ach, roof_alb, shading, wwr]
    x_in = np.array([[wall_t, vent_ach, roof_alb, shading, wwr]])
    pred_peak = float(model.predict(x_in)[0])
    peak_suppression = 45.52 - pred_peak
    return f"{pred_peak:.2f} °C", f"{peak_suppression:.2f} °C"

def get_benchmark_table(mode_choice):
    if not BENCHMARK_JSON.exists():
        return pd.DataFrame([{"Message": "Run `python cli.py --benchmark` to generate ablation results."}])
    
    with open(BENCHMARK_JSON) as f:
        bench_data = json.load(f)

    mode_key = "as_built" if "Mode A" in mode_choice else "shared_interior"
    table_rows = []

    # Ambient row
    table_rows.append({
        "Archetype": bench_data["ambient"]["label"],
        "Peak Temp [°C]": f"{bench_data['ambient']['peak']:.2f} °C",
        "Mean Temp [°C]": f"{bench_data['ambient']['mean']:.2f} °C",
        "DDH (>30°C)": f"{bench_data['ambient']['ddh_30']:.1f}",
        "Daily Decrement (f)": f"{bench_data['ambient']['decrement_f']:.2f}"
    })

    for k, v in bench_data[mode_key].items():
        table_rows.append({
            "Archetype": v["label"],
            "Peak Temp [°C]": f"{v['peak']:.2f} °C",
            "Mean Temp [°C]": f"{v['mean']:.2f} °C",
            "DDH (>30°C)": f"{v['ddh_30']:.1f}",
            "Daily Decrement (f)": f"{v['decrement_f']:.2f}"
        })

    return pd.DataFrame(table_rows)

def get_image_path(filename):
    path = Path(filename)
    return str(path) if path.exists() else None

with gr.Blocks(title="HeatBee - Vernacular Thermal Surrogate") as demo:
    gr.Markdown("# 🏛️ HeatBee: Vernacular Thermal Physics & AI Surrogate")
    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)*")
    
    if model is None:
        gr.Warning("Surrogate model not found. Please run `python cli.py --train` first.")

    with gr.Row():
        with gr.Column():
            gr.Markdown("### 🛠️ Design Envelope Controls")
            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.")
            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.")
            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.")
            wwr = gr.Slider(0.20, 0.50, value=0.25, step=0.05, label="Courtyard Window-to-Wall Ratio")
            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.")

        with gr.Column():
            gr.Markdown("### ⚡ Real-Time Physics Inference (< 1 ms)")
            pred_out = gr.Textbox(label="Predicted Peak Operative Temp")
            supp_out = gr.Textbox(label="Peak Suppression vs Ambient", info="Difference between peak outdoor ambient (45.52°C) and indoor peak operative temperature.")
            
            gr.Markdown("---")
            gr.Markdown("### 🔬 Benchmark Comparison")
            mode_choice = gr.Radio(
                choices=["Mode A: As-Built Typologies (Archetype Partitions)", "Mode B: Shared Interior Mass (15cm Concrete Slab for All)"],
                value="Mode A: As-Built Typologies (Archetype Partitions)",
                label="Benchmark Mode:"
            )
            bench_table = gr.Dataframe(interactive=False)
            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.*")

    gr.Markdown("---")
    gr.Markdown("### 📊 Visualizations")
    with gr.Tabs():
        with gr.Tab("1-to-1 Heatwave Ablation Profile"):
            gr.Image(value=get_image_path("rigorous_ablation_heatwave_profile.png"), show_label=False)
        with gr.Tab("Direct Physical Sweeps"):
            gr.Image(value=get_image_path("direct_physics_sweeps.png"), show_label=False)
        with gr.Tab("Surrogate Parity"):
            gr.Image(value=get_image_path("surrogate_parity_plot.png"), show_label=False)

    # Reactive Bindings
    inputs = [wall_t, roof_alb, shading, wwr, vent_ach]
    outputs = [pred_out, supp_out]

    for component in inputs:
        component.change(fn=predict_thermal_performance, inputs=inputs, outputs=outputs)
    
    demo.load(fn=predict_thermal_performance, inputs=inputs, outputs=outputs)
    mode_choice.change(fn=get_benchmark_table, inputs=[mode_choice], outputs=[bench_table])
    demo.load(fn=get_benchmark_table, inputs=[mode_choice], outputs=[bench_table])

if __name__ == "__main__":
    demo.launch()