Spaces:
No application file
No application file
File size: 8,297 Bytes
9a16769 | 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 196 | '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 = `<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}`);
});
|