rrodden commited on
Commit
bcb368b
Β·
verified Β·
1 Parent(s): b1818e8

Setup for BOxCrete

Browse files
Files changed (4) hide show
  1. Dockerfile +17 -0
  2. README.md +29 -5
  3. main.py +186 -0
  4. requirements.txt +5 -0
Dockerfile ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ FROM python:3.11-slim
2
+
3
+ WORKDIR /app
4
+
5
+ RUN apt-get update && apt-get install -y --no-install-recommends \
6
+ build-essential git \
7
+ && rm -rf /var/lib/apt/lists/*
8
+
9
+ COPY requirements.txt .
10
+ RUN pip install --no-cache-dir -r requirements.txt
11
+
12
+ COPY main.py .
13
+
14
+ # HF Spaces requires port 7860
15
+ EXPOSE 7860
16
+
17
+ CMD ["python", "main.py"]
README.md CHANGED
@@ -1,10 +1,34 @@
1
  ---
2
- title: Mix
3
- emoji: 🐨
4
- colorFrom: indigo
5
- colorTo: pink
6
  sdk: docker
 
7
  pinned: false
8
  ---
9
 
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
+ title: MetaMix API
3
+ emoji: πŸ—οΈ
4
+ colorFrom: gray
5
+ colorTo: blue
6
  sdk: docker
7
+ app_port: 7860
8
  pinned: false
9
  ---
10
 
11
+ # MetaMix API
12
+
13
+ FastAPI backend for the [MetaMix](https://pavements.design/metamix) tool on pavements.design.
14
+
15
+ Wraps [BOxCrete](https://github.com/facebookresearch/SustainableConcrete) β€” a Bayesian
16
+ optimisation framework for sustainable concrete mix design by Meta AI Research.
17
+
18
+ ## Endpoints
19
+
20
+ - `GET /health` β€” liveness check
21
+ - `POST /predict` β€” returns GWP and strength curve for a given mix
22
+
23
+ ## Citation
24
+
25
+ ```
26
+ @misc{ament2023sustainable,
27
+ title={Sustainable Concrete via Bayesian Optimization},
28
+ author={Sebastian Ament and Andrew Witte and Nishant Garg and Julius Kusuma},
29
+ year={2023},
30
+ eprint={2310.18288},
31
+ archivePrefix={arXiv},
32
+ primaryClass={cs.LG}
33
+ }
34
+ ```
main.py ADDED
@@ -0,0 +1,186 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ MetaMix API β€” FastAPI wrapper around BOxCrete
3
+ =============================================
4
+ Designed to run as a Hugging Face Space (Docker SDK).
5
+
6
+ Deploy steps:
7
+ 1. Create a new Space at https://huggingface.co/spaces
8
+ - Owner: your HF username
9
+ - Space name: metamix-api (or whatever you like)
10
+ - SDK: Docker
11
+ 2. Push this file + requirements.txt + README.md to the Space repo
12
+ 3. HF builds the Docker image automatically
13
+ 4. Copy the Space URL into Vercel env: METAMIX_API_URL=https://<user>-metamix-api.hf.space/predict
14
+
15
+ Cold start note:
16
+ The model fits from the bundled BOxCrete dataset on first request (~30 s).
17
+ Subsequent requests in the same session are fast (< 2 s).
18
+ HF free Spaces sleep after ~15 min of inactivity β€” the next request will
19
+ trigger another cold start. The page.tsx loading state handles this gracefully.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ import logging
25
+ from contextlib import asynccontextmanager
26
+ from typing import Optional
27
+
28
+ import torch
29
+ from fastapi import FastAPI, HTTPException
30
+ from fastapi.middleware.cors import CORSMiddleware
31
+ from pydantic import BaseModel, Field
32
+
33
+ from boxcrete.models import SustainableConcreteModel
34
+ from boxcrete.utils import load_concrete_strength, get_bounds
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+ # ── Global model state ────────────────────────────────────────────────────────
39
+
40
+ _model: Optional[SustainableConcreteModel] = None
41
+ _data = None
42
+
43
+ CURVE_DAYS = list(range(1, 91))
44
+ STRENGTH_DAYS = [1, 3, 7, 14, 28, 56, 90]
45
+
46
+
47
+ def _fit_model() -> SustainableConcreteModel:
48
+ global _data
49
+ logger.info("Loading BOxCrete dataset…")
50
+ _data = load_concrete_strength()
51
+ _data.bounds = get_bounds(_data.X_columns)
52
+
53
+ model = SustainableConcreteModel(strength_days=STRENGTH_DAYS)
54
+ logger.info("Fitting GWP model…")
55
+ model.fit_gwp_model(_data)
56
+ logger.info("Fitting strength model…")
57
+ model.fit_strength_model(_data)
58
+ logger.info("Model ready.")
59
+ return model
60
+
61
+
62
+ @asynccontextmanager
63
+ async def lifespan(app: FastAPI):
64
+ global _model
65
+ _model = _fit_model()
66
+ yield
67
+
68
+
69
+ # ── App ───────────────────────────────────────────────────────────────────────
70
+
71
+ app = FastAPI(title="MetaMix API", lifespan=lifespan)
72
+
73
+ app.add_middleware(
74
+ CORSMiddleware,
75
+ allow_origins=[
76
+ "https://pavements.design",
77
+ "https://www.pavements.design",
78
+ "http://localhost:3000",
79
+ ],
80
+ allow_methods=["POST", "GET"],
81
+ allow_headers=["Content-Type"],
82
+ )
83
+
84
+ # ── Schemas ───────────────────────────────────────────────────────────────────
85
+
86
+ class MixRequest(BaseModel):
87
+ cement: float = Field(..., ge=0, le=600)
88
+ fly_ash: float = Field(0.0, ge=0, le=400)
89
+ slag: float = Field(0.0, ge=0, le=400)
90
+ water: float = Field(..., ge=80, le=350)
91
+ fine_aggregate: float = Field(0.0, ge=0, le=1200)
92
+ coarse_aggregate: float = Field(0.0, ge=0, le=1200)
93
+ superplasticizer: float = Field(0.0, ge=0, le=30)
94
+
95
+
96
+ class StrengthPoint(BaseModel):
97
+ day: int
98
+ mean: float
99
+ lower: float
100
+ upper: float
101
+
102
+
103
+ class PredictionResponse(BaseModel):
104
+ gwp: float
105
+ gwp_lower: float
106
+ gwp_upper: float
107
+ strength_28d: float
108
+ strength_28d_lower: float
109
+ strength_28d_upper: float
110
+ strength_curve: list[StrengthPoint]
111
+
112
+
113
+ # ── Helpers ───────────────────────────────────────────────────────────────────
114
+
115
+ def _build_comp_tensor(req: MixRequest, comp_cols: list[str]) -> torch.Tensor:
116
+ """Map request fields onto the column order BOxCrete expects."""
117
+ mapping = {
118
+ "Cement (kg/m3)": req.cement,
119
+ "Fly Ash (kg/m3)": req.fly_ash,
120
+ "Slag (kg/m3)": req.slag,
121
+ "Water (kg/m3)": req.water,
122
+ "Fine Aggregate (kg/m3)": req.fine_aggregate,
123
+ "Coarse Aggregate (kg/m3)": req.coarse_aggregate,
124
+ "Superplasticizer (kg/m3)": req.superplasticizer,
125
+ }
126
+ values = [mapping.get(col, 0.0) for col in comp_cols]
127
+ return torch.tensor(values, dtype=torch.float64).unsqueeze(0) # 1 Γ— d
128
+
129
+
130
+ def _posterior_stats(model, X: torch.Tensor) -> tuple[float, float, float]:
131
+ with torch.no_grad():
132
+ posterior = model.posterior(X)
133
+ mean = posterior.mean.squeeze().item()
134
+ std = posterior.variance.squeeze().item() ** 0.5
135
+ return mean, mean - 1.96 * std, mean + 1.96 * std
136
+
137
+
138
+ # ── Endpoints ─────────────────────────────────────────────────────────────────
139
+
140
+ @app.get("/health")
141
+ async def health():
142
+ return {"status": "ok", "model_ready": _model is not None}
143
+
144
+
145
+ @app.post("/predict", response_model=PredictionResponse)
146
+ async def predict(req: MixRequest):
147
+ if _model is None or _data is None:
148
+ raise HTTPException(status_code=503, detail="Model not yet initialised")
149
+
150
+ # Composition columns (everything except Time)
151
+ comp_cols = _data.X_columns[:-1]
152
+ comp = _build_comp_tensor(req, comp_cols) # 1 Γ— (d-1)
153
+
154
+ # GWP
155
+ gwp_mean, gwp_lo, gwp_hi = _posterior_stats(_model.gwp_model, comp)
156
+
157
+ # Strength curve (day 1 β†’ 90)
158
+ curve: list[StrengthPoint] = []
159
+ str28_mean = str28_lo = str28_hi = 0.0
160
+
161
+ for day in CURVE_DAYS:
162
+ t = torch.tensor([[float(day)]], dtype=torch.float64)
163
+ X_t = torch.cat([comp, t], dim=-1)
164
+ mean, lo, hi = _posterior_stats(_model.strength_model, X_t)
165
+ mean = max(mean, 0.0)
166
+ lo = max(lo, 0.0)
167
+ curve.append(StrengthPoint(day=day, mean=round(mean, 2), lower=round(lo, 2), upper=round(hi, 2)))
168
+ if day == 28:
169
+ str28_mean, str28_lo, str28_hi = mean, lo, hi
170
+
171
+ return PredictionResponse(
172
+ gwp=round(gwp_mean, 2),
173
+ gwp_lower=round(gwp_lo, 2),
174
+ gwp_upper=round(gwp_hi, 2),
175
+ strength_28d=round(str28_mean, 2),
176
+ strength_28d_lower=round(str28_lo, 2),
177
+ strength_28d_upper=round(str28_hi, 2),
178
+ strength_curve=curve,
179
+ )
180
+
181
+
182
+ # ── Entry point (HF Spaces requires port 7860) ────────────────────────────────
183
+
184
+ if __name__ == "__main__":
185
+ import uvicorn
186
+ uvicorn.run(app, host="0.0.0.0", port=7860)
requirements.txt ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ fastapi[standard]>=0.115.0
2
+ uvicorn>=0.30.0
3
+ # CPU-only PyTorch β€” smaller image, sufficient for GP inference
4
+ torch>=2.2.0 --index-url https://download.pytorch.org/whl/cpu
5
+ git+https://github.com/facebookresearch/SustainableConcrete.git