File size: 7,147 Bytes
37dfff7
 
 
 
08b2383
37dfff7
 
 
 
08b2383
37dfff7
 
08b2383
37dfff7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
08b2383
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37dfff7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a48f23b
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
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
import os
import sys
from pathlib import Path
import io
import json
import asyncio
import traceback
import pandas as pd
import uvicorn
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from fastapi.responses import StreamingResponse

# Add project root and backend directory to sys.path
CURRENT_DIR = Path(__file__).resolve().parent
ROOT_DIR = CURRENT_DIR if (CURRENT_DIR / "index.html").exists() else CURRENT_DIR.parent
BACKEND_DIR = ROOT_DIR / "backend"
for d in [str(ROOT_DIR), str(BACKEND_DIR), str(CURRENT_DIR)]:
    if d not in sys.path:
        sys.path.insert(0, d)

try:
    from backend.cv_solver import solve_cv
except ImportError:
    try:
        from cv_solver import solve_cv
    except ImportError:
        from FD_solver import solve_cv

# ZeroGPU Support: Use Hugging Face ZeroGPU @spaces.GPU decorator if running on ZeroGPU
try:
    import spaces
    has_spaces = True
except Exception:
    has_spaces = False

if has_spaces:
    @spaces.GPU(duration=120)
    def compute_solve_cv(df, config, pot_col, cur_col, queue, loop):
        solve_cv(df, config, pot_col, cur_col, queue, loop)
else:
    def compute_solve_cv(df, config, pot_col, cur_col, queue, loop):
        solve_cv(df, config, pot_col, cur_col, queue, loop)

app = FastAPI(title="CV Curve Fitting Pro - JAX Engine")

# Enable Cross-Origin Resource Sharing (CORS) for all origins
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/health")
@app.get("/api/health")
def health_check():
    import jax
    devices = [str(d) for d in jax.devices()]
    is_gpu = any("gpu" in d.lower() or "cuda" in d.lower() for d in devices)
    return {
        "status": "ok",
        "engine": "JAX Hardware Accelerated (ZeroGPU / A100)" if (has_spaces or is_gpu) else "JAX Hardware Accelerated (CPU/XLA)",
        "hardware": "Hugging Face ZeroGPU (NVIDIA A100/H100)" if has_spaces else ("GPU" if is_gpu else "CPU / Local"),
        "cost": "100% Free",
        "devices": devices,
        "features": ["ZeroGPU Dynamic Allocation", "Automatic Differentiation", "JIT Parallelized Scan", "L-BFGS-B Multi-stage"]
    }

@app.post("/api/solve")
@app.post("/solve")
async def api_solve_stream(request: Request):
    data = await request.json()
    raw_config = data.get("config", {})
    file_content = data.get("file_content", "")
    
    if not file_content:
        return {"type": "error", "message": "No CSV file content received. Please select and upload a CV file."}
        
    config = {
        "scan_rate_v_s": float(raw_config.get("scan_rate", 0.010)),
        "film_thickness": float(raw_config.get("film_thickness", 1e-4)),
        "v_min": float(raw_config.get("v_min", -1.0)),
        "v_max": float(raw_config.get("v_max", 1.0)),
        "skip_factor": int(raw_config.get("skip_factor", 5)),
        "num_peaks": int(raw_config.get("num_peaks", 50)),
        "max_iter": int(raw_config.get("max_iter", 100)),
        "tol_ftol": float(raw_config.get("tol_ftol", 1e-8)),
        "tol_gtol": float(raw_config.get("tol_gtol", 1e-7)),
        "num_terms": int(raw_config.get("num_terms", 50)),
        "loss_weight_const": float(raw_config.get("loss_weight_const", 1.0))
    }
    pot_col = int(raw_config.get("pot_col", 8))
    cur_col = int(raw_config.get("cur_col", 9))
    
    df = pd.read_csv(io.StringIO(file_content), sep=None, engine='python')
    
    queue = asyncio.Queue()
    loop = asyncio.get_running_loop()
    
    def run_solver():
        try:
            compute_solve_cv(df, config, pot_col, cur_col, queue, loop)
        except Exception as e:
            loop.call_soon_threadsafe(
                queue.put_nowait, {
                    "type": "error",
                    "message": str(e),
                    "trace": traceback.format_exc()
                }
            )
            
    asyncio.create_task(asyncio.to_thread(run_solver))
    
    async def event_generator():
        while True:
            msg = await queue.get()
            yield json.dumps(msg) + "\n"
            if msg.get("type") in ("done", "error"):
                break
                
    return StreamingResponse(event_generator(), media_type="application/x-ndjson")

async def handle_solver_websocket(websocket: WebSocket):
    await websocket.accept()
    try:
        data = await websocket.receive_json()
        raw_config = data.get("config", {})
        file_content = data.get("file_content", "")
        
        if not file_content:
            await websocket.send_json({"type": "error", "message": "No CSV file content received. Please select and upload a CV file."})
            return
            
        config = {
            "scan_rate_v_s": float(raw_config.get("scan_rate", 0.010)),
            "film_thickness": float(raw_config.get("film_thickness", 1e-4)),
            "v_min": float(raw_config.get("v_min", -1.0)),
            "v_max": float(raw_config.get("v_max", 1.0)),
            "skip_factor": int(raw_config.get("skip_factor", 5)),
            "num_peaks": int(raw_config.get("num_peaks", 50)),
            "max_iter": int(raw_config.get("max_iter", 100)),
            "tol_ftol": float(raw_config.get("tol_ftol", 1e-8)),
            "tol_gtol": float(raw_config.get("tol_gtol", 1e-7)),
            "num_terms": int(raw_config.get("num_terms", 50)),
            "loss_weight_const": float(raw_config.get("loss_weight_const", 1.0))
        }
        pot_col = int(raw_config.get("pot_col", 8))
        cur_col = int(raw_config.get("cur_col", 9))
        
        df = pd.read_csv(io.StringIO(file_content), sep=None, engine='python')
        queue = asyncio.Queue()
        loop = asyncio.get_running_loop()
        
        def run_solver():
            try:
                compute_solve_cv(df, config, pot_col, cur_col, queue, loop)
            except Exception as e:
                loop.call_soon_threadsafe(
                    queue.put_nowait, {
                        "type": "error",
                        "message": str(e),
                        "trace": traceback.format_exc()
                    }
                )
                
        asyncio.create_task(asyncio.to_thread(run_solver))
        
        while True:
            msg = await queue.get()
            await websocket.send_json(msg)
            if msg.get("type") in ("done", "error"):
                break
                
    except WebSocketDisconnect:
        pass
    except Exception as e:
        try:
            await websocket.send_json({"type": "error", "message": str(e)})
        except Exception:
            pass

@app.websocket("/ws/solve")
async def websocket_solve(websocket: WebSocket):
    await handle_solver_websocket(websocket)

@app.websocket("/ws")
async def websocket_root(websocket: WebSocket):
    await handle_solver_websocket(websocket)

if __name__ == "__main__":
    port = int(os.environ.get("PORT", 8000))
    uvicorn.run("main:app", host="0.0.0.0", port=port, reload=False)