/** * 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, }; }