adn / backend /server.js
dimensionalpulsar's picture
Upload 38 files
9a16769 verified
Raw History Blame Contribute Delete
8.3 kB
'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}`);
});