'use strict'; 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 = `[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}`); });