UNPKG

herta

Version:

Advanced mathematics framework for scientific, engineering, and financial applications

346 lines (290 loc) 11.4 kB
/** * Fluid Dynamics module for herta.js * Provides computational fluid dynamics algorithms and simulations */ const matrix = require('../core/matrix'); const arithmetic = require('../core/arithmetic'); const fluidDynamics = {}; /** * Calculate Reynolds number for a flow * @param {number} density - Fluid density (kg/m³) * @param {number} velocity - Flow velocity (m/s) * @param {number} length - Characteristic length (m) * @param {number} viscosity - Dynamic viscosity (kg/(m·s)) * @returns {number} - Reynolds number (dimensionless) */ fluidDynamics.reynoldsNumber = function (density, velocity, length, viscosity) { return (density * velocity * length) / viscosity; }; /** * Calculate pressure drop in a pipe using Darcy-Weisbach equation * @param {number} frictionFactor - Darcy friction factor * @param {number} length - Pipe length (m) * @param {number} diameter - Pipe diameter (m) * @param {number} density - Fluid density (kg/m³) * @param {number} velocity - Flow velocity (m/s) * @returns {number} - Pressure drop (Pa) */ fluidDynamics.pressureDrop = function (frictionFactor, length, diameter, density, velocity) { return frictionFactor * (length / diameter) * (density * velocity ** 2) / 2; }; /** * Calculate friction factor using Colebrook-White equation * @param {number} reynoldsNumber - Reynolds number * @param {number} relativeRoughness - Relative roughness (ε/D) * @returns {number} - Darcy friction factor */ fluidDynamics.frictionFactor = function (reynoldsNumber, relativeRoughness) { // Initial guess (from Haaland equation) let f = (-1.8 * Math.log10((relativeRoughness / 3.7) ** 1.11 + 6.9 / reynoldsNumber)) ** -2; // Newton-Raphson method to solve Colebrook-White equation const maxIterations = 100; const tolerance = 1e-6; for (let i = 0; i < maxIterations; i++) { const term = relativeRoughness / 3.7 + 2.51 / (reynoldsNumber * Math.sqrt(f)); const f_old = f; // Update f using the derivative of Colebrook-White const df = 1 / (2 * Math.sqrt(f)) * 2.51 / (reynoldsNumber * term * Math.log(10)); f -= (1 / Math.sqrt(f) + 2 * Math.log10(term)) / (df); if (Math.abs(f - f_old) < tolerance) break; } return f; }; /** * Calculate head loss in a pipe system * @param {number} frictionFactor - Darcy friction factor * @param {number} length - Pipe length (m) * @param {number} diameter - Pipe diameter (m) * @param {number} velocity - Flow velocity (m/s) * @param {number} gravity - Gravitational acceleration (m/s²) * @returns {number} - Head loss (m) */ fluidDynamics.headLoss = function (frictionFactor, length, diameter, velocity, gravity = 9.81) { return frictionFactor * (length / diameter) * velocity ** 2 / (2 * gravity); }; /** * Calculate the Mach number for a fluid flow * @param {number} velocity - Flow velocity (m/s) * @param {number} speedOfSound - Speed of sound in the fluid (m/s) * @returns {number} - Mach number (dimensionless) */ fluidDynamics.machNumber = function (velocity, speedOfSound) { return velocity / speedOfSound; }; /** * Solve 1D advection equation using upwind scheme * @param {Array} initialValues - Initial field values * @param {number} velocity - Advection velocity * @param {number} dx - Spatial step size * @param {number} dt - Time step size * @param {number} totalTime - Total simulation time * @returns {Array} - Solution at each time step */ fluidDynamics.advection1D = function (initialValues, velocity, dx, dt, totalTime) { const nx = initialValues.length; const nt = Math.floor(totalTime / dt); const courantNumber = Math.abs(velocity) * dt / dx; if (courantNumber > 1) { console.warn(`Courant number (${courantNumber}) > 1, solution may be unstable`); } const solution = new Array(nt + 1); solution[0] = [...initialValues]; for (let n = 0; n < nt; n++) { solution[n + 1] = new Array(nx); for (let i = 0; i < nx; i++) { if (velocity > 0) { // Upwind scheme for positive velocity const upwindIndex = (i > 0) ? i - 1 : nx - 1; solution[n + 1][i] = solution[n][i] - velocity * dt / dx * (solution[n][i] - solution[n][upwindIndex]); } else { // Upwind scheme for negative velocity const upwindIndex = (i < nx - 1) ? i + 1 : 0; solution[n + 1][i] = solution[n][i] - velocity * dt / dx * (solution[n][upwindIndex] - solution[n][i]); } } } return solution; }; /** * Solve 1D diffusion equation using explicit scheme * @param {Array} initialValues - Initial field values * @param {number} diffusionCoefficient - Diffusion coefficient * @param {number} dx - Spatial step size * @param {number} dt - Time step size * @param {number} totalTime - Total simulation time * @returns {Array} - Solution at each time step */ fluidDynamics.diffusion1D = function (initialValues, diffusionCoefficient, dx, dt, totalTime) { const nx = initialValues.length; const nt = Math.floor(totalTime / dt); const alpha = diffusionCoefficient * dt / (dx * dx); if (alpha > 0.5) { console.warn(`Diffusion coefficient (${alpha}) > 0.5, solution may be unstable`); } const solution = new Array(nt + 1); solution[0] = [...initialValues]; for (let n = 0; n < nt; n++) { solution[n + 1] = new Array(nx); for (let i = 0; i < nx; i++) { const left = (i > 0) ? i - 1 : nx - 1; const right = (i < nx - 1) ? i + 1 : 0; solution[n + 1][i] = solution[n][i] + alpha * (solution[n][left] - 2 * solution[n][i] + solution[n][right]); } } return solution; }; /** * Solve 1D Burgers equation using the Lax-Wendroff scheme * @param {Array} initialValues - Initial field values * @param {number} viscosity - Fluid viscosity * @param {number} dx - Spatial step size * @param {number} dt - Time step size * @param {number} totalTime - Total simulation time * @returns {Array} - Solution at each time step */ fluidDynamics.burgers1D = function (initialValues, viscosity, dx, dt, totalTime) { const nx = initialValues.length; const nt = Math.floor(totalTime / dt); const solution = new Array(nt + 1); solution[0] = [...initialValues]; for (let n = 0; n < nt; n++) { solution[n + 1] = new Array(nx); for (let i = 0; i < nx; i++) { const im1 = (i > 0) ? i - 1 : nx - 1; const ip1 = (i < nx - 1) ? i + 1 : 0; // Nonlinear advection term const advection = -0.5 * solution[n][i] * (solution[n][ip1] - solution[n][im1]) / dx; // Diffusion term const diffusion = viscosity * (solution[n][im1] - 2 * solution[n][i] + solution[n][ip1]) / (dx * dx); solution[n + 1][i] = solution[n][i] + dt * (advection + diffusion); } } return solution; }; /** * Solve 2D incompressible Navier-Stokes equations using a simplified method * @param {Object} options - Simulation options * @returns {Object} - Simulation results */ fluidDynamics.navierstokes2D = function (options) { const { nx = 50, // Grid points in x direction ny = 50, // Grid points in y direction dx = 0.1, // Spatial step in x dy = 0.1, // Spatial step in y dt = 0.01, // Time step steps = 100, // Number of time steps rho = 1.0, // Density nu = 0.1, // Kinematic viscosity initialU = null, // Initial x-velocity field initialV = null, // Initial y-velocity field initialP = null // Initial pressure field } = options; // Initialize fields if not provided const u = Array(steps + 1).fill().map(() => Array(ny).fill().map(() => Array(nx).fill(0))); const v = Array(steps + 1).fill().map(() => Array(ny).fill().map(() => Array(nx).fill(0))); const p = Array(steps + 1).fill().map(() => Array(ny).fill().map(() => Array(nx).fill(0))); // Set initial conditions if (initialU) { for (let j = 0; j < ny; j++) { for (let i = 0; i < nx; i++) { u[0][j][i] = initialU[j][i]; } } } if (initialV) { for (let j = 0; j < ny; j++) { for (let i = 0; i < nx; i++) { v[0][j][i] = initialV[j][i]; } } } if (initialP) { for (let j = 0; j < ny; j++) { for (let i = 0; i < nx; i++) { p[0][j][i] = initialP[j][i]; } } } // Simplified solver - Demo implementation // For a real solver, we would need proper handling of boundary conditions // and more sophisticated algorithms for pressure-velocity coupling for (let n = 0; n < steps; n++) { // Simplified solver approach // 1. Compute intermediate velocity field (without pressure gradient) const u_star = Array(ny).fill().map(() => Array(nx).fill(0)); const v_star = Array(ny).fill().map(() => Array(nx).fill(0)); for (let j = 1; j < ny - 1; j++) { for (let i = 1; i < nx - 1; i++) { // x-momentum equation (simplified) const u_lap = (u[n][j][i + 1] + u[n][j][i - 1] + u[n][j + 1][i] + u[n][j - 1][i] - 4 * u[n][j][i]) / (dx * dx); u_star[j][i] = u[n][j][i] + dt * (nu * u_lap); // y-momentum equation (simplified) const v_lap = (v[n][j][i + 1] + v[n][j][i - 1] + v[n][j + 1][i] + v[n][j - 1][i] - 4 * v[n][j][i]) / (dy * dy); v_star[j][i] = v[n][j][i] + dt * (nu * v_lap); } } // 2. Solve pressure Poisson equation (simplified approach) const p_next = Array(ny).fill().map(() => Array(nx).fill(0)); // In a real solver, we would iterate to convergence for (let iter = 0; iter < 10; iter++) { for (let j = 1; j < ny - 1; j++) { for (let i = 1; i < nx - 1; i++) { const div = (u_star[j][i + 1] - u_star[j][i - 1]) / (2 * dx) + (v_star[j + 1][i] - v_star[j - 1][i]) / (2 * dy); p_next[j][i] = 0.25 * ( p[n][j][i + 1] + p[n][j][i - 1] + p[n][j + 1][i] + p[n][j - 1][i] - div * rho * dx * dy / dt ); } } } // 3. Correct velocity field using pressure gradient for (let j = 1; j < ny - 1; j++) { for (let i = 1; i < nx - 1; i++) { u[n + 1][j][i] = u_star[j][i] - (dt / rho) * (p_next[j][i + 1] - p_next[j][i - 1]) / (2 * dx); v[n + 1][j][i] = v_star[j][i] - (dt / rho) * (p_next[j + 1][i] - p_next[j - 1][i]) / (2 * dy); } } // Update pressure field for (let j = 0; j < ny; j++) { for (let i = 0; i < nx; i++) { p[n + 1][j][i] = p_next[j][i]; } } // Apply simplified boundary conditions applyBoundaryConditions(u[n + 1], v[n + 1], p[n + 1], nx, ny); } return { u, v, p }; // Helper function for boundary conditions function applyBoundaryConditions(u, v, p, nx, ny) { // Simplified no-slip boundary conditions for (let i = 0; i < nx; i++) { u[0][i] = 0; // Bottom wall u[ny - 1][i] = 0; // Top wall v[0][i] = 0; // Bottom wall v[ny - 1][i] = 0; // Top wall } for (let j = 0; j < ny; j++) { u[j][0] = 0; // Left wall u[j][nx - 1] = 0; // Right wall v[j][0] = 0; // Left wall v[j][nx - 1] = 0; // Right wall } // Neumann boundary condition for pressure for (let i = 0; i < nx; i++) { p[0][i] = p[1][i]; // Bottom wall p[ny - 1][i] = p[ny - 2][i]; // Top wall } for (let j = 0; j < ny; j++) { p[j][0] = p[j][1]; // Left wall p[j][nx - 1] = p[j][nx - 2]; // Right wall } } }; module.exports = fluidDynamics;