feat: unified 0 and 90 degree PDF envelope and category descriptors
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user