Files
BrainWind/app/src/lib/modules/cylinder.ts
T

102 lines
2.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Strategy para cilindros de seção circular (NBR 6123:2023, sec. 6.2.1).
*
* Casos cobertos:
* - Silos / reservatórios / chaminés (eixo vertical)
* - Tubulações aéreas (eixo horizontal)
* - Topo aberto (Cpi específico pela Tabela 13 / sec. 6.3.2.3)
*/
import { getCpeCylinder, reynoldsCylinder, isSupercritical } from '../nbr-tables/table-13';
import { computeCpiCylinderOpenTop } from '../internal-pressure';
import { clampCpi } from '../internal-pressure';
export type CylinderEndType = 'closed' | 'open-top' | 'open-bottom' | 'open-both';
export interface CylinderInput {
/** Diâmetro (m) */
d: number;
/** Altura (m) */
h: number;
/** Velocidade característica Vk (m/s) */
vk: number;
/** Tipo de superfície */
surface: 'rough' | 'smooth';
/** Tipo de extremidade */
endType: CylinderEndType;
/** Cpi base (usado se fechado) */
baseCpi?: number;
}
export interface CylinderPoint {
angle: number;
cpe: number;
pressureKN_m2: number;
}
export interface CylinderResult {
re: number;
supercritical: boolean;
hOverD: number;
cpi: number;
cpiNote: string;
profile: CylinderPoint[];
/** Força horizontal total por unidade de altura (kN/m) — integração numérica */
forcePerHeightKN_m: number;
}
/** Integração numérica da força de arrasto em torno do cilindro */
function integrateCylinderForce(
profile: CylinderPoint[],
d: number,
): number {
let total = 0;
for (let i = 0; i < profile.length - 1; i++) {
const a = profile[i];
const b = profile[i + 1];
const da = (b.angle - a.angle) * Math.PI / 180;
const avg = (a.pressureKN_m2 + b.pressureKN_m2) / 2;
const radius = d / 2;
total += avg * da * radius;
}
return Number(total.toFixed(3));
}
export function calculateCylinder(input: CylinderInput): CylinderResult {
const { d, h, vk, surface, endType } = input;
const hOverD = h / d;
const re = reynoldsCylinder(vk, d);
const supercritical = isSupercritical(re);
let cpi = input.baseCpi ?? 0;
let cpiNote = 'Edição fechada — usando Cpi global.';
if (endType === 'open-top') {
cpi = clampCpi(computeCpiCylinderOpenTop(hOverD));
cpiNote = `Topo aberto: Cpi = ${cpi} (sec. 6.3.2.3, h/d = ${hOverD.toFixed(2)}).`;
} else if (endType === 'open-bottom') {
cpi = -0.5;
cpiNote = 'Base aberta: Cpi = 0,5 (conservador).';
} else if (endType === 'open-both') {
cpi = -0.7;
cpiNote = 'Topo e base abertos: Cpi = 0,7 (conservador).';
}
const angles = [0, 15, 30, 45, 60, 75, 90, 105, 120, 135, 150, 165, 180];
const profile: CylinderPoint[] = angles.map((angle) => {
const cpe = getCpeCylinder(angle, hOverD, surface);
const p = (0.613 * Math.pow(vk, 2) * (cpe - cpi)) / 1000; // kN/m²
return { angle, cpe, pressureKN_m2: Number(p.toFixed(3)) };
});
const forcePerHeightKN_m = integrateCylinderForce(profile, d);
return {
re,
supercritical,
hOverD,
cpi,
cpiNote,
profile,
forcePerHeightKN_m,
};
}