Spaces:
No application file
No application file
Download backend/server.js from dimensionalpulsar/adn: direct link, hf CLI and curl.
- Browser
- Download file 8.3 kB
-
https://huggingface.co/spaces/dimensionalpulsar/adn/resolve/main/backend/server.js
- Command line
-
hf download hf://spaces/dimensionalpulsar/adn/backend/server.js
-
curl -L -o server.js https://huggingface.co/spaces/dimensionalpulsar/adn/resolve/main/backend/server.js
8.3 kB
| ; | |
| const express = require('express'); | |
| const cors = require('cors'); | |
| const dotenv = require('dotenv'); | |
| const { HfInference } = require('@huggingface/inference'); | |
| dotenv.config(); | |
| const app = express(); | |
| const PORT = process.env.PORT || 3001; | |
| const HF_TOKEN = process.env.HF_TOKEN; | |
| const hf = new HfInference(HF_TOKEN); | |
| app.use(cors()); | |
| app.use(express.json()); | |
| // ───────────────────────────────────────────────── | |
| // Navier-Stokes simplified 2-D pressure solver | |
| // (Pressure-Poisson approach, finite differences, single step) | |
| // Returns surface elevation field as a flat array [rows x cols] | |
| // ───────────────────────────────────────────────── | |
| function navierStokesStep({ amplitude, expansion, time }) { | |
| const N = 16; // grid resolution (16x16 snapshot) | |
| const dx = 1.0; | |
| const dt = 0.016; | |
| const nu = 0.01; // kinematic viscosity (water) | |
| const field = []; | |
| for (let j = 0; j < N; j++) { | |
| for (let i = 0; i < N; i++) { | |
| const x = i * dx; | |
| const z = j * dx; | |
| // Gerstner + diffusion approximation | |
| const k1 = 0.5 * expansion; | |
| const k2 = 0.3 * expansion; | |
| const omega1 = Math.sqrt(9.81 * k1); | |
| const omega2 = Math.sqrt(9.81 * k2); | |
| const eta = | |
| amplitude * Math.sin(k1 * x - omega1 * time) + | |
| amplitude * 0.6 * Math.cos(k2 * z - omega2 * time * 0.9); | |
| // simplified diffusion term (Laplacian) — mimics viscous damping | |
| const lap = | |
| -nu * (k1 * k1 + k2 * k2) * eta * dt; | |
| field.push(+(eta + lap).toFixed(4)); | |
| } | |
| } | |
| return { field, gridSize: N }; | |
| } | |
| // ───────────────────────────────────────────────── | |
| // Hydrostatic + wave resistance calculator | |
| // Based on Michell thin-ship integral (linearised) | |
| // ───────────────────────────────────────────────── | |
| function calcResistance({ waveAmplitude, temperature, finScale, shipSpeed = 10 }) { | |
| const rho = 1025 - temperature * 0.2; // kg/m³ seawater density drops with temp | |
| const g = 9.81; | |
| const L = 300; // ship length (m) | |
| const Cb = 0.6; // block coefficient (transatlantic liner) | |
| const Fn = shipSpeed / Math.sqrt(g * L); // Froude number | |
| // Wave resistance (Michell simplified) | |
| const Rw = 0.5 * rho * shipSpeed ** 2 * (Cb * 0.05) * (1 + waveAmplitude * 0.3) * Fn ** 2; | |
| // Viscous resistance | |
| const Re = (rho * shipSpeed * L) / 0.001; // dynamic viscosity ~0.001 Pa·s | |
| const Cf = 0.075 / (Math.log10(Re) - 2) ** 2; // ITTC-57 formula | |
| const Rv = 0.5 * rho * shipSpeed ** 2 * (Cb * L * 20) * Cf; | |
| // Fin drag / stabilisation penalty | |
| const finDrag = finScale * 0.02 * Rv; | |
| const totalResistance = Rw + Rv + finDrag; | |
| return { | |
| waveResistance: +Rw.toFixed(2), | |
| viscousResistance: +Rv.toFixed(2), | |
| finPenalty: +finDrag.toFixed(2), | |
| totalResistance: +totalResistance.toFixed(2), | |
| froudeNumber: +Fn.toFixed(4), | |
| reynoldsNumber: +Re.toExponential(3), | |
| }; | |
| } | |
| // ───────────────────────────────────────────────── | |
| // Random fin geometry generator seeded by conditions | |
| // ───────────────────────────────────────────────── | |
| function generateRandomFinDesign(waveAmplitude, temperature) { | |
| const seed = waveAmplitude * 1000 + temperature; | |
| const pseudo = (n) => Math.abs(Math.sin(seed * n * 0.1234)) ; | |
| const profiles = ['NACA0012', 'NACA2412', 'Delta', 'Swept', 'Trapezoidal']; | |
| const idx = Math.floor(pseudo(1) * profiles.length); | |
| return { | |
| profile: profiles[idx], | |
| aspectRatio: +(1.5 + pseudo(2) * 3.0).toFixed(2), // 1.5–4.5 | |
| sweepAngle: +(pseudo(3) * 45).toFixed(1), // 0–45° | |
| thickness: +(0.08 + pseudo(4) * 0.12).toFixed(3), // 8–20 % chord | |
| span: +(4 + pseudo(5) * 6).toFixed(2), // 4–10 m | |
| quantity: pseudo(6) > 0.5 ? 4 : 2, | |
| }; | |
| } | |
| // ───────────────────────────────────────────────── | |
| // POST /api/fluid-state — wave field snapshot | |
| // ───────────────────────────────────────────────── | |
| app.post('/api/fluid-state', (req, res) => { | |
| const { waveAmplitude = 1, waveExpansion = 1, time = 0 } = req.body; | |
| const result = navierStokesStep({ | |
| amplitude: waveAmplitude, | |
| expansion: waveExpansion, | |
| time, | |
| }); | |
| res.json(result); | |
| }); | |
| // ───────────────────────────────────────────────── | |
| // POST /api/adapt-fins — AI fin adaptation via HF | |
| // ───────────────────────────────────────────────── | |
| app.post('/api/adapt-fins', async (req, res) => { | |
| const { | |
| waveAmplitude = 1, | |
| temperature = 20, | |
| currentFinScale = 1, | |
| waveExpansion = 1, | |
| } = req.body; | |
| // Physics baseline | |
| const resistance = calcResistance({ waveAmplitude, temperature, finScale: currentFinScale }); | |
| const randomDesign = generateRandomFinDesign(waveAmplitude, temperature); | |
| // Algorithmic fallback result (used if HF call fails) | |
| const baseScale = Math.max( | |
| 0.5, | |
| Math.min(2.0, 1.0 + waveAmplitude * 0.12 - (temperature - 20) * 0.008) | |
| ); | |
| const fallback = { | |
| suggestedFinScale: +baseScale.toFixed(3), | |
| finDesign: randomDesign, | |
| resistance, | |
| designNote: `[Algorithmic] ${randomDesign.profile} fins at ${randomDesign.sweepAngle}° sweep. ` + | |
| `Rw=${resistance.waveResistance} N, Rv=${resistance.viscousResistance} N`, | |
| source: 'algorithmic', | |
| }; | |
| try { | |
| const prompt = `<s>[INST] You are an expert naval hydrodynamics AI system. | |
| Current ocean conditions: | |
| - Wave amplitude: ${waveAmplitude} m | |
| - Water temperature: ${temperature} °C | |
| - Wave expansion factor: ${waveExpansion} | |
| - Current fin scale: ${currentFinScale} | |
| - Froude number: ${resistance.froudeNumber} | |
| - Total resistance: ${resistance.totalResistance} N | |
| Current random fin design generated: | |
| - Profile: ${randomDesign.profile} | |
| - Aspect ratio: ${randomDesign.aspectRatio} | |
| - Sweep angle: ${randomDesign.sweepAngle}° | |
| - Thickness: ${randomDesign.thickness} | |
| - Quantity: ${randomDesign.quantity} | |
| Based on these parameters and hydrodynamic theory, suggest an optimised fin scale (0.5 to 2.0) and a brief engineering note. | |
| Respond ONLY with valid JSON: {"suggestedFinScale": number, "designNote": "string"} [/INST]`; | |
| const hfResponse = await hf.textGeneration({ | |
| model: 'mistralai/Mistral-7B-Instruct-v0.3', | |
| inputs: prompt, | |
| parameters: { max_new_tokens: 200, temperature: 0.15, return_full_text: false }, | |
| }); | |
| const raw = hfResponse.generated_text; | |
| const jsonMatch = raw.match(/\{[\s\S]*?\}/); | |
| if (!jsonMatch) throw new Error('No JSON in HF response'); | |
| const parsed = JSON.parse(jsonMatch[0]); | |
| res.json({ | |
| suggestedFinScale: Math.max(0.5, Math.min(2.0, parsed.suggestedFinScale || baseScale)), | |
| finDesign: randomDesign, | |
| resistance, | |
| designNote: parsed.designNote || fallback.designNote, | |
| source: 'huggingface', | |
| }); | |
| } catch (err) { | |
| console.warn('HF call failed, using algorithmic fallback:', err.message); | |
| res.json(fallback); | |
| } | |
| }); | |
| // ───────────────────────────────────────────────── | |
| // GET /api/health | |
| // ───────────────────────────────────────────────── | |
| app.get('/api/health', (_req, res) => { | |
| res.json({ status: 'ok', timestamp: new Date().toISOString() }); | |
| }); | |
| app.listen(PORT, '0.0.0.0', () => { | |
| console.log(`[NavSim Backend] Listening on port ${PORT}`); | |
| }); | |