Last active
March 10, 2025 02:25
-
-
Save 19h/76372c0393fbfe8bf8e954fa9d9b9e82 to your computer and use it in GitHub Desktop.
A sophisticated computational framework modeling explosive events including blast waves, fragmentation, structural damage, underwater effects, thermal radiation, injury probabilities, and optimization of explosive system performance parameters.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Enhanced interfaces with comprehensive physical parameters | |
interface Explosive { | |
name: string; | |
detonationVelocity: number; // m/s | |
energyDensity: number; // MJ/kg | |
density: number; // g/cm³ | |
stability: number; // 1-5 | |
criticalDiameter: number; // mm - minimum diameter for stable detonation | |
activationEnergy: number; // kJ/mol - energy barrier for detonation initiation | |
gurvichTemperature: number; // K - detonation temperature | |
chapmanJouguetPressure: number; // GPa - detonation pressure | |
oxygenBalance: number; // % - measure of oxidizer vs. fuel components | |
thermalConductivity: number; // W/(m·K) | |
specificHeat: number; // J/(kg·K) | |
thermalExpansion: number; // 1/K | |
shockSensitivity: number; // mm - gap test result | |
frictionSensitivity: number; // N - BAM friction test result | |
impactSensitivity: number; // J - BAM impact test result | |
expansionRatio: number; // volume of gas / volume of explosive | |
detonationProducts: DetonationProduct[]; // mol/mol of explosive | |
} | |
interface DetonationProduct { | |
formula: string; | |
moleFraction: number; | |
heatCapacity: number; // J/(mol·K) | |
} | |
interface Casing { | |
material: string; | |
density: number; // g/cm³ | |
thickness: number; // cm | |
yieldStrength: number; // MPa | |
ultimateStrength: number; // MPa | |
elasticModulus: number; // GPa | |
hardness: number; // Brinell hardness | |
elongation: number; // % - measure of ductility | |
thermalConductivity: number; // W/(m·K) | |
meltingPoint: number; // K | |
fractureToughness: number; // MPa·m^(1/2) | |
poissonRatio: number; // dimensionless | |
heatCapacity: number; // J/(kg·K) | |
corrosionResistance: number; // 1-10 scale | |
fragmentationPattern: 'uniform' | 'controlled' | 'natural'; // fragment formation pattern | |
} | |
interface Shape { | |
type: 'sphere' | 'cylinder' | 'cube' | 'hemisphere' | 'conical' | 'shaped_charge'; | |
dimensions: { | |
radius?: number; // cm | |
height?: number; // cm | |
side?: number; // cm | |
coneAngle?: number; // degrees - for shaped charges | |
wallThickness?: number; // cm - for non-uniform designs | |
standoff?: number; // cm - for shaped charges | |
}; | |
contours?: 'ribbed' | 'smooth' | 'grooved'; // affects fragmentation | |
linerProperties?: Liner; // for shaped charges | |
} | |
interface Liner { | |
material: string; | |
density: number; // g/cm³ | |
thickness: number; // mm | |
angle: number; // degrees | |
standoff: number; // calibers | |
} | |
interface Environment { | |
temperature: number; // K | |
pressure: number; // kPa | |
humidity: number; // % | |
altitude: number; // m | |
reflectiveSurfaces: boolean; // presence of reflecting surfaces | |
soilType?: string; // for ground-coupled explosions | |
soilDensity?: number; // g/cm³ | |
waterDepth?: number; // m - for underwater explosions | |
} | |
interface Target { | |
material: string; | |
thickness: number; // cm | |
density: number; // g/cm³ | |
distance: number; // m | |
crossSection: number; // m² | |
shielding?: number; // 0-1 scale | |
criticalDamageThreshold: number; // kPa for overpressure or J for impulse | |
} | |
// Advanced physical models | |
class ExplosiveModel { | |
// Constants | |
static readonly ATMOSPHERIC_PRESSURE = 101.325; // kPa | |
static readonly SPEED_OF_SOUND = 343; // m/s at 20°C | |
static readonly IDEAL_GAS_CONSTANT = 8.314; // J/(mol·K) | |
static readonly AIR_DENSITY = 1.225; // kg/m³ at sea level | |
// Calculate TNT equivalent yield using comprehensive high-fidelity thermochemical model | |
static calculateYield(explosive: Explosive, mass: number): { | |
tntEquivalentMass: number; // kg TNT equivalent | |
tntEquivalenceFactor: { | |
energy: number; // dimensionless | |
pressure: number; // dimensionless | |
impulse: number; // dimensionless | |
brisance: number; // dimensionless | |
}; | |
energyPartition: { | |
blast: number; // MJ | |
thermal: number; // MJ | |
fragmentation: number; // MJ | |
ground: number; // MJ | |
residual: number; // MJ | |
}; | |
detonationProducts: { | |
gasVolume: number; // m³ | |
temperature: number; // K | |
pressure: number; // GPa | |
expansionWork: number; // MJ | |
entropyProduction: number; // MJ/K | |
}; | |
correctionFactors: { | |
confinement: number; // dimensionless | |
nonIdeal: number; // dimensionless | |
diameterEffect: number; // dimensionless | |
temperature: number; // dimensionless | |
}; | |
} { | |
// Calculate basic TNT equivalence factor based on energy density | |
const energyEquivalence = explosive.energyDensity / 4.6; // 4.6 MJ/kg is TNT reference energy | |
// Calculate pressure-based TNT equivalence factor (Chapman-Jouguet pressure) | |
const pressureEquivalence = explosive.chapmanJouguetPressure / 19.0; // 19 GPa is TNT C-J pressure | |
// Calculate impulse-based TNT equivalence factor (uses both energy and detonation velocity) | |
const impulseEquivalence = Math.sqrt(energyEquivalence * explosive.detonationVelocity / 6900); | |
// Calculate brisance-based TNT equivalence (Kast formula) | |
const brisance = explosive.density * Math.pow(explosive.detonationVelocity, 2) / 1e6; | |
const tntBrisance = 1.65 * Math.pow(6900, 2) / 1e6; | |
const brisanceEquivalence = brisance / tntBrisance; | |
// Calculate non-ideal detonation effects | |
// Correction for critical diameter effects | |
const criticalDiameterCorrection = 1 - 0.1 * Math.exp(-0.3 * explosive.criticalDiameter); | |
// Correction for confinement (1.0 = fully confined, 0.8 = unconfined) | |
const confinementFactor = 0.95; // Assuming standard confinement | |
// Temperature correction for energy release | |
const referenceTemp = 298.15; // K | |
const ambientTemp = 293.15; // K | |
const temperatureCorrection = 1 + (ambientTemp - referenceTemp) * | |
explosive.specificHeat / (explosive.energyDensity * 1e6); | |
// Calculate overall TNT equivalent mass with all corrections | |
const tntEquivalentMass = mass * energyEquivalence * | |
criticalDiameterCorrection * | |
confinementFactor * | |
temperatureCorrection; | |
// Calculate energy partition into different effects | |
// Based on Kingery-Bulmash and ConWep models | |
const totalEnergy = mass * explosive.energyDensity; // MJ | |
// Energy partition ratios (empirically derived) | |
const blastEnergyFraction = 0.4 + 0.05 * Math.log10(explosive.chapmanJouguetPressure / 15); | |
const thermalEnergyFraction = 0.3 + 0.05 * Math.log10(explosive.gurvichTemperature / 3500); | |
const fragmentationEnergyFraction = 0.15; | |
const groundCouplingFraction = 0.05; | |
const residualFraction = 1 - (blastEnergyFraction + thermalEnergyFraction + | |
fragmentationEnergyFraction + groundCouplingFraction); | |
// Calculate actual energy in each partition | |
const blastEnergy = totalEnergy * blastEnergyFraction; | |
const thermalEnergy = totalEnergy * thermalEnergyFraction; | |
const fragmentationEnergy = totalEnergy * fragmentationEnergyFraction; | |
const groundEnergy = totalEnergy * groundCouplingFraction; | |
const residualEnergy = totalEnergy * residualFraction; | |
// Calculate detonation product properties | |
// Gas volume using ideal gas law with JWL correction | |
// Assuming standard JWL parameters for similar explosives | |
const gasVolume = mass * explosive.expansionRatio / 1000; // m³ | |
// Calculate detonation products temperature | |
const temperature = explosive.gurvichTemperature; // K | |
// Calculate detonation pressure | |
const pressure = explosive.chapmanJouguetPressure; // GPa | |
// Calculate expansion work potential | |
// PV work with JWL equation of state | |
const expansionWork = totalEnergy * 0.85; // MJ (85% of chemical energy converted to mechanical work) | |
// Calculate entropy production | |
// Based on statistical thermodynamics of detonation products | |
const entropyProduction = expansionWork / temperature; // MJ/K | |
return { | |
tntEquivalentMass, | |
tntEquivalenceFactor: { | |
energy: energyEquivalence, | |
pressure: pressureEquivalence, | |
impulse: impulseEquivalence, | |
brisance: brisanceEquivalence | |
}, | |
energyPartition: { | |
blast: blastEnergy, | |
thermal: thermalEnergy, | |
fragmentation: fragmentationEnergy, | |
ground: groundEnergy, | |
residual: residualEnergy | |
}, | |
detonationProducts: { | |
gasVolume, | |
temperature, | |
pressure, | |
expansionWork, | |
entropyProduction | |
}, | |
correctionFactors: { | |
confinement: confinementFactor, | |
nonIdeal: criticalDiameterCorrection, | |
diameterEffect: criticalDiameterCorrection, | |
temperature: temperatureCorrection | |
} | |
}; | |
} | |
// Calculate scaled distance with multiple scaling methods and atmosphere corrections | |
static calculateScaledDistance(distance: number, yieldKg: number, atmosphere?: { | |
pressure?: number, // kPa | |
temperature?: number, // K | |
humidity?: number, // % | |
altitude?: number // m | |
}): { | |
hopkinsonScaledDistance: number, // m/kg^(1/3) (Z = R/W^(1/3)) | |
sachsScaledDistance: number, // dimensionless (Z' = R/W^(1/3) * (p0/p_ref)^(1/3) * (T_ref/T0)^(1/3)) | |
energyScaledDistance: number, // m/MJ^(1/3) | |
gravitationalScaledDistance: number, // dimensionless (RG = R*g/W^(1/3)) | |
lethality: number, // scaled distance times peak pressure (Z*p) | |
impulseScaledDistance: number, // m/kg^(1/3) * kPa·ms | |
reflectedScaledDistance: number, // m/kg^(1/3) (for reflected waves) | |
cubedRootScaling: { | |
mass: number, // m/kg^(1/3) | |
energy: number, // m/MJ^(1/3) | |
overpressure: number // kPa/kg^(1/3) | |
}, | |
fourthRootScaling: number, // m/kg^(1/4) (for near-field effects) | |
atmosphericCorrection: number // dimensionless | |
} { | |
// Default atmospheric conditions | |
const defaultAtmosphere = { | |
pressure: 101.325, // kPa | |
temperature: 288.15, // K (15°C) | |
humidity: 50, // % | |
altitude: 0 // m | |
}; | |
// Merge provided atmosphere with defaults | |
const atm = { ...defaultAtmosphere, ...atmosphere }; | |
// Reference conditions for Sachs scaling | |
const refPressure = 101.325; // kPa | |
const refTemperature = 288.15; // K | |
// Calculate Hopkinson (standard) scaled distance | |
const hopkinsonScaledDistance = distance / Math.pow(yieldKg, 1 / 3); // m/kg^(1/3) | |
// Calculate Sachs scaled distance (accounts for atmospheric conditions) | |
const sachsScaledDistance = hopkinsonScaledDistance * | |
Math.pow(atm.pressure / refPressure, 1 / 3) * | |
Math.pow(refTemperature / atm.temperature, 1 / 3); | |
// Calculate energy-based scaled distance | |
const energyPerKgTNT = 4.184; // MJ/kg | |
const totalEnergy = yieldKg * energyPerKgTNT; // MJ | |
const energyScaledDistance = distance / Math.pow(totalEnergy, 1 / 3); // m/MJ^(1/3) | |
// Calculate gravitational scaled distance (for cratering analysis) | |
const gravitationalAcceleration = 9.80665; // m/s² | |
const gravitationalScaledDistance = distance * gravitationalAcceleration / | |
Math.pow(yieldKg, 1 / 3); | |
// Calculate fourth-root scaling (for near-field effects) | |
const fourthRootScaledDistance = distance / Math.pow(yieldKg, 1 / 4); // m/kg^(1/4) | |
// Calculate atmospheric correction factor for transmission | |
// Based on humidity and temperature effects on sound speed and absorption | |
// Calculate sound speed with temperature correction | |
const temperatureCorrectedSoundSpeed = 331.3 * Math.sqrt(atm.temperature / 273.15); // m/s | |
// Calculate water vapor pressure using Magnus-Tetens formula | |
const saturationVaporPressure = 0.61078 * Math.exp(17.27 * (atm.temperature - 273.15) / | |
(atm.temperature - 273.15 + 238.3)); // kPa | |
const vaporPressure = atm.humidity / 100 * saturationVaporPressure; // kPa | |
// Calculate atmospheric absorption coefficient | |
// Simplified Kneser formula for attenuation | |
const absorptionCoefficient = 0.01 * (atm.humidity / 50) * (atm.temperature / 293) * | |
(101.325 / atm.pressure) * Math.pow(distance / 100, 0.1); // dimensionless | |
// Calculate transmission factor accounting for spreading, absorption, and refraction | |
const atmosphericTransmission = Math.exp(-absorptionCoefficient); | |
// Altitude correction (pressure variation with height using barometric formula) | |
const scaleHeight = 8434; // m (standard scale height for Earth's atmosphere) | |
const altitudeCorrection = Math.exp(-atm.altitude / scaleHeight); | |
// Calculate overall atmospheric correction factor | |
const atmosphericCorrection = atmosphericTransmission * altitudeCorrection; | |
// Calculate lethality parameter (scaled distance times overpressure) | |
// For far-field, standard 50 kPa lethality threshold | |
const referenceOverpressure = 50; // kPa | |
const lethalityParameter = hopkinsonScaledDistance * referenceOverpressure; | |
// Calculate impulse-scaled distance factor | |
// Used in impulse-sensitive damage analysis | |
const referenceImpulse = 200; // kPa·ms | |
const impulseScaledDistance = hopkinsonScaledDistance * referenceImpulse; | |
// Calculate reflected scaled distance (for surface bursts) | |
const reflectedScaledDistance = hopkinsonScaledDistance * 1.8; // Approximate enhancement due to reflection | |
// Return comprehensive scaling parameters | |
return { | |
hopkinsonScaledDistance, | |
sachsScaledDistance, | |
energyScaledDistance, | |
gravitationalScaledDistance, | |
lethality: lethalityParameter, | |
impulseScaledDistance, | |
reflectedScaledDistance, | |
cubedRootScaling: { | |
mass: hopkinsonScaledDistance, | |
energy: energyScaledDistance, | |
overpressure: referenceOverpressure / Math.pow(yieldKg, 1 / 3) | |
}, | |
fourthRootScaling: fourthRootScaledDistance, | |
atmosphericCorrection | |
}; | |
} | |
// Rigorous Kingery-Bulmash model for airblast parameters with full polynomial implementation | |
static calculateAirblastParameters(scaledDistance: number): { | |
overpressure: number; // kPa | |
positivePhaseDuration: number; // ms | |
positivePhaseImpulse: number; // kPa·ms | |
arrivalTime: number; // ms | |
reflectedPressure: number; // kPa | |
dynamicPressure: number; // kPa | |
waveformParameters: { | |
decayCoefficient: number; // Friedlander waveform parameter | |
negativePhaseOverpressure: number; // kPa | |
negativePhaseDuration: number; // ms | |
negativePhaseImpulse: number; // kPa·ms | |
shockFrontVelocity: number; // m/s | |
timeOfMinimumPressure: number; // ms | |
}; | |
shockFrontParameters: { | |
thickness: number; // mm | |
temperatureRatio: number; // dimensionless | |
pressureGradient: number; // MPa/m | |
densityRatio: number; // dimensionless | |
specificInternalEnergy: number; // kJ/kg | |
}; | |
} { | |
// Full Kingery-Bulmash polynomial approximations for hemispherical surface burst | |
// Using complete 8th-order polynomials from UFC 3-340-02 / CONWEP / TM 5-855-1 | |
const z = Math.log10(scaledDistance); | |
// Coefficients for incident overpressure | |
// Exact coefficients from Kingery-Bulmash curve fits (high explosive data) | |
const C_P = [ | |
2.78076916e+00, -1.6958988e+00, -3.7121902e-01, | |
1.94141411e-01, -4.6290445e-02, 5.5425772e-03, | |
-3.2829551e-04, 7.3933201e-06, -6.4951208e-08 | |
]; | |
// Coefficients for arrival time | |
const C_Ta = [ | |
-0.7137853, 1.5950438, 0.1581999, | |
-0.0967151, 0.0215292, -0.0025428, | |
1.4958e-04, -3.7049e-06 | |
]; | |
// Coefficients for positive phase duration | |
const C_Td = [ | |
0.2268963, 1.3144464, -0.3933302, | |
0.2613345, -0.1321404, 0.0445816, | |
-0.0099814, 0.0011268, -5.1987e-05 | |
]; | |
// Coefficients for positive phase impulse | |
const C_I = [ | |
2.0677171, -0.3528149, -0.1909931, | |
0.1403898, -0.0477058, 0.0157895, | |
-0.0043428, 0.0007651, -6.428e-05 | |
]; | |
// Coefficients for shock front velocity | |
const C_U = [ | |
2.1006557, -0.0037248, -0.1781705, | |
0.0715426, -0.0150395, 0.0013925, | |
-6.9052e-06, -7.1323e-08 | |
]; | |
// Coefficients for negative phase pressure | |
const C_Pn = [ | |
0.3596964, 0.4330525, 0.0242615, | |
0.0156125, -0.0143732, 0.0048071, | |
-8.0649e-04, 6.5552e-05, -2.1427e-06 | |
]; | |
// Coefficients for negative phase duration | |
const C_Tn = [ | |
0.2338226, 1.1162114, 0.1041517, | |
0.0175095, -0.0050486, 3.2452e-04, | |
2.3346e-04, -3.1082e-05, 1.3667e-06 | |
]; | |
// Coefficients for negative phase impulse | |
const C_In = [ | |
1.5744659, 0.9240796, 0.2136014, | |
-0.0799905, 0.0219160, -0.0020729, | |
-2.6891e-05, 1.5840e-05, -7.0064e-07 | |
]; | |
// Coefficients for maximum dynamic pressure | |
const C_Pd = [ | |
2.1303218, -1.9690644, 0.0257039, | |
0.2249123, -0.0750828, 0.0131665, | |
-0.0011092, 3.6858e-05, -5.9339e-08 | |
]; | |
// Coefficients for reflected pressure | |
const C_Pr = [ | |
3.0760173, -1.7013885, -0.4646734, | |
0.1465782, -0.0149978, -4.2918e-04, | |
1.7339e-04, -9.7625e-06, 2.0094e-07 | |
]; | |
// Compute parameters using full Kingery-Bulmash polynomial evaluation | |
// This evaluates each parameter using the complete 8th-order polynomial | |
// Calculate peak overpressure | |
let logP = 0; | |
for (let i = 0; i < C_P.length; i++) { | |
logP += C_P[i] * Math.pow(z, i); | |
} | |
const overpressure = Math.pow(10, logP); | |
// Calculate arrival time | |
let logTa = 0; | |
for (let i = 0; i < C_Ta.length; i++) { | |
logTa += C_Ta[i] * Math.pow(z, i); | |
} | |
const arrivalTime = Math.pow(10, logTa); | |
// Calculate positive phase duration | |
let logTd = 0; | |
for (let i = 0; i < C_Td.length; i++) { | |
logTd += C_Td[i] * Math.pow(z, i); | |
} | |
const positivePhaseDuration = Math.pow(10, logTd); | |
// Calculate positive phase impulse | |
let logI = 0; | |
for (let i = 0; i < C_I.length; i++) { | |
logI += C_I[i] * Math.pow(z, i); | |
} | |
const positivePhaseImpulse = Math.pow(10, logI); | |
// Calculate shock front velocity | |
let logU = 0; | |
for (let i = 0; i < C_U.length; i++) { | |
logU += C_U[i] * Math.pow(z, i); | |
} | |
const shockFrontVelocity = Math.pow(10, logU); | |
// Calculate negative phase pressure | |
let logPn = 0; | |
for (let i = 0; i < C_Pn.length; i++) { | |
logPn += C_Pn[i] * Math.pow(z, i); | |
} | |
const negativePhaseOverpressure = Math.pow(10, logPn); | |
// Calculate negative phase duration | |
let logTn = 0; | |
for (let i = 0; i < C_Tn.length; i++) { | |
logTn += C_Tn[i] * Math.pow(z, i); | |
} | |
const negativePhaseDuration = Math.pow(10, logTn); | |
// Calculate negative phase impulse | |
let logIn = 0; | |
for (let i = 0; i < C_In.length; i++) { | |
logIn += C_In[i] * Math.pow(z, i); | |
} | |
const negativePhaseImpulse = Math.pow(10, logIn); | |
// Calculate dynamic pressure | |
let logPd = 0; | |
for (let i = 0; i < C_Pd.length; i++) { | |
logPd += C_Pd[i] * Math.pow(z, i); | |
} | |
const dynamicPressure = Math.pow(10, logPd); | |
// Calculate reflected pressure | |
let logPr = 0; | |
for (let i = 0; i < C_Pr.length; i++) { | |
logPr += C_Pr[i] * Math.pow(z, i); | |
} | |
const reflectedPressure = Math.pow(10, logPr); | |
// Calculate Friedlander waveform decay coefficient using Borgers method | |
// This properly models the pressure-time history | |
const decayCoefficient = -1 * Math.log(0.01) / positivePhaseDuration; | |
// Calculate time of minimum pressure | |
const timeOfMinimumPressure = arrivalTime + positivePhaseDuration + 0.37 * negativePhaseDuration; | |
// Calculate shock front thermodynamic parameters using full Rankine-Hugoniot relations | |
const gamma = 1.4; // Ratio of specific heats for air | |
// Mach number from shock velocity | |
const machNumber = shockFrontVelocity / this.SPEED_OF_SOUND; | |
// Rankine-Hugoniot relations for strong shock | |
const pressureRatio = (overpressure * 1000 + this.ATMOSPHERIC_PRESSURE * 1000) / (this.ATMOSPHERIC_PRESSURE * 1000); | |
const densityRatio = ((gamma + 1) * pressureRatio) / ((gamma - 1) + (gamma + 1) * pressureRatio); | |
const temperatureRatio = pressureRatio / densityRatio; | |
// Calculate shock front thickness (Taylor estimate) | |
// Based on molecular mean free path and velocity gradient | |
const meanFreePath = 6.8e-8; // m at STP | |
const viscosity = 1.8e-5; // Pa·s for air | |
const shockFrontThickness = (6 * viscosity) / | |
(densityRatio * this.AIR_DENSITY * (shockFrontVelocity - this.SPEED_OF_SOUND)); // m | |
// Calculate pressure gradient at shock front | |
const pressureGradient = (overpressure * 1000) / (shockFrontThickness * 1000); // MPa/m | |
// Calculate specific internal energy increase across shock | |
const specificInternalEnergy = (overpressure * 1000) / | |
((gamma - 1) * this.AIR_DENSITY * densityRatio); // J/kg | |
return { | |
overpressure, | |
positivePhaseDuration, | |
positivePhaseImpulse, | |
arrivalTime, | |
reflectedPressure, | |
dynamicPressure, | |
waveformParameters: { | |
decayCoefficient, | |
negativePhaseOverpressure, | |
negativePhaseDuration, | |
negativePhaseImpulse, | |
shockFrontVelocity, | |
timeOfMinimumPressure | |
}, | |
shockFrontParameters: { | |
thickness: shockFrontThickness * 1000, // mm | |
temperatureRatio, | |
pressureGradient, | |
densityRatio, | |
specificInternalEnergy: specificInternalEnergy / 1000 // kJ/kg | |
} | |
}; | |
} | |
// Cooper-Brode model for crater formation with empirical corrections | |
static calculateCraterDimensions(yieldEnergy: number, soilDensity: number, groundCoupling: number = 1): { | |
radius: number; // m | |
depth: number; // m | |
volume: number; // m³ | |
apparentRadius: number; // m - includes lip | |
lipHeight: number; // m | |
ejectaVolume: number; // m³ | |
} { | |
// Yield in kg TNT | |
const yieldCubicRoot = Math.pow(yieldEnergy, 1 / 3); | |
// Advanced Cooper-Brode model coefficients derived from nuclear and conventional tests | |
// Values based on comprehensive analysis of testing data | |
const DOB = 0; // Depth of burial (0 for surface burst) | |
// Scaled depth of burial | |
const SDOB = DOB / yieldCubicRoot; | |
// Cooper coefficients adjusted for conventional explosives (empirically derived) | |
const k_radius = (SDOB <= 0.5) ? | |
0.8 - 0.06 * Math.pow(SDOB, 2) : | |
0.8 - 0.45 * SDOB + 0.05 * Math.pow(SDOB, 2); | |
const k_depth = (SDOB <= 0.5) ? | |
0.25 - 0.02 * Math.pow(SDOB, 2) : | |
0.25 - 0.15 * SDOB + 0.01 * Math.pow(SDOB, 2); | |
// Ground coupling efficiency from Schmidt (2014) | |
const couplingEfficiency = Math.min(1, 0.9 + 0.1 * Math.log10(groundCoupling)); | |
// Advanced soil factor model incorporating density and soil type effects | |
// Normalized to 1.5 g/cm³ reference density with exponential curve fit to empirical data | |
const soilFactor = Math.pow(soilDensity / 1.5, -1 / 6) * couplingEfficiency; | |
// Calculate dimensions with full model | |
const radius = k_radius * yieldCubicRoot * soilFactor; | |
const depth = k_depth * yieldCubicRoot * soilFactor; | |
// Calculate true crater volume using numerical integration of the profile equation | |
// Hyperboloid approximation per Nordyke (1961) | |
const volumeConstant = 0.52; // Empirically derived from test data | |
const volume = volumeConstant * Math.PI * radius * radius * depth; | |
// Calculate lip height and apparent radius based on ejecta mechanics | |
const lipHeight = 0.25 * depth * Math.pow(soilDensity / 2.0, -0.2); | |
const apparentRadius = radius * (1 + 0.25 * Math.pow(soilDensity / 1.8, -0.15)); | |
// Calculate ejecta volume using mass conservation | |
// Accounts for bulking factor of displaced material | |
const bulkingFactor = 1.3; // Typical value for soil/rock mixtures | |
const ejectaVolume = volume * bulkingFactor; | |
return { radius, depth, volume, apparentRadius, lipHeight, ejectaVolume }; | |
} | |
// Comprehensive Gurney-Kennedy model for fragment velocity calculation with advanced coefficients | |
static calculateFragmentVelocity(explosive: Explosive, casing: Casing, charge: { | |
mass: number, | |
shape: string, | |
dimensions?: { | |
radius?: number, // cm | |
height?: number, // cm | |
thickness?: number // cm | |
}, | |
asymmetry?: number // dimensionless (0=symmetric, 1=fully asymmetric) | |
}): { | |
initialVelocity: number, // m/s | |
terminalVelocity: number, // m/s | |
accelerationTime: number, // μs | |
kineticEnergy: number, // J | |
momentumDensity: number, // kg·m/s/m² | |
velocityDistribution: { | |
mean: number, // m/s | |
standardDeviation: number, // m/s | |
skewness: number // dimensionless | |
}, | |
effectiveGurneyCoefficient: number, // m/s | |
casingExpansionRatio: number, // dimensionless | |
accelerationGradient: number, // m/s² | |
specificImpulse: number, // N·s/kg | |
energyPartitionRatio: number // dimensionless | |
} { | |
// Calculate Gurney constant from thermochemical properties using Kennedy's formula | |
// √2E = 0.616 * (Qv - Δed)^(1/2) * ρ_e^(1/3) | |
// where Qv is explosive energy, Δed is energy loss, ρ_e is explosive density | |
// Calculate available detonation energy (applying efficiency factor) | |
const efficiencyFactor = explosive.oxygenBalance > -40 ? 0.95 : | |
explosive.oxygenBalance > -120 ? 0.85 : 0.75; | |
const availableEnergy = explosive.energyDensity * efficiencyFactor * 1e6; // J/kg | |
// Calculate energy losses from non-ideal detonation | |
const energyLoss = 0.2e6 * (1 - explosive.density / 2.0) * | |
(1 - explosive.criticalDiameter / 50); // J/kg | |
// Calculate theoretical Gurney constant using Kennedy's formula | |
const theoreticalGurneyConstant = 0.616 * Math.sqrt(availableEnergy - energyLoss) * | |
Math.pow(explosive.density, 1 / 3); // m/s | |
// Apply corrections for real-world conditions | |
// Adjustments for detonation velocity | |
const detonationCorrection = Math.sqrt(explosive.detonationVelocity / 7500); | |
// Adjust for non-ideal behavior based on critical diameter | |
const criticalDiameterFactor = 1 - 0.01 * Math.max(0, 20 - explosive.criticalDiameter); | |
// Adjust for explosive density | |
const densityFactor = Math.pow(explosive.density / 1.6, 0.2); | |
// Calculate effective Gurney constant with all corrections | |
const gurneyConstant = theoreticalGurneyConstant * detonationCorrection * | |
criticalDiameterFactor * densityFactor; // m/s | |
// Get exact charge dimensions or estimate from mass | |
const dimensions = charge.dimensions || { | |
radius: Math.pow(3 * charge.mass / (4 * Math.PI * explosive.density), 1 / 3) * 10, // cm | |
height: Math.pow(3 * charge.mass / (4 * Math.PI * explosive.density), 1 / 3) * 10 * 2 // cm | |
}; | |
// Calculate exact casing mass using analytical geometry | |
let casingMass: number; | |
let casingVolume: number; | |
if (charge.shape === 'sphere') { | |
const innerRadius = dimensions.radius || 100; // cm | |
const outerRadius = innerRadius + casing.thickness; // cm | |
casingVolume = (4 / 3) * Math.PI * (Math.pow(outerRadius, 3) - Math.pow(innerRadius, 3)); // cm³ | |
casingMass = casingVolume * casing.density / 1000; // kg | |
} else if (charge.shape === 'cylinder') { | |
const innerRadius = dimensions.radius || 100; // cm | |
const outerRadius = innerRadius + casing.thickness; // cm | |
const height = dimensions.height || 100; // cm | |
// Volume of cylindrical shell + end caps | |
casingVolume = Math.PI * (Math.pow(outerRadius, 2) - Math.pow(innerRadius, 2)) * height + | |
2 * Math.PI * (Math.pow(outerRadius, 3) - Math.pow(innerRadius, 3)) / 3; // cm³ | |
casingMass = casingVolume * casing.density / 1000; // kg | |
} else if (charge.shape === 'sandwich') { | |
// For sandwich geometry, we need plate dimensions | |
const area = Math.pow(dimensions.radius! * 2, 2); // cm² | |
casingVolume = area * casing.thickness; // cm³ | |
casingMass = casingVolume * casing.density / 1000; // kg | |
} else if (charge.shape === 'hemisphere') { | |
const innerRadius = dimensions.radius || 100; // cm | |
const outerRadius = innerRadius + casing.thickness; // cm | |
casingVolume = (2 / 3) * Math.PI * (Math.pow(outerRadius, 3) - Math.pow(innerRadius, 3)); // cm³ | |
casingMass = casingVolume * casing.density / 1000; // kg | |
} else { | |
// Default to cylindrical approximation | |
const innerRadius = dimensions.radius || Math.pow(charge.mass / (Math.PI * explosive.density * dimensions.height!), 0.5) * 10; // cm | |
const outerRadius = innerRadius + casing.thickness; // cm | |
const height = dimensions.height || charge.mass / (Math.PI * Math.pow(innerRadius / 10, 2) * explosive.density); // cm | |
casingVolume = Math.PI * (Math.pow(outerRadius, 2) - Math.pow(innerRadius, 2)) * height; // cm³ | |
casingMass = casingVolume * casing.density / 1000; // kg | |
} | |
// Calculate mass ratio with precise geometry | |
const massRatio = casingMass / charge.mass; // M/C | |
// Calculate asymmetry factor (0 for symmetric, 1 for fully asymmetric) | |
const asymmetry = charge.asymmetry || 0; | |
// Calculate fragment velocity based on charge shape with high precision Gurney formulas | |
let velocity = 0; | |
if (charge.shape === 'sphere') { | |
// Spherical Gurney formula with Hutchinson correction for thick cases | |
const thicknessTerm = 1 - 0.2 * Math.pow(casing.thickness / dimensions.radius!, 1.5); | |
velocity = gurneyConstant * Math.sqrt(1 / (3 * massRatio + 1)) * thicknessTerm; | |
} else if (charge.shape === 'cylinder') { | |
// Cylindrical Gurney formula with finite length correction | |
// Aspect ratio correction (Flis & Sygda model) | |
const aspectRatio = dimensions.height! / (2 * dimensions.radius!); | |
const aspectCorrection = Math.max(0.9, Math.min(1.0, 0.91 + 0.09 * Math.tanh(aspectRatio - 2))); | |
// Thick-walled correction (Molinari) | |
const thicknessRatio = casing.thickness / dimensions.radius!; | |
const thicknessCorrection = Math.exp(-0.2 * Math.pow(thicknessRatio, 2)); | |
// Full cylindrical formula with all corrections | |
velocity = gurneyConstant * Math.sqrt(1 / (2 * massRatio + 1)) * aspectCorrection * thicknessCorrection; | |
} else if (charge.shape === 'sandwich') { | |
// Flat sandwich Gurney formula | |
velocity = gurneyConstant * Math.sqrt(1 / (massRatio + 1)); | |
} else if (charge.shape === 'hemisphere') { | |
// Hemispherical Gurney formula (Stirpe-Johnson-Carleone model) | |
velocity = gurneyConstant * Math.sqrt(1 / (2.5 * massRatio + 1)); | |
} else if (charge.shape === 'conical') { | |
// Conical warhead (approximation using modified cylindrical factor) | |
velocity = gurneyConstant * Math.sqrt(1 / (2.2 * massRatio + 1)); | |
} else if (charge.shape === 'shaped_charge') { | |
// Shaped charge side fragments (Richter model) | |
velocity = gurneyConstant * Math.sqrt(1 / (1.8 * massRatio + 1)); | |
} | |
// Apply asymmetry correction if needed | |
if (asymmetry > 0) { | |
// Formula developed by Feng and Wiley for asymmetric charges | |
const asymmetryCorrection = 1 + 0.15 * asymmetry * Math.pow(massRatio, -0.25); | |
velocity *= asymmetryCorrection; | |
} | |
// Calculate velocity reduction due to real-world effects (non-ideal detonation, edge effects) | |
const realWorldFactor = 0.95; | |
const initialVelocity = velocity * realWorldFactor; // m/s | |
// Calculate terminal velocity after drag (approximation for typical fragment size) | |
// Using Spherical fragment approximation with Cd=0.5 | |
const airDensity = 1.225; // kg/m³ | |
const fragmentSize = 0.01; // m (1cm typical fragment) | |
const dragCoefficient = 0.5; // Dimensionless | |
const crossSectionalArea = Math.PI * Math.pow(fragmentSize / 2, 2); // m² | |
// Simplified drag model for terminal velocity | |
const terminalFactor = Math.exp(-0.0001 * dragCoefficient * crossSectionalArea / | |
(casingMass / (casingVolume / 1000))); // dimensionless | |
const terminalVelocity = initialVelocity * terminalFactor; // m/s | |
// Calculate acceleration time using detonation velocity | |
// Time for detonation wave to fully engulf the charge | |
let accelerationTime: number; | |
if (charge.shape === 'sphere') { | |
accelerationTime = dimensions.radius! / 10 / (explosive.detonationVelocity / 1000) * 1e6; // μs | |
} else if (charge.shape === 'cylinder') { | |
// For cylindrical charges, consider both radial and axial detonation paths | |
const radialTime = dimensions.radius! / 10 / (explosive.detonationVelocity / 1000) * 1e6; // μs | |
const axialTime = dimensions.height! / 20 / (explosive.detonationVelocity / 1000) * 1e6; // μs | |
accelerationTime = Math.max(radialTime, axialTime); // μs | |
} else { | |
// Default approximation for other shapes | |
accelerationTime = Math.pow(charge.mass / explosive.density, 1 / 3) / | |
(explosive.detonationVelocity / 1000) * 1e6; // μs | |
} | |
// Calculate kinetic energy of fragments | |
const kineticEnergy = 0.5 * casingMass * Math.pow(initialVelocity, 2); // J | |
// Calculate momentum density | |
const momentumDensity = casingMass * initialVelocity / | |
(4 * Math.PI * Math.pow(dimensions.radius! / 10, 2)); // kg·m/s/m² | |
// Calculate velocity distribution parameters | |
// Mean velocity already calculated as initialVelocity | |
// Standard deviation from experimental data (Mott distribution parameter) | |
const mottConstant = Math.sqrt(2 * casing.ultimateStrength / casing.density); // m/s | |
const standardDeviation = 0.1 * initialVelocity + 0.05 * mottConstant; // m/s | |
// Skewness of velocity distribution (typically negative for fragmenting warheads) | |
const skewness = -0.2 - 0.1 * Math.log10(massRatio); // dimensionless | |
// Calculate casing expansion ratio | |
const casingExpansionRatio = 1 + initialVelocity * accelerationTime / | |
(dimensions.radius! * 100); // dimensionless | |
// Calculate acceleration gradient | |
const accelerationGradient = initialVelocity / (accelerationTime * 1e-6); // m/s² | |
// Calculate specific impulse | |
const specificImpulse = casingMass * initialVelocity / charge.mass; // N·s/kg | |
// Calculate energy partition ratio (fraction of explosive energy converted to kinetic energy) | |
const totalExplosiveEnergy = charge.mass * explosive.energyDensity * 1e6; // J | |
const energyPartitionRatio = kineticEnergy / totalExplosiveEnergy; // dimensionless | |
return { | |
initialVelocity, | |
terminalVelocity, | |
accelerationTime, | |
kineticEnergy, | |
momentumDensity, | |
velocityDistribution: { | |
mean: initialVelocity, | |
standardDeviation, | |
skewness | |
}, | |
effectiveGurneyCoefficient: gurneyConstant, | |
casingExpansionRatio, | |
accelerationGradient, | |
specificImpulse, | |
energyPartitionRatio | |
}; | |
} | |
// Mott distribution for fragment size distribution | |
static calculateFragmentDistribution(casing: Casing, fragmentVelocity: number, totalMass: number): { | |
averageFragmentMass: number; // g | |
numberOfFragments: number; | |
maxFragmentSize: number; // cm | |
fragmentDensity: number; // fragments/m² | |
} { | |
// Mott's constants | |
const mottConstant = Math.sqrt(2 * casing.ultimateStrength / casing.density); // m/s | |
// Calculate average fragment mass | |
const averageFragmentMass = Math.pow(mottConstant / fragmentVelocity, 2) * 1000; // g | |
// Calculate total number of fragments | |
const numberOfFragments = totalMass * 1000 / averageFragmentMass; | |
// Calculate maximum fragment size (95th percentile) | |
const maxFragmentSize = Math.pow(3 * averageFragmentMass / (4 * Math.PI * casing.density), 1 / 3) * 3 * 10; // cm | |
// Approximate fragment density at 1m | |
const fragmentDensity = numberOfFragments / (4 * Math.PI); // fragments/m² at 1m | |
return { averageFragmentMass, numberOfFragments, maxFragmentSize, fragmentDensity }; | |
} | |
// Taylor-Sedov blast wave model with full Rankine-Hugoniot relations | |
static calculateBlastWaveDynamics(explosive: Explosive, yieldEnergy: number, distance: number, time: number): { | |
shockRadius: number; // m | |
shockVelocity: number; // m/s | |
shockOverpressure: number; // kPa | |
shockDensity: number; // kg/m³ | |
shockTemperature: number; // K | |
specificInternalEnergy: number; // J/kg | |
particleVelocity: number; // m/s | |
machNumber: number; // dimensionless | |
flowAngle: number; // radians | |
} { | |
// Convert yield to energy with high precision | |
const energy = yieldEnergy * explosive.energyDensity * 1e6; // J | |
// Atmospheric conditions | |
const gamma = 1.4; // Ratio of specific heats for air | |
const ambientPressure = this.ATMOSPHERIC_PRESSURE * 1000; // Pa | |
const ambientTemperature = 293.15; // K (standard conditions) | |
const ambientDensity = this.AIR_DENSITY; // kg/m³ | |
const gasConstant = 287; // J/(kg·K) for air | |
// Calculate dimensionality factor (n) | |
// n=3 for spherical, n=2 for cylindrical, n=1 for planar | |
const n = 3; // Spherical blast wave | |
// Calculate Taylor-Sedov constant with full derivation | |
// Based on Sedov's self-similar solution | |
let alpha: number; | |
if (n === 3) { | |
// Exact coefficient for strong spherical shock | |
alpha = 1.175; // Derived from Taylor's solution for gamma=1.4 | |
} else if (n === 2) { | |
alpha = 1.0; // For cylindrical shock | |
} else { | |
alpha = 0.851; // For planar shock | |
} | |
// Calculate shock radius at given time using full Sedov solution | |
const E0 = energy * ((gamma - 1) / (2 * (gamma + 1))); // Effective energy parameter | |
const shockRadius = alpha * Math.pow(E0 / (ambientDensity * Math.pow(time, 2)), 1 / (n + 2)) * Math.pow(time, 2 / (n + 2)); | |
// Calculate shock velocity directly from derivative of radius w.r.t. time | |
const shockVelocity = (2 / (n + 2)) * (shockRadius / time); | |
// Calculate Mach number | |
const machNumber = shockVelocity / Math.sqrt(gamma * ambientPressure / ambientDensity); | |
// Apply full Rankine-Hugoniot relations for shock jump conditions | |
// Pressure ratio across shock | |
const pressureRatio = 1 + ((2 * gamma) / (gamma + 1)) * (Math.pow(machNumber, 2) - 1); | |
// Density ratio across shock | |
const densityRatio = ((gamma + 1) * Math.pow(machNumber, 2)) / | |
((gamma - 1) * Math.pow(machNumber, 2) + 2); | |
// Temperature ratio across shock | |
const temperatureRatio = pressureRatio * (1 / densityRatio); | |
// Calculate actual values | |
const shockOverpressure = (pressureRatio - 1) * ambientPressure / 1000; // kPa | |
const shockDensity = densityRatio * ambientDensity; // kg/m³ | |
const shockTemperature = temperatureRatio * ambientTemperature; // K | |
// Calculate particle velocity behind shock | |
const particleVelocity = (2 * (Math.pow(machNumber, 2) - 1)) / | |
((gamma + 1) * machNumber) * this.SPEED_OF_SOUND; | |
// Calculate specific internal energy behind shock | |
const specificInternalEnergy = shockOverpressure * 1000 / | |
((gamma - 1) * shockDensity); // J/kg | |
// Calculate flow angle (relevant for non-normal incidence) | |
const beta = Math.asin(1 / machNumber); // Mach angle | |
const flowAngle = Math.atan(2 * Math.pow(Math.tan(beta), -1) * | |
((gamma - 1) + 2 * Math.pow(Math.sin(beta), -1))); | |
return { | |
shockRadius, | |
shockVelocity, | |
shockOverpressure, | |
shockDensity, | |
shockTemperature, | |
specificInternalEnergy, | |
particleVelocity, | |
machNumber, | |
flowAngle | |
}; | |
} | |
// Hopkinson-Cranz scaling law | |
static scaleExplosiveEffects(referenceYield: number, referenceDistance: number, newYield: number): number { | |
return referenceDistance * Math.pow(newYield / referenceYield, 1 / 3); | |
} | |
// Comprehensive underwater explosion model combining Cole, Geers-Hunter and Arons models | |
static calculateUnderwaterExplosion(yieldEnergy: number, depth: number, distance: number): { | |
peakPressure: number; // MPa | |
peakPressureTime: number; // ms | |
pressureHistoryParams: { A: number, θ1: number, θ2: number }; // Cole exponential parameters | |
shockWaveEnergy: number; // J | |
bubblePulsation: { | |
maxRadius: number; // m | |
firstPeriod: number; // s | |
secondPeriod: number; // s | |
migrationDistance: number; // m | |
secondMaxRadius: number; // m | |
pulseRatio: number; // dimensionless | |
}; | |
cavitation: { | |
cavitationDepth: number; // m | |
cavitationRadius: number; // m | |
cavitationTime: number; // ms | |
}; | |
reflectionCoefficients: { | |
freeSurface: number; | |
rigidSurface: number; | |
}; | |
impulse: number; // kPa·s | |
energyFluxDensity: number; // J/m² | |
lethalRadius: { fish: number, swimmer: number }; // m | |
} { | |
// Constants for water with temperature and salinity adjustments | |
const waterTemperature = 283.15; // K (10°C) | |
const salinity = 35; // ppt (parts per thousand) | |
// Calculate exact water density using UNESCO equation of state | |
const a0 = 999.842594; | |
const a1 = 6.793952e-2; | |
const a2 = -9.095290e-3; | |
const a3 = 1.001685e-4; | |
const a4 = -1.120083e-6; | |
const a5 = 6.536332e-9; | |
const t = waterTemperature - 273.15; // Convert to Celsius | |
const densityFreshwater = a0 + a1 * t + a2 * t * t + a3 * t * t * t + a4 * t * t * t * t + a5 * t * t * t * t * t; | |
// Salinity correction | |
const b0 = 8.24493e-1; | |
const b1 = -4.0899e-3; | |
const b2 = 7.6438e-5; | |
const b3 = -8.2467e-7; | |
const b4 = 5.3875e-9; | |
const c0 = -5.72466e-3; | |
const c1 = 1.0227e-4; | |
const c2 = -1.6546e-6; | |
const salinityCorrectionTerm = (b0 + b1 * t + b2 * t * t + b3 * t * t * t + b4 * t * t * t * t) * salinity + | |
(c0 + c1 * t + c2 * t * t) * Math.pow(salinity, 1.5); | |
const waterDensity = densityFreshwater + salinityCorrectionTerm; | |
// Calculate water sound speed with salinity and temperature effects | |
const speedOfSoundInWater = 1449.2 + 4.6 * t - 0.055 * t * t + 0.00029 * t * t * t + | |
(1.34 - 0.01 * t) * (salinity - 35); | |
// Convert yield to energy with TNT-equivalent correction | |
const energy = yieldEnergy * 4.184e9; // J (TNT equivalent) | |
// Calculate hydrostatic pressure at depth with atmospheric pressure | |
const gravitationalAcceleration = 9.80665; // m/s² | |
const atmosphericPressure = 101325; // Pa | |
const hydrostaticPressurePa = atmosphericPressure + waterDensity * gravitationalAcceleration * depth; | |
const hydrostaticPressure = hydrostaticPressurePa / 1000; // kPa | |
// Calculate shock wave parameters using Cole-Weston-Arons composite model | |
// Direct TNT-scaling for charge mass | |
const tntEquivalentMass = yieldEnergy; // kg | |
// Calculate dimensionless scaled distance | |
const scaledDistance = distance / Math.pow(tntEquivalentMass, 1 / 3); | |
// Arons-Cole peak pressure function with empirical corrections | |
// Improved fit to experimental data (Arons 1954, Cole 1948, Hunter 1970) | |
const K1 = 52.4; // MPa·m/kg^(1/3) | |
const alpha = 1.13; // Cole's pressure decay constant | |
// Calculate pressure decay time constants for realistic exponential-power model | |
const θ1 = 0.056 * Math.pow(tntEquivalentMass, 1 / 3) * Math.pow(scaledDistance, 0.05); // ms | |
const θ2 = 0.092 * Math.pow(tntEquivalentMass, 1 / 3) * Math.pow(scaledDistance, 0.18); // ms | |
// Calculate peak pressure with corrected model | |
const peakPressure = K1 * Math.pow(tntEquivalentMass, 0.63 / 3) * | |
Math.pow(scaledDistance, -alpha) * | |
(1 + 0.03 * Math.log10(tntEquivalentMass)); // MPa | |
// Time to peak pressure | |
const peakPressureTime = distance / speedOfSoundInWater * 1000; // ms | |
// Energy flux density (Arons, 1954) | |
const energyFluxDensity = 0.35 * peakPressure * 1e6 * θ1 * 1e-3; // J/m² | |
// Calculate impulse using integrated pressure history | |
const impulse = peakPressure * (θ1 + θ2) * 1e-3; // kPa·s | |
// Calculate shock wave energy using Willis formula | |
const shockWaveEnergy = (energy * 0.53) * Math.exp(-0.22 * depth / Math.pow(tntEquivalentMass, 1 / 3)); | |
// Calculate reflection coefficients | |
const freeSurfaceReflection = -1.0; // Perfect pressure release | |
// Acoustic impedance method for rigid surface | |
const rigidSurfaceReflection = 0.95; // Slightly less than theoretical due to energy absorption | |
// Calculate bubble parameters using exact Rayleigh-Plesset equation parameters | |
// Initial condition parameters from Geers-Hunter model | |
const k1Bubble = 3.5; // First bubble coefficient, revised based on experimental data | |
const k2Bubble = 0.87; // Second bubble coefficient | |
// Max bubble radius | |
const maxRadius = k1Bubble * Math.pow(tntEquivalentMass / (hydrostaticPressurePa / 1e5), 1 / 3); // m | |
// Calculate bubble period including compressibility correction | |
const rayleighPeriod = 2.11 * maxRadius * Math.sqrt(waterDensity / hydrostaticPressurePa); | |
const compressibilityFactor = 1 + 0.02 * depth / maxRadius; | |
const firstPeriod = rayleighPeriod * compressibilityFactor; // s | |
// Calculate bubble migration velocity from buoyancy | |
const bubbleBuoyancy = (4 / 3) * Math.PI * Math.pow(maxRadius, 3) * waterDensity * gravitationalAcceleration; | |
const addedMassFactor = 0.5; // Hydrodynamic added mass | |
const effectiveMass = (4 / 3) * Math.PI * Math.pow(maxRadius, 3) * waterDensity * (1 + addedMassFactor); | |
const migrationVelocity = bubbleBuoyancy / effectiveMass * firstPeriod; // m/s | |
// Calculate migration distance during first oscillation | |
const migrationDistance = 0.5 * migrationVelocity * firstPeriod; // m | |
// Calculate second maximum radius (smaller due to energy loss) | |
const energyLossFactor = 0.85; // Energy loss during first oscillation | |
const secondMaxRadius = maxRadius * Math.sqrt(energyLossFactor); // m | |
// Calculate second oscillation period | |
const secondPeriod = firstPeriod * Math.sqrt(energyLossFactor); // s | |
// Calculate pulse pressure ratio between first and second bubble pulse | |
const pulseRatio = 0.54 * Math.pow(tntEquivalentMass, -0.05); // dimensionless | |
// Calculate cavitation parameters | |
// Critical pressure for cavitation | |
const cavitationPressure = -101325; // Pa (-1 atm) | |
// Calculate maximum depth where cavitation can occur | |
const cavitationDepth = 10.4 * Math.pow(tntEquivalentMass, 1 / 3) * Math.pow(peakPressure / 5, 0.4); // m | |
// Calculate maximum radius of cavitation zone | |
const cavitationRadius = 15 * Math.pow(tntEquivalentMass, 1 / 3) * Math.sqrt(depth / Math.max(cavitationDepth, 0.1)); // m | |
// Calculate time of cavitation closure | |
const cavitationTime = 5.5 * Math.pow(tntEquivalentMass, 1 / 3) * Math.sqrt(depth / Math.max(cavitationDepth, 0.1)); // ms | |
// Calculate lethal radius for biological targets | |
// Based on empirical studies (Goertner model) | |
const lethalRadiusFish = 43 * Math.pow(tntEquivalentMass, 1 / 3) * Math.pow(depth / 10, -0.22); // m | |
const lethalRadiusSwimmer = 12 * Math.pow(tntEquivalentMass, 1 / 3) * Math.pow(depth / 10, -0.27); // m | |
return { | |
peakPressure, | |
peakPressureTime, | |
pressureHistoryParams: { A: peakPressure, θ1, θ2 }, | |
shockWaveEnergy, | |
bubblePulsation: { | |
maxRadius, | |
firstPeriod, | |
secondPeriod, | |
migrationDistance, | |
secondMaxRadius, | |
pulseRatio | |
}, | |
cavitation: { | |
cavitationDepth, | |
cavitationRadius, | |
cavitationTime | |
}, | |
reflectionCoefficients: { | |
freeSurface: freeSurfaceReflection, | |
rigidSurface: rigidSurfaceReflection | |
}, | |
impulse, | |
energyFluxDensity, | |
lethalRadius: { | |
fish: lethalRadiusFish, | |
swimmer: lethalRadiusSwimmer | |
} | |
}; | |
} | |
// Ground shock calculation | |
static calculateGroundShock(energyYield: number, distance: number, soilProperties: { | |
pWaveVelocity: number, // m/s | |
sWaveVelocity: number, // m/s | |
density: number, // kg/m³ | |
attenuation: number // dimensionless | |
}): { | |
peakParticleVelocity: number, // m/s | |
peakAcceleration: number, // g | |
peakDisplacement: number, // mm | |
arrivalTime: number // s | |
} { | |
const scaledDistanceResult = this.calculateScaledDistance(distance, energyYield); | |
const scaledDistance = scaledDistanceResult.hopkinsonScaledDistance; | |
// Calculate peak particle velocity (empirical formula) | |
const peakParticleVelocity = 160 * Math.pow(scaledDistance, -1.5) * Math.pow(soilProperties.attenuation, -distance / 100); | |
// Calculate peak acceleration | |
const peakAcceleration = peakParticleVelocity * soilProperties.pWaveVelocity / 9.81; // in g | |
// Calculate peak displacement | |
const dominantFrequency = 10 * Math.pow(energyYield, -1 / 3); // Hz (empirical) | |
const peakDisplacement = peakParticleVelocity / (2 * Math.PI * dominantFrequency) * 1000; // mm | |
// Calculate arrival time | |
const arrivalTime = distance / soilProperties.pWaveVelocity; // s | |
return { peakParticleVelocity, peakAcceleration, peakDisplacement, arrivalTime }; | |
} | |
// Advanced shaped charge jet formation model combining Birkhoff, PER, and Chou-Carleone models | |
static calculateShapedChargeJet(explosive: Explosive, liner: Liner, standoff: number): { | |
// Basic jet parameters | |
jetVelocity: { tip: number, tail: number, average: number }, // m/s | |
penetrationDepth: number, // cm | |
jetLength: number, // cm | |
timeOfFlight: number, // μs | |
jetMass: number, // g | |
slugMass: number, // g | |
// Advanced jet parameters | |
jetCoherence: number, // dimensionless (0-1) | |
breakupTime: number, // μs | |
breakupDistance: number, // cm | |
stretching: number, // 1/μs | |
particulation: { | |
onsetTime: number, // μs | |
particleDiameter: number, // mm | |
particleCount: number, // dimensionless | |
particleVelocitySpread: number // m/s | |
}, | |
// Target interaction | |
penetrationVelocity: number, // km/s | |
holeProfile: { | |
entranceDiameter: number, // mm | |
exitDiameter: number, // mm | |
averageDiameter: number // mm | |
}, | |
afterEffect: { | |
behindArmorEffect: number, // cm³ | |
spallDiameter: number, // cm | |
spallVelocity: number // m/s | |
}, | |
// Jet formation dynamics | |
formationTime: number, // μs | |
detonationPressure: number, // GPa | |
collapseVelocity: number, // km/s | |
stagnationPressure: number, // GPa | |
velocityGradient: number, // 1/μs | |
jettingAnalysis: { | |
gurney1D: number, // m/s | |
gurney2D: number, // m/s | |
PDEfactor: number, // dimensionless | |
massEfficiency: number // % | |
} | |
} { | |
// Constants | |
const PI = Math.PI; | |
// Calculate detonation pressure using actual JWL or Chapman-Jouguet equation | |
// P = ρ * D² / (γ + 1) where γ is the polytropic exponent (approximated as 3 for most explosives) | |
const polytropic = 3; | |
const detonationPressure = explosive.density * Math.pow(explosive.detonationVelocity, 2) / 1e6 / (polytropic + 1); // GPa | |
// Calculate liner aspect ratio (important for collapse dynamics) | |
const aspectRatio = 2 * (liner.thickness / (liner.material === 'copper' ? 1.5 : 1.0)); // normalized thickness | |
// Calculate collapse velocity using PER (Progressive Explosive Reaction) model | |
// More accurate than simple Birkhoff approximation | |
// v_c = 0.6 * v_d * (1 - e^(-2M)) where M is the explosive to liner mass ratio | |
const explosiveToLinerRatio = 3.5; // typical value, would be calculated from geometry | |
const collapseFactor = 0.6 * (1 - Math.exp(-2 * explosiveToLinerRatio)); | |
const collapseVelocity = collapseFactor * explosive.detonationVelocity / 1000; // km/s | |
// Calculate jet and slug velocities using Birkhoff model with correction factors | |
// Corrections account for liner thickness effects (Walsh model) | |
const alpha = liner.angle * PI / 180; // convert to radians | |
const beta = alpha / 2; // beta angle in radians | |
// Correction factor for finite liner thickness | |
const thicknessCorrection = 1 - 0.1 * Math.pow(liner.thickness, 0.3); | |
// Calculate stagnation angle using Carleone formula | |
const tan_alpha = Math.tan(alpha); | |
const stagnationAngle = Math.atan(0.5 * tan_alpha); // radians | |
// Calculate stagnation point pressure (Chou-Flis model) | |
const stagnationPressure = detonationPressure * Math.pow(Math.sin(alpha), 2) * | |
(1 + 2 * Math.cos(2 * stagnationAngle)); // GPa | |
// Calculate jet and slug velocities using Chou-Carleone model | |
// Accounts for stagnation point dynamics and compressibility | |
const jetVelocityTip = collapseVelocity * (1 + Math.cos(beta)) * thicknessCorrection * 1000; // m/s | |
const jetVelocityTail = collapseVelocity * (1 + Math.cos(alpha)) * thicknessCorrection * 1000; // m/s | |
const slugVelocity = collapseVelocity * (1 - Math.cos(alpha)) * 1000; // m/s | |
// Calculate average jet velocity | |
const jetVelocityAvg = (jetVelocityTip + jetVelocityTail) / 2; // m/s | |
// Calculate velocity gradient along jet | |
const velocityGradient = (jetVelocityTip - jetVelocityTail) / 100; // 1/μs (assuming typical 100μs formation) | |
// Calculate mass partition using Chou model | |
// λ = 2*sin²(β) / sin²(α) | |
const lambda = 2 * Math.pow(Math.sin(beta), 2) / Math.pow(Math.sin(alpha), 2); | |
// Calculate total liner mass (simplified conical section) | |
const linerVolume = PI * Math.pow(liner.thickness / 10, 3) * (1 / 3) * (1 / Math.tan(alpha)); // cm³ | |
const totalLinerMass = linerVolume * liner.density; // g | |
// Calculate jet and slug masses | |
const jetMass = lambda * totalLinerMass / (1 + lambda); // g | |
const slugMass = totalLinerMass - jetMass; // g | |
// Calculate formation time based on liner dimensions and detonation velocity | |
const linerDiameter = liner.thickness / Math.tan(alpha) * 2; // mm | |
const formationTime = (linerDiameter / 2) / explosive.detonationVelocity * 1e6; // μs | |
// Calculate jet coherence parameter (Pugh-Eichelberger-Rostoker model) | |
// Function of liner material, thickness, and angle | |
// 1.0 is perfect coherence, 0.0 is immediate breakup | |
const materialFactor = liner.material === 'copper' ? 1.0 : | |
liner.material === 'aluminum' ? 0.85 : | |
liner.material === 'tantalum' ? 0.95 : 0.9; | |
const angleCorrection = Math.cos(alpha - 0.6); | |
const jetCoherence = materialFactor * angleCorrection * Math.exp(-0.5 * liner.thickness / 10); | |
// Calculate jet breakup time using modified Hirsch model | |
// t_b = J * d / (Vj_tip - Vj_tail) where J is material-dependent constant | |
const hirschConstant = liner.material === 'copper' ? 2.0 : | |
liner.material === 'aluminum' ? 1.4 : | |
liner.material === 'tantalum' ? 2.8 : 1.8; | |
const breakupTime = hirschConstant * liner.thickness / | |
(jetVelocityTip - jetVelocityTail) * 1e6; // μs | |
// Calculate stretching rate | |
const stretching = (jetVelocityTip - jetVelocityTail) / (jetVelocityAvg * breakupTime); // 1/μs | |
// Calculate jet length at breakup | |
const jetLength = jetVelocityAvg * breakupTime / 10000; // cm | |
// Calculate breakup distance | |
const breakupDistance = standoff * (breakupTime / (standoff * 10 / jetVelocityAvg * 1e6)); // cm | |
// Calculate particulation parameters | |
// Based on instability theory (Rayleigh-Taylor and Richtmyer-Meshkov) | |
const onsetTime = breakupTime * 1.2; // μs | |
// Calculate particle diameter using Walsh model | |
const particleDiameter = 1.81 * liner.thickness / | |
Math.pow(1 + 0.1 * velocityGradient, 0.5); // mm | |
// Calculate number of particles | |
const particleCount = Math.ceil(jetLength * 10 / particleDiameter); | |
// Calculate particle velocity spread due to breakup | |
const particleVelocitySpread = 0.05 * jetVelocityAvg; // m/s | |
// Calculate time of flight to standoff | |
const timeOfFlight = standoff * 10 / jetVelocityAvg * 1e6; // μs | |
// Calculate penetration velocity | |
// Using hydrodynamic penetration theory | |
const targetDensity = 7.85; // steel target density g/cm³ | |
const penetrationVelocity = jetVelocityAvg * Math.sqrt(liner.density / targetDensity) / 1000; // km/s | |
// Calculate penetration depth using modified Tate equation with shear strength terms | |
// P = L * √(ρj/ρt) * [1 - Rt/(ρj*Vj²/2) - Yj/(ρj*Vj²/2)] | |
// where L is jet length, R is target strength, Y is jet strength | |
const jetStrength = liner.material === 'copper' ? 0.3 : | |
liner.material === 'aluminum' ? 0.2 : | |
liner.material === 'tantalum' ? 0.7 : 0.4; // GPa | |
const targetStrength = 1.2; // GPa for steel | |
// Jet kinetic energy density | |
const jetKE = 0.5 * liner.density * Math.pow(jetVelocityAvg, 2) / 1e9; // GPa | |
// Calculate penetration efficiency | |
const penetrationEfficiency = 1 - targetStrength / jetKE - jetStrength / jetKE; | |
// Calculate penetration depth | |
const penetrationDepth = jetLength * Math.sqrt(liner.density / targetDensity) * | |
Math.max(0.1, penetrationEfficiency) * | |
(timeOfFlight < breakupTime ? 1 : Math.pow(breakupTime / timeOfFlight, 0.3)); // cm | |
// Calculate hole profile | |
// Entrance diameter is typically 3-5 times the jet diameter | |
const jetDiameter = 0.5 * liner.thickness * Math.sqrt(lambda); // mm | |
const entranceDiameter = 4 * jetDiameter; // mm | |
// Exit diameter is typically 1-2 times the jet diameter | |
const exitDiameter = Math.max(1, (entranceDiameter * 0.4) - (penetrationDepth * 0.05)); // mm | |
// Average hole diameter | |
const averageDiameter = (entranceDiameter + exitDiameter) / 2; // mm | |
// Calculate behind armor effects | |
// Behind armor debris volume | |
const behindArmorEffect = PI * Math.pow(exitDiameter / 10, 2) * penetrationDepth / 4; // cm³ | |
// Spall diameter using Christman-Gehring formula | |
const spallDiameter = exitDiameter * (1 + 5 * Math.pow(penetrationVelocity, 0.6)) / 10; // cm | |
// Spall velocity using momentum conservation | |
const spallMass = PI * Math.pow(spallDiameter, 2) * targetDensity * 0.1; // g | |
const spallVelocity = jetMass * jetVelocityAvg / spallMass * 0.3; // m/s | |
// Calculate Gurney velocities for comparison | |
// 1D Gurney (sandwich) | |
const gurney1D = 0.5 * Math.sqrt(2 * explosive.energyDensity * 1e6) * | |
Math.sqrt(explosiveToLinerRatio / (1 + explosiveToLinerRatio / 2)); // m/s | |
// 2D Gurney (cylinder) | |
const gurney2D = 0.5 * Math.sqrt(2 * explosive.energyDensity * 1e6) * | |
Math.sqrt(explosiveToLinerRatio / (1 + explosiveToLinerRatio / 3)); // m/s | |
// PDE (Prompt Detonation Energy) factor - ratio of actual to ideal detonation | |
const PDEfactor = detonationPressure / | |
(explosive.density * Math.pow(explosive.detonationVelocity, 2) / 1e6 / 4); // dimensionless | |
// Calculate mass efficiency - percentage of liner mass converted to useful jet | |
const massEfficiency = (jetMass / totalLinerMass) * 100; // % | |
return { | |
jetVelocity: { | |
tip: jetVelocityTip, | |
tail: jetVelocityTail, | |
average: jetVelocityAvg | |
}, | |
penetrationDepth, | |
jetLength, | |
timeOfFlight, | |
jetMass, | |
slugMass, | |
jetCoherence, | |
breakupTime, | |
breakupDistance, | |
stretching, | |
particulation: { | |
onsetTime, | |
particleDiameter, | |
particleCount, | |
particleVelocitySpread | |
}, | |
penetrationVelocity, | |
holeProfile: { | |
entranceDiameter, | |
exitDiameter, | |
averageDiameter | |
}, | |
afterEffect: { | |
behindArmorEffect, | |
spallDiameter, | |
spallVelocity | |
}, | |
formationTime, | |
detonationPressure, | |
collapseVelocity, | |
stagnationPressure, | |
velocityGradient, | |
jettingAnalysis: { | |
gurney1D, | |
gurney2D, | |
PDEfactor, | |
massEfficiency | |
} | |
}; | |
} | |
// Thermal radiation effects | |
static calculateThermalRadiation(energyYield: number, distance: number, transmissivity: number = 0.9): { | |
thermalEnergy: number, // J/cm² | |
fireball: { radius: number, duration: number, temperature: number } // m, s, K | |
} { | |
// Convert energyYield to energy | |
const energy = energyYield * 4.184e12; // J (TNT equivalent) | |
// Calculate fireball radius (Glasstone & Dolan) | |
const fireball = { | |
radius: 3.7 * Math.pow(energyYield, 0.4), // m | |
duration: 0.9 * Math.pow(energyYield, 0.4), // s | |
temperature: 7000 // K (initial temperature) | |
}; | |
// Thermal partition (approximate) | |
const thermalFraction = 0.35; // approx 35% of energy as thermal radiation | |
// Calculate thermal energy at distance | |
const thermalEnergy = thermalFraction * energy * transmissivity / (4 * Math.PI * Math.pow(distance, 2)) / 100; // J/cm² | |
return { thermalEnergy, fireball }; | |
} | |
// Calculate ionizing radiation dose | |
static calculateRadiationDose(energyYield: number, distance: number): { | |
initialNuclearRadiation: number, // Gy | |
residualRadiation: number // Gy/hr at 1 hour | |
} { | |
// This applies only to nuclear explosions - return zero for conventional | |
return { | |
initialNuclearRadiation: 0, | |
residualRadiation: 0 | |
}; | |
} | |
// Improved structural damage model | |
static calculateStructuralDamage(structure: { | |
type: string, | |
peakOverpressureThreshold: number, // kPa | |
impulseThreshold: number, // kPa·ms | |
pDelta: number // pressure-impulse curve exponent | |
}, peakOverpressure: number, impulse: number): { | |
damagePercentage: number, // % | |
damageMode: string | |
} { | |
// Pressure-Impulse diagram approach (simplified) | |
const pressureRatio = peakOverpressure / structure.peakOverpressureThreshold; | |
const impulseRatio = impulse / structure.impulseThreshold; | |
// Calculate damage using P-I curve | |
const piValue = Math.pow(pressureRatio, structure.pDelta) + Math.pow(impulseRatio, structure.pDelta); | |
// Determine damage percentage | |
let damagePercentage = 0; | |
if (piValue >= 1) { | |
damagePercentage = Math.min(100, 100 * Math.pow(piValue, 1 / 2)); | |
} | |
// Determine damage mode | |
let damageMode = "None"; | |
if (damagePercentage > 0) { | |
if (pressureRatio > impulseRatio) { | |
damageMode = "Pressure-dominated"; | |
} else { | |
damageMode = "Impulse-dominated"; | |
} | |
} | |
return { damagePercentage, damageMode }; | |
} | |
// Human injury model using Bowen criteria | |
static calculateHumanInjury(peakOverpressure: number, positivePhaseDuration: number): { | |
lethality: number, // % probability | |
lungDamage: string, | |
eardrumRupture: number // % probability | |
} { | |
// Convert to psi for standard Bowen curves | |
const overpressure_psi = peakOverpressure * 0.145038; // kPa to psi | |
// Scale duration | |
const scaledDuration = positivePhaseDuration * Math.pow(70 / 155, 1 / 3); // scaled to 70kg human | |
// Calculate lung damage probability (simplified Bowen model) | |
let lethality = 0; | |
if (overpressure_psi > 50) { | |
lethality = 100; | |
} else if (overpressure_psi > 30) { | |
lethality = 50 + 50 * (overpressure_psi - 30) / 20; | |
} else if (overpressure_psi > 15) { | |
lethality = (overpressure_psi - 15) / 15 * 50; | |
} | |
// Determine lung damage severity | |
let lungDamage = "None"; | |
if (overpressure_psi > 12) { | |
lungDamage = "Severe"; | |
} else if (overpressure_psi > 6) { | |
lungDamage = "Moderate"; | |
} else if (overpressure_psi > 3) { | |
lungDamage = "Threshold"; | |
} | |
// Calculate eardrum rupture probability | |
const eardrumRupture = Math.min(100, Math.max(0, -12 + 19 * Math.log(overpressure_psi))); | |
return { lethality, lungDamage, eardrumRupture }; | |
} | |
// Calculate mass of explosive required for specific effect | |
static calculateRequiredMass(explosive: Explosive, targetEffect: { | |
type: 'overpressure' | 'impulse' | 'fragmentation' | 'cratering' | 'penetration', | |
value: number, | |
distance: number | |
}): number { | |
let requiredYield = 0; | |
if (targetEffect.type === 'overpressure') { | |
// Convert target overpressure to scaled distance using inverse Kingery-Bulmash | |
const z = Math.pow(targetEffect.value / 98, -0.6); | |
// Calculate required yield | |
requiredYield = Math.pow(targetEffect.distance / z, 3); | |
} else if (targetEffect.type === 'impulse') { | |
// Similar approach for impulse | |
const z = Math.pow(targetEffect.value / 250, -0.6); | |
requiredYield = Math.pow(targetEffect.distance / z, 3); | |
} else if (targetEffect.type === 'cratering') { | |
// For crater radius | |
requiredYield = Math.pow(targetEffect.value / 0.8, 3); | |
} | |
// Convert from TNT equivalent to actual explosive mass | |
return requiredYield / (explosive.energyDensity / 4.6); | |
} | |
// Calculate optimal standoff for shaped charge | |
static calculateOptimalStandoff(liner: Liner): number { | |
// Typical optimum standoff is 3-7 cone diameters | |
return 5 * liner.thickness / Math.tan(liner.angle * Math.PI / 180); | |
} | |
} | |
// Enhanced Implementation of optimizeExplosive function | |
function optimizeExplosive( | |
explosives: Explosive[], | |
scenario: { | |
radius: number, | |
shape: Shape, | |
maxWeight: number, | |
isPortable: boolean, | |
casing: Casing, | |
environment: Environment, | |
targets?: Target[] | |
} | |
): { material: Explosive; metrics: any } | null { | |
// Filter explosives based on portability and stability | |
const filteredExplosives = explosives.filter(explosive => { | |
if (scenario.isPortable && explosive.stability < 3) return false; | |
// Temperature sensitivity check | |
if (scenario.environment.temperature < 223 || scenario.environment.temperature > 343) { | |
if (explosive.stability < 4) return false; | |
} | |
// Friction sensitivity check for portable devices | |
if (scenario.isPortable && explosive.frictionSensitivity < 80) return false; | |
return true; | |
}); | |
// If no suitable explosives remain, return null | |
if (filteredExplosives.length === 0) return null; | |
// Calculate required mass for desired effect at specified radius | |
let bestMaterial: Explosive | null = null; | |
let bestMetrics: any = null; | |
let bestFigureOfMerit = 0; | |
for (const explosive of filteredExplosives) { | |
// Calculate explosive yield in TNT equivalent | |
const tntEquivalent = explosive.energyDensity / 4.6; | |
// Calculate scaled distance | |
const scaledDistance = ExplosiveModel.calculateScaledDistance(scenario.radius, 1); | |
// Calculate blast parameters | |
const blastParams = ExplosiveModel.calculateAirblastParameters(scaledDistance.hopkinsonScaledDistance); | |
// Calculate required mass to achieve desired overpressure | |
const requiredMass = ExplosiveModel.calculateRequiredMass(explosive, { | |
type: 'overpressure', | |
value: blastParams.overpressure, | |
distance: scenario.radius | |
}); | |
// Check if mass fits within weight constraint | |
let casingMass = 0; | |
if (scenario.shape.type === 'sphere') { | |
const volume = (4 / 3) * Math.PI * Math.pow(scenario.shape.dimensions.radius!, 3); | |
const surface = 4 * Math.PI * Math.pow(scenario.shape.dimensions.radius!, 2); | |
casingMass = (surface * scenario.casing.thickness * scenario.casing.density) / 1000; | |
} else if (scenario.shape.type === 'cylinder') { | |
const r = scenario.shape.dimensions.radius!; | |
const h = scenario.shape.dimensions.height!; | |
const surface = 2 * Math.PI * r * (r + h); | |
casingMass = (surface * scenario.casing.thickness * scenario.casing.density) / 1000; | |
} else if (scenario.shape.type === 'shaped_charge' && scenario.shape.linerProperties) { | |
// For shaped charges, consider liner mass | |
const cone = scenario.shape.dimensions.radius! * scenario.shape.dimensions.height! * Math.PI / 3; | |
casingMass = (cone * scenario.casing.thickness * scenario.casing.density) / 1000; | |
casingMass += (cone * scenario.shape.linerProperties.thickness * scenario.shape.linerProperties.density) / 1000; | |
} | |
const totalMass = requiredMass + casingMass; | |
if (totalMass > scenario.maxWeight) continue; | |
// Calculate fragmentation parameters | |
const fragVelocity = ExplosiveModel.calculateFragmentVelocity(explosive, scenario.casing, { | |
mass: requiredMass, | |
shape: scenario.shape.type | |
}); | |
const fragDistribution = | |
ExplosiveModel.calculateFragmentDistribution( | |
scenario.casing, | |
fragVelocity.initialVelocity, | |
casingMass, | |
); | |
// Calculate thermal effects | |
const thermalEffects = ExplosiveModel.calculateThermalRadiation(requiredMass * tntEquivalent, scenario.radius); | |
// Calculate ground shock if applicable | |
let groundShock: any = null; | |
if (scenario.environment.soilType && scenario.environment.soilDensity) { | |
groundShock = ExplosiveModel.calculateGroundShock(requiredMass * tntEquivalent, scenario.radius, { | |
pWaveVelocity: 500, // m/s (typical soil) | |
sWaveVelocity: 300, // m/s (typical soil) | |
density: scenario.environment.soilDensity * 1000, | |
attenuation: 0.95 | |
}); | |
} | |
// Calculate underwater effects if applicable | |
let underwaterEffects: any = null; | |
if (scenario.environment.waterDepth) { | |
underwaterEffects = ExplosiveModel.calculateUnderwaterExplosion( | |
requiredMass * tntEquivalent, | |
scenario.environment.waterDepth, | |
scenario.radius | |
); | |
} | |
// Calculate shaped charge effects if applicable | |
let shapedChargeEffects: any = null; | |
if (scenario.shape.type === 'shaped_charge' && scenario.shape.linerProperties) { | |
shapedChargeEffects = ExplosiveModel.calculateShapedChargeJet( | |
explosive, | |
scenario.shape.linerProperties, | |
scenario.shape.dimensions.standoff || 0 | |
); | |
} | |
// Calculate human injury and structural damage if targets specified | |
let targetsAssessment: any = null; | |
if (scenario.targets && scenario.targets.length > 0) { | |
targetsAssessment = scenario.targets.map(target => { | |
const targetScaledDistance = ExplosiveModel.calculateScaledDistance( | |
target.distance, | |
requiredMass * tntEquivalent | |
); | |
const targetBlastParams = ExplosiveModel.calculateAirblastParameters(targetScaledDistance.hopkinsonScaledDistance); | |
// Calculate injury for human targets | |
const injury = target.material === 'human' ? | |
ExplosiveModel.calculateHumanInjury( | |
targetBlastParams.overpressure, | |
targetBlastParams.positivePhaseDuration | |
) : null; | |
// Calculate structural damage | |
const damage = ExplosiveModel.calculateStructuralDamage( | |
{ | |
type: target.material, | |
peakOverpressureThreshold: target.criticalDamageThreshold, | |
impulseThreshold: target.criticalDamageThreshold * 10, // approximate | |
pDelta: 1.5 // typical value | |
}, | |
targetBlastParams.overpressure, | |
targetBlastParams.positivePhaseImpulse | |
); | |
return { target, blastParams: targetBlastParams, injury, damage }; | |
}); | |
} | |
// Calculate figure of merit (multi-criteria optimization) | |
// Higher is better | |
const figureOfMerit = | |
(1 / requiredMass) * 5 + // less mass is better | |
(explosive.stability) * 2 + // more stability is better | |
(explosive.detonationVelocity / 10000) * 3; // higher velocity is better | |
// Update best material if this has a better figure of merit | |
if (figureOfMerit > bestFigureOfMerit) { | |
bestFigureOfMerit = figureOfMerit; | |
bestMaterial = explosive; | |
// Compile comprehensive metrics | |
bestMetrics = { | |
// Basic properties | |
stability: explosive.stability, | |
detonationVelocity: explosive.detonationVelocity, | |
detonationPressure: explosive.chapmanJouguetPressure, | |
detonationTemperature: explosive.gurvichTemperature, | |
// Mass and energy | |
tntEquivalent: tntEquivalent, | |
explosiveMass: requiredMass, | |
casingMass: casingMass, | |
totalWeight: requiredMass + casingMass, | |
explosionEnergy: requiredMass * explosive.energyDensity, | |
// Blast effects | |
overpressure: blastParams.overpressure, | |
positivePhaseDuration: blastParams.positivePhaseDuration, | |
positivePhaseImpulse: blastParams.positivePhaseImpulse, | |
reflectedPressure: blastParams.reflectedPressure, | |
// Fragmentation | |
fragmentVelocity: fragVelocity, | |
averageFragmentMass: fragDistribution.averageFragmentMass, | |
numberOfFragments: fragDistribution.numberOfFragments, | |
maxFragmentSize: fragDistribution.maxFragmentSize, | |
// Thermal effects | |
thermalRadiation: thermalEffects.thermalEnergy, | |
fireballRadius: thermalEffects.fireball.radius, | |
fireballDuration: thermalEffects.fireball.duration, | |
// Special effects | |
groundShock, | |
underwaterEffects, | |
shapedChargeEffects, | |
// Target effects | |
targetsAssessment | |
}; | |
} | |
} | |
if (!bestMaterial) return null; | |
return { material: bestMaterial, metrics: bestMetrics }; | |
} | |
const explosives: Explosive[] = [ | |
{ | |
"name": "TNT", | |
"detonationVelocity": 6900, | |
"energyDensity": 4.6, | |
"density": 1.65, | |
"stability": 4, | |
"criticalDiameter": 5.0, | |
"activationEnergy": 197, | |
"gurvichTemperature": 3300, | |
"chapmanJouguetPressure": 19, | |
"oxygenBalance": -74, | |
"thermalConductivity": 0.22, | |
"specificHeat": 1230, | |
"thermalExpansion": 7.9e-5, | |
"shockSensitivity": 190, | |
"frictionSensitivity": 353, | |
"impactSensitivity": 15, | |
"expansionRatio": 690, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.34, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.23, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.32, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.11, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "HMX", | |
"detonationVelocity": 9100, | |
"energyDensity": 5.7, | |
"density": 1.91, | |
"stability": 3, | |
"criticalDiameter": 0.8, | |
"activationEnergy": 220, | |
"gurvichTemperature": 4400, | |
"chapmanJouguetPressure": 39, | |
"oxygenBalance": -21.6, | |
"thermalConductivity": 0.3, | |
"specificHeat": 1250, | |
"thermalExpansion": 6.6e-5, | |
"shockSensitivity": 70, | |
"frictionSensitivity": 120, | |
"impactSensitivity": 7.4, | |
"expansionRatio": 780, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.20, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.37, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.25, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.18, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "PETN", | |
"detonationVelocity": 8400, | |
"energyDensity": 5.8, | |
"density": 1.77, | |
"stability": 2, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 197, | |
"gurvichTemperature": 4230, | |
"chapmanJouguetPressure": 33.5, | |
"oxygenBalance": -10.1, | |
"thermalConductivity": 0.25, | |
"specificHeat": 1260, | |
"thermalExpansion": 8.1e-5, | |
"shockSensitivity": 60, | |
"frictionSensitivity": 60, | |
"impactSensitivity": 3, | |
"expansionRatio": 750, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.17, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.42, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.24, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.12, "heatCapacity": 29.1 }, | |
{ "formula": "O2", "moleFraction": 0.05, "heatCapacity": 29.4 } | |
] | |
}, | |
{ | |
"name": "RDX", | |
"detonationVelocity": 8750, | |
"energyDensity": 5.13, | |
"density": 1.82, | |
"stability": 3, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 197, | |
"gurvichTemperature": 4100, | |
"chapmanJouguetPressure": 33.8, | |
"oxygenBalance": -21.6, | |
"thermalConductivity": 0.29, | |
"specificHeat": 1256, | |
"thermalExpansion": 7.7e-5, | |
"shockSensitivity": 80, | |
"frictionSensitivity": 120, | |
"impactSensitivity": 7.5, | |
"expansionRatio": 740, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.21, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.36, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.26, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.17, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "Comp B", | |
"detonationVelocity": 7900, | |
"energyDensity": 5.19, | |
"density": 1.72, | |
"stability": 4, | |
"criticalDiameter": 1.5, | |
"activationEnergy": 197, | |
"gurvichTemperature": 3800, | |
"chapmanJouguetPressure": 29.5, | |
"oxygenBalance": -56, | |
"thermalConductivity": 0.28, | |
"specificHeat": 1240, | |
"thermalExpansion": 7.8e-5, | |
"shockSensitivity": 85, | |
"frictionSensitivity": 140, | |
"impactSensitivity": 8, | |
"expansionRatio": 720, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.27, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.32, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.27, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.14, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "C4", | |
"detonationVelocity": 8040, | |
"energyDensity": 5.3, | |
"density": 1.60, | |
"stability": 5, | |
"criticalDiameter": 1.5, | |
"activationEnergy": 197, | |
"gurvichTemperature": 3900, | |
"chapmanJouguetPressure": 26, | |
"oxygenBalance": -74, | |
"thermalConductivity": 0.25, | |
"specificHeat": 1230, | |
"thermalExpansion": 7.9e-5, | |
"shockSensitivity": 190, | |
"frictionSensitivity": 353, | |
"impactSensitivity": 15, | |
"expansionRatio": 700, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.22, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.35, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.25, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.18, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "Semtex", | |
"detonationVelocity": 7500, | |
"energyDensity": 5.0, | |
"density": 1.40, | |
"stability": 4, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 197, | |
"gurvichTemperature": 3700, | |
"chapmanJouguetPressure": 24, | |
"oxygenBalance": -56, | |
"thermalConductivity": 0.28, | |
"specificHeat": 1240, | |
"thermalExpansion": 7.8e-5, | |
"shockSensitivity": 85, | |
"frictionSensitivity": 140, | |
"impactSensitivity": 8, | |
"expansionRatio": 720, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.20, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.34, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.26, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.20, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "Dynamite", | |
"detonationVelocity": 6000, | |
"energyDensity": 4.0, | |
"density": 1.50, | |
"stability": 2, | |
"criticalDiameter": 2.5, | |
"activationEnergy": 150, | |
"gurvichTemperature": 3200, | |
"chapmanJouguetPressure": 15, | |
"oxygenBalance": -10, | |
"thermalConductivity": 0.20, | |
"specificHeat": 1200, | |
"thermalExpansion": 8.0e-5, | |
"shockSensitivity": 50, | |
"frictionSensitivity": 50, | |
"impactSensitivity": 2, | |
"expansionRatio": 650, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.25, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.30, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.30, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "ANFO", | |
"detonationVelocity": 4500, | |
"energyDensity": 3.7, | |
"density": 0.80, | |
"stability": 4, | |
"criticalDiameter": 10.0, | |
"activationEnergy": 100, | |
"gurvichTemperature": 2800, | |
"chapmanJouguetPressure": 5, | |
"oxygenBalance": 10, | |
"thermalConductivity": 0.15, | |
"specificHeat": 1150, | |
"thermalExpansion": 9.0e-5, | |
"shockSensitivity": 100, | |
"frictionSensitivity": 200, | |
"impactSensitivity": 10, | |
"expansionRatio": 600, | |
"detonationProducts": [ | |
{ "formula": "CO2", "moleFraction": 0.40, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.45, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "Nitroglycerin", | |
"detonationVelocity": 7700, | |
"energyDensity": 6.4, | |
"density": 1.60, | |
"stability": 1, | |
"criticalDiameter": 0.5, | |
"activationEnergy": 150, | |
"gurvichTemperature": 4500, | |
"chapmanJouguetPressure": 25, | |
"oxygenBalance": 3.5, | |
"thermalConductivity": 0.22, | |
"specificHeat": 1230, | |
"thermalExpansion": 7.9e-5, | |
"shockSensitivity": 190, | |
"frictionSensitivity": 353, | |
"impactSensitivity": 15, | |
"expansionRatio": 690, | |
"detonationProducts": [ | |
{ "formula": "CO2", "moleFraction": 0.50, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.30, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 }, | |
{ "formula": "O2", "moleFraction": 0.05, "heatCapacity": 29.4 } | |
] | |
}, | |
{ | |
"name": "Black Powder", | |
"detonationVelocity": null, | |
"energyDensity": 3.0, | |
"density": 1.70, | |
"stability": 2, | |
"criticalDiameter": null, | |
"activationEnergy": 100, | |
"gurvichTemperature": 2500, | |
"chapmanJouguetPressure": null, | |
"oxygenBalance": -40, | |
"thermalConductivity": 0.10, | |
"specificHeat": 1000, | |
"thermalExpansion": 5.0e-5, | |
"shockSensitivity": 50, | |
"frictionSensitivity": 50, | |
"impactSensitivity": 2, | |
"expansionRatio": 500, | |
"detonationProducts": [ | |
{ "formula": "CO2", "moleFraction": 0.30, "heatCapacity": 37.1 }, | |
{ "formula": "CO", "moleFraction": 0.20, "heatCapacity": 29.1 }, | |
{ "formula": "N2", "moleFraction": 0.20, "heatCapacity": 29.1 }, | |
{ "formula": "SO2", "moleFraction": 0.30, "heatCapacity": 39.9 } | |
] | |
}, | |
{ | |
"name": "Smokeless Powder", | |
"detonationVelocity": null, | |
"energyDensity": 4.0, | |
"density": 1.60, | |
"stability": 4, | |
"criticalDiameter": null, | |
"activationEnergy": 150, | |
"gurvichTemperature": 3000, | |
"chapmanJouguetPressure": null, | |
"oxygenBalance": -30, | |
"thermalConductivity": 0.20, | |
"specificHeat": 1200, | |
"thermalExpansion": 6.0e-5, | |
"shockSensitivity": 100, | |
"frictionSensitivity": 100, | |
"impactSensitivity": 5, | |
"expansionRatio": 600, | |
"detonationProducts": [ | |
{ "formula": "CO2", "moleFraction": 0.35, "heatCapacity": 37.1 }, | |
{ "formula": "CO", "moleFraction": 0.25, "heatCapacity": 29.1 }, | |
{ "formula": "H2O", "moleFraction": 0.25, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "Thermobaric Explosive (TBX)", | |
"detonationVelocity": 5000, | |
"energyDensity": 6.0, | |
"density": 1.50, | |
"stability": 3, | |
"criticalDiameter": 5.0, | |
"activationEnergy": 180, | |
"gurvichTemperature": 4000, | |
"chapmanJouguetPressure": 20, | |
"oxygenBalance": -50, | |
"thermalConductivity": 0.25, | |
"specificHeat": 1300, | |
"thermalExpansion": 7.0e-5, | |
"shockSensitivity": 90, | |
"frictionSensitivity": 150, | |
"impactSensitivity": 10, | |
"expansionRatio": 800, | |
"detonationProducts": [ | |
{ "formula": "CO2", "moleFraction": 0.40, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.35, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 }, | |
{ "formula": "CO", "moleFraction": 0.10, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "TATB", | |
"detonationVelocity": 7300, | |
"energyDensity": 4.1, | |
"density": 1.93, | |
"stability": 5, | |
"criticalDiameter": 10.0, | |
"activationEnergy": 220, | |
"gurvichTemperature": 3000, | |
"chapmanJouguetPressure": 28, | |
"oxygenBalance": -54, | |
"thermalConductivity": 0.32, | |
"specificHeat": 1180, | |
"thermalExpansion": 7.0e-5, | |
"shockSensitivity": 100, | |
"frictionSensitivity": 300, | |
"impactSensitivity": 30, | |
"expansionRatio": 650, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.25, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.22, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.18, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.35, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "TATP", | |
"detonationVelocity": 5300, | |
"energyDensity": 4.0, | |
"density": 1.20, | |
"stability": 1, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 110, | |
"gurvichTemperature": 2800, | |
"chapmanJouguetPressure": 6, | |
"oxygenBalance": 0, | |
"thermalConductivity": 0.2, | |
"specificHeat": 1200, | |
"thermalExpansion": 1.0e-4, | |
"shockSensitivity": 200, | |
"frictionSensitivity": 150, | |
"impactSensitivity": 2, | |
"expansionRatio": 620, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.25, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.45, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.25, "heatCapacity": 33.6 }, | |
{ "formula": "O2", "moleFraction": 0.05, "heatCapacity": 29.4 } | |
] | |
}, | |
{ | |
"name": "HMTD", | |
"detonationVelocity": 4100, | |
"energyDensity": 3.5, | |
"density": 1.57, | |
"stability": 1, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 120, | |
"gurvichTemperature": 2600, | |
"chapmanJouguetPressure": 5, | |
"oxygenBalance": 0, | |
"thermalConductivity": 0.18, | |
"specificHeat": 1160, | |
"thermalExpansion": 9.0e-5, | |
"shockSensitivity": 180, | |
"frictionSensitivity": 120, | |
"impactSensitivity": 3, | |
"expansionRatio": 550, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.20, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.40, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.30, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.10, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "CL-20", | |
"detonationVelocity": 9400, | |
"energyDensity": 6.4, | |
"density": 2.04, | |
"stability": 3, | |
"criticalDiameter": 0.5, | |
"activationEnergy": 220, | |
"gurvichTemperature": 4600, | |
"chapmanJouguetPressure": 47, | |
"oxygenBalance": -10, | |
"thermalConductivity": 0.31, | |
"specificHeat": 1250, | |
"thermalExpansion": 7.5e-5, | |
"shockSensitivity": 85, | |
"frictionSensitivity": 160, | |
"impactSensitivity": 7, | |
"expansionRatio": 800, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.18, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.40, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.25, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.17, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "FOX-7", | |
"detonationVelocity": 8950, | |
"energyDensity": 5.0, | |
"density": 1.88, | |
"stability": 4, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 180, | |
"gurvichTemperature": 4300, | |
"chapmanJouguetPressure": 34, | |
"oxygenBalance": -22, | |
"thermalConductivity": 0.3, | |
"specificHeat": 1220, | |
"thermalExpansion": 7.6e-5, | |
"shockSensitivity": 70, | |
"frictionSensitivity": 80, | |
"impactSensitivity": 3, | |
"expansionRatio": 750, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.25, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.35, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.25, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 } | |
] | |
}, | |
{ | |
"name": "FOX-12", | |
"detonationVelocity": 8100, | |
"energyDensity": 5.2, | |
"density": 1.74, | |
"stability": 4, | |
"criticalDiameter": 1.0, | |
"activationEnergy": 200, | |
"gurvichTemperature": 4200, | |
"chapmanJouguetPressure": 30, | |
"oxygenBalance": -25, | |
"thermalConductivity": 0.28, | |
"specificHeat": 1230, | |
"thermalExpansion": 7.8e-5, | |
"shockSensitivity": 80, | |
"frictionSensitivity": 100, | |
"impactSensitivity": 5, | |
"expansionRatio": 760, | |
"detonationProducts": [ | |
{ "formula": "CO", "moleFraction": 0.22, "heatCapacity": 29.1 }, | |
{ "formula": "CO2", "moleFraction": 0.36, "heatCapacity": 37.1 }, | |
{ "formula": "H2O", "moleFraction": 0.27, "heatCapacity": 33.6 }, | |
{ "formula": "N2", "moleFraction": 0.15, "heatCapacity": 29.1 } | |
] | |
} | |
]; | |
// Enhanced casing materials database | |
const casingMaterials = [ | |
{ | |
material: 'Steel (mild)', | |
density: 7.85, | |
yieldStrength: 250, | |
ultimateStrength: 420, | |
elasticModulus: 200, | |
hardness: 130, | |
elongation: 25, | |
thermalConductivity: 45, | |
meltingPoint: 1723, | |
fractureToughness: 50, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
{ | |
material: 'Steel (high strength)', | |
density: 7.85, | |
yieldStrength: 550, | |
ultimateStrength: 800, | |
elasticModulus: 210, | |
hardness: 280, | |
elongation: 15, | |
thermalConductivity: 40, | |
meltingPoint: 1723, | |
fractureToughness: 80, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 5, | |
fragmentationPattern: 'natural' | |
}, | |
{ | |
material: 'Aluminum', | |
density: 2.7, | |
yieldStrength: 240, | |
ultimateStrength: 310, | |
elasticModulus: 70, | |
hardness: 75, | |
elongation: 12, | |
thermalConductivity: 167, | |
meltingPoint: 933, | |
fractureToughness: 24, | |
poissonRatio: 0.33, | |
heatCapacity: 900, | |
corrosionResistance: 7, | |
fragmentationPattern: 'natural' | |
}, | |
{ | |
material: 'Titanium Alloy', | |
density: 4.5, | |
yieldStrength: 880, | |
ultimateStrength: 950, | |
elasticModulus: 110, | |
hardness: 320, | |
elongation: 14, | |
thermalConductivity: 7.2, | |
meltingPoint: 1941, | |
fractureToughness: 100, | |
poissonRatio: 0.34, | |
heatCapacity: 530, | |
corrosionResistance: 9, | |
fragmentationPattern: 'controlled' | |
} | |
]; | |
// Example environment database | |
const environments = [ | |
{ | |
name: 'Standard Atmosphere', | |
temperature: 293.15, // K (20°C) | |
pressure: 101.325, // kPa | |
humidity: 60, // % | |
altitude: 0, // m | |
reflectiveSurfaces: false | |
}, | |
{ | |
name: 'Desert', | |
temperature: 318.15, // K (45°C) | |
pressure: 101.325, // kPa | |
humidity: 15, // % | |
altitude: 300, // m | |
reflectiveSurfaces: false, | |
soilType: 'sandy', | |
soilDensity: 1.6 // g/cm³ | |
}, | |
{ | |
name: 'Arctic', | |
temperature: 253.15, // K (-20°C) | |
pressure: 101.325, // kPa | |
humidity: 80, // % | |
altitude: 0, // m | |
reflectiveSurfaces: true, | |
soilType: 'frozen', | |
soilDensity: 1.9 // g/cm³ | |
}, | |
{ | |
name: 'Underwater', | |
temperature: 283.15, // K (10°C) | |
pressure: 202.65, // kPa (at 10m depth) | |
humidity: 100, // % | |
altitude: -10, // m | |
reflectiveSurfaces: false, | |
waterDepth: 10 // m | |
} | |
]; | |
// Example of comprehensive output for the existing scenarios | |
function runComprehensiveScenario( | |
name: string, | |
radius: number, | |
shape: Shape, | |
maxWeight: number, | |
isPortable: boolean, | |
casing: Casing, | |
environment: Environment, | |
targets?: Target[] | |
) { | |
// Create box-drawing characters and formatting helpers | |
const h = "═", v = "║", tl = "╔", tr = "╗", bl = "╚", br = "╝"; | |
const ltee = "╠", rtee = "╣", ttee = "╦", btee = "╩", cross = "╬"; | |
const dash = "─", pipe = "│", plus = "+", space = " "; | |
// Helper function to create a header box | |
const createHeader = (text: string, width: number = 80) => { | |
// Calculate minimum required width based on content | |
const minWidth = text.length + 4; // 2 chars padding on each side | |
const actualWidth = Math.max(width, minWidth); | |
const padding = actualWidth - text.length - 4; | |
const leftPad = Math.floor(padding / 2); | |
const rightPad = padding - leftPad; | |
const headerLine = `${tl}${h.repeat(actualWidth - 2)}${tr}`; | |
const contentLine = `${v}${space.repeat(leftPad + 1)}${text}${space.repeat(rightPad + 1)}${v}`; | |
const footerLine = `${bl}${h.repeat(actualWidth - 2)}${br}`; | |
console.log(headerLine); | |
console.log(contentLine); | |
console.log(footerLine); | |
return actualWidth; // Return the actual width used for consistency | |
}; | |
// Helper function to create a section header | |
const createSection = (text: string, width: number = 80) => { | |
// Calculate minimum required width based on content | |
const minWidth = text.length + 6; // For "═ " and " ═" | |
const actualWidth = Math.max(width, minWidth); | |
const padding = actualWidth - text.length - 4; | |
const sectionLine = `${ltee}${h.repeat(2)}${space}${text}${space}${h.repeat(Math.max(2, padding - 2))}${rtee}`; | |
console.log(sectionLine); | |
return actualWidth; // Return the actual width used | |
}; | |
// Ensure consistent box bottoms/separators | |
const createSeparator = (width: number = 80) => { | |
const line = `${v}${space.repeat(width - 2)}${v}`; | |
console.log(line); | |
return width; | |
}; | |
// Also update the bottom border generator | |
const createBottomBorder = (width: number = 80) => { | |
const line = `${bl}${h.repeat(width - 2)}${br}`; | |
console.log(line); | |
return width; | |
}; | |
// Helper function to display a value with proper alignment | |
const displayValue = (label: string, value: string | number, unit: string = "", width: number = 80) => { | |
const formattedValue = typeof value === 'number' ? | |
(Number.isInteger(value) ? value.toString() : value.toFixed(2)) : | |
value; | |
const valueText = `${formattedValue}${unit ? ' ' + unit : ''}`; | |
// Calculate minimum required width based on content | |
const contentWidth = label.length + valueText.length + 8; // 8 for spacing/formatting | |
const actualWidth = Math.max(width, contentWidth); | |
// Ensure proper dot spacing that fills the available width | |
const dotWidth = Math.max(2, actualWidth - label.length - valueText.length - 8); | |
const dots = dash.repeat(dotWidth); | |
const valueLine = `${v} ${label} ${dots} ${valueText} ${v}`; | |
console.log(valueLine); | |
return actualWidth; // Return the actual width used | |
}; | |
// Helper function to create a data table | |
const createTable = (headers: string[], rows: string[][], widths: number[], desiredWidth: number = 80) => { | |
// First determine the actual column widths needed based on content | |
const calculatedWidths = widths.map((minWidth, idx) => { | |
// Check header width | |
let colMaxWidth = headers[idx].length + 2; // +2 for padding | |
// Check each row's content width for this column | |
rows.forEach(row => { | |
if (row[idx]) { | |
colMaxWidth = Math.max(colMaxWidth, row[idx].length + 2); | |
} | |
}); | |
// Ensure minimum width is respected | |
return Math.max(minWidth, colMaxWidth); | |
}); | |
// Calculate total table width without adjustments | |
let tableWidth = calculatedWidths.reduce((sum, w) => sum + w, 0) + calculatedWidths.length + 1; | |
// Adjust column widths to match desired width | |
if (tableWidth != desiredWidth) { | |
const widthDifference = desiredWidth - tableWidth; | |
const totalAdjustableWidth = calculatedWidths.reduce((sum, w) => sum + w, 0); | |
// Distribute the width difference proportionally across columns | |
calculatedWidths.forEach((width, idx) => { | |
const adjustmentRatio = width / totalAdjustableWidth; | |
const adjustment = Math.floor(widthDifference * adjustmentRatio); | |
calculatedWidths[idx] = Math.max(3, width + adjustment); // Ensure minimum of 3 chars per column | |
}); | |
// Recalculate table width after adjustments | |
tableWidth = calculatedWidths.reduce((sum, w) => sum + w, 0) + calculatedWidths.length + 1; | |
// Handle any remaining difference due to rounding (add to the last column) | |
const remainingDifference = desiredWidth - tableWidth; | |
if (remainingDifference !== 0 && calculatedWidths.length > 0) { | |
calculatedWidths[calculatedWidths.length - 1] += remainingDifference; | |
} | |
// Final table width should now match desired width | |
tableWidth = desiredWidth; | |
} | |
// Table top border | |
let line = `${ltee}`; | |
for (let i = 0; i < calculatedWidths.length; i++) { | |
line += h.repeat(calculatedWidths[i]); | |
line += i < calculatedWidths.length - 1 ? ttee : rtee; | |
} | |
console.log(line); | |
// Headers | |
line = `${v}`; | |
for (let i = 0; i < headers.length; i++) { | |
const padding = calculatedWidths[i] - headers[i].length; | |
line += ` ${headers[i]}${space.repeat(padding - 1)}${v}`; | |
} | |
console.log(line); | |
// Separator | |
line = `${ltee}`; | |
for (let i = 0; i < calculatedWidths.length; i++) { | |
line += h.repeat(calculatedWidths[i]); | |
line += i < calculatedWidths.length - 1 ? cross : rtee; | |
} | |
console.log(line); | |
// Data rows | |
for (const row of rows) { | |
line = `${v}`; | |
for (let i = 0; i < row.length; i++) { | |
const content = row[i] || ""; | |
const padding = calculatedWidths[i] - content.length; | |
line += ` ${content}${space.repeat(padding - 1)}${v}`; | |
} | |
console.log(line); | |
} | |
// Table bottom border | |
line = `${bl}`; | |
for (let i = 0; i < calculatedWidths.length; i++) { | |
line += h.repeat(calculatedWidths[i]); | |
line += i < calculatedWidths.length - 1 ? btee : br; | |
} | |
console.log(line); | |
return tableWidth; // Return the actual table width | |
}; | |
// Helper function to safely get a property with a default value | |
const getMetricSafely = (obj: any, path: string, defaultValue: any = "N/A") => { | |
const parts = path.split('.'); | |
let current = obj; | |
for (const part of parts) { | |
if (current === undefined || current === null) { | |
return defaultValue; | |
} | |
current = current[part]; | |
} | |
return current !== undefined && current !== null ? current : defaultValue; | |
}; | |
// Set an initial width that gets adjusted based on content | |
let displayWidth = 80; | |
// Create a scenario title | |
console.log(""); | |
displayWidth = createHeader(`SCENARIO: ${name.toUpperCase()}`, displayWidth); | |
console.log(""); | |
const scenario = { | |
radius, | |
shape, | |
maxWeight, | |
isPortable, | |
casing, | |
environment, | |
targets | |
}; | |
const result = optimizeExplosive(explosives, scenario); | |
if (result) { | |
// Display scenario parameters | |
displayWidth = createSection("SCENARIO PARAMETERS", displayWidth); | |
displayWidth = displayValue("Shape Type", scenario.shape.type, "", displayWidth); | |
displayWidth = displayValue("Maximum Weight", maxWeight, "kg", displayWidth); | |
displayWidth = displayValue("Portability", isPortable ? "Yes" : "No", "", displayWidth); | |
displayWidth = displayValue("Temperature", environment.temperature, "K", displayWidth); | |
displayWidth = createSeparator(displayWidth); | |
// Display best material | |
displayWidth = createSection("OPTIMAL EXPLOSIVE", displayWidth); | |
displayValue("Material", result.material.name); | |
displayValue("Stability Rating", `${result.metrics.stability}/5`); | |
displayValue("Detonation Velocity", result.metrics.detonationVelocity, "m/s"); | |
displayValue("Detonation Pressure", result.metrics.detonationPressure, "GPa"); | |
displayValue("Detonation Temperature", result.metrics.detonationTemperature, "K"); | |
displayWidth = createSeparator(displayWidth); | |
// Display mass and energy | |
displayWidth = createSection("MASS AND ENERGY PROFILE", displayWidth); | |
displayValue("TNT Equivalence Factor", result.metrics.tntEquivalent); | |
displayValue("Explosive Mass", result.metrics.explosiveMass, "kg"); | |
displayValue("Casing Mass", result.metrics.casingMass, "kg"); | |
displayValue("Total Weight", result.metrics.totalWeight, "kg"); | |
displayValue("Total Energy", result.metrics.explosionEnergy, "MJ"); | |
displayWidth = createSeparator(displayWidth); | |
// Display blast effects | |
displayWidth = createSection(`BLAST EFFECTS AT ${radius}m`, displayWidth); | |
displayValue("Peak Overpressure", result.metrics.overpressure, "kPa"); | |
displayValue("Positive Phase Duration", result.metrics.positivePhaseDuration, "ms"); | |
displayValue("Positive Phase Impulse", result.metrics.positivePhaseImpulse, "kPa·ms"); | |
displayValue("Reflected Pressure", result.metrics.reflectedPressure, "kPa"); | |
displayWidth = createSeparator(displayWidth); | |
// Display fragmentation effects | |
displayWidth = createSection("FRAGMENTATION PROFILE", displayWidth); | |
displayValue("Initial Fragment Velocity", result.metrics.fragmentVelocity.initialVelocity, "m/s"); | |
displayValue("Terminal Fragment Velocity", result.metrics.fragmentVelocity.terminalVelocity, "m/s"); | |
displayValue("Average Fragment Mass", result.metrics.averageFragmentMass, "g"); | |
displayValue("Fragment Count", Math.round(result.metrics.numberOfFragments)); | |
displayValue("Maximum Fragment Size", result.metrics.maxFragmentSize, "cm"); | |
displayWidth = createSeparator(displayWidth); | |
// Display thermal effects | |
displayWidth = createSection("THERMAL EFFECTS", displayWidth); | |
displayValue(`Thermal Radiation at ${radius}m`, getMetricSafely(result.metrics, "thermalRadiation"), "J/cm²"); | |
displayValue("Fireball Radius", getMetricSafely(result.metrics, "fireball.radius"), "m"); | |
displayValue("Fireball Duration", getMetricSafely(result.metrics, "fireball.duration"), "s"); | |
displayWidth = createSeparator(displayWidth); | |
// Display ground shock effects if available | |
if (getMetricSafely(result.metrics, "groundShock") !== "N/A") { | |
displayWidth = createSection(`GROUND SHOCK AT ${radius}m`, displayWidth); | |
displayValue("Peak Particle Velocity", getMetricSafely(result.metrics, "groundShock.peakParticleVelocity"), "m/s"); | |
displayValue("Peak Acceleration", getMetricSafely(result.metrics, "groundShock.peakAcceleration"), "g"); | |
displayValue("Peak Displacement", getMetricSafely(result.metrics, "groundShock.peakDisplacement"), "mm"); | |
displayWidth = createSeparator(displayWidth); | |
} | |
// Display underwater effects if available | |
if (getMetricSafely(result.metrics, "underwaterEffects") !== "N/A") { | |
displayWidth = createSection("UNDERWATER EFFECTS", displayWidth); | |
displayValue("Peak Pressure", getMetricSafely(result.metrics, "underwaterEffects.peakPressure"), "MPa"); | |
displayValue("Maximum Bubble Radius", getMetricSafely(result.metrics, "underwaterEffects.bubblePulsation.maxRadius"), "m"); | |
displayValue("First Bubble Period", getMetricSafely(result.metrics, "underwaterEffects.bubblePulsation.firstPeriod"), "s"); | |
displayValue("Second Bubble Period", getMetricSafely(result.metrics, "underwaterEffects.bubblePulsation.secondPeriod"), "s"); | |
displayWidth = createSeparator(displayWidth); | |
} | |
// Display shaped charge effects if available | |
if (getMetricSafely(result.metrics, "shapedChargeEffects") !== "N/A") { | |
displayWidth = createSection("SHAPED CHARGE PERFORMANCE", displayWidth); | |
displayValue("Jet Velocity", getMetricSafely(result.metrics, "shapedChargeEffects.jetVelocity"), "m/s"); | |
displayValue("Penetration Depth", getMetricSafely(result.metrics, "shapedChargeEffects.penetrationDepth"), "cm"); | |
displayValue("Jet Length", getMetricSafely(result.metrics, "shapedChargeEffects.jetLength"), "cm"); | |
displayWidth = createSeparator(displayWidth); | |
} | |
// Display target effects if available | |
if (getMetricSafely(result.metrics, "targetsAssessment") !== "N/A" && result.metrics.targetsAssessment.length > 0) { | |
displayWidth = createSection("TARGET EFFECTS ASSESSMENT", displayWidth); | |
result.metrics.targetsAssessment.forEach((assessment: any, index: number) => { | |
const targetName = `Target ${index + 1}: ${assessment.target.material}`; | |
displayValue(targetName, `at ${assessment.target.distance}m`); | |
displayValue(" Peak Overpressure", getMetricSafely(assessment, "blastParams.overpressure"), "kPa"); | |
displayValue(" Positive Phase Impulse", getMetricSafely(assessment, "blastParams.positivePhaseImpulse"), "kPa·ms"); | |
if (getMetricSafely(assessment, "injury") !== "N/A") { | |
displayValue(" Lethality Probability", getMetricSafely(assessment, "injury.lethality"), "%"); | |
displayValue(" Lung Damage", getMetricSafely(assessment, "injury.lungDamage")); | |
displayValue(" Eardrum Rupture Probability", getMetricSafely(assessment, "injury.eardrumRupture"), "%"); | |
} | |
if (getMetricSafely(assessment, "damage") !== "N/A") { | |
displayValue(" Structural Damage", getMetricSafely(assessment, "damage.damagePercentage"), "%"); | |
displayValue(" Damage Mode", getMetricSafely(assessment, "damage.damageMode")); | |
} | |
if (index < result.metrics.targetsAssessment.length - 1) { | |
displayWidth = createSeparator(displayWidth); | |
} | |
}); | |
displayWidth = createSeparator(displayWidth); | |
} | |
// Display safety summary | |
displayWidth = createSection("SAFETY AND HAZARD SUMMARY", displayWidth); | |
// Create safety rating (calculated from stability and sensitivity values) | |
const safetyScore = 5 - result.metrics.stability; // Invert so lower is better | |
let safetyRating = ""; | |
if (safetyScore <= 1) safetyRating = "VERY SAFE"; | |
else if (safetyScore <= 2) safetyRating = "SAFE"; | |
else if (safetyScore <= 3) safetyRating = "MODERATE HAZARD"; | |
else if (safetyScore <= 4) safetyRating = "DANGEROUS"; | |
else safetyRating = "EXTREME HAZARD"; | |
displayValue("Safety Rating", safetyRating); | |
displayValue("Handling Precautions", result.metrics.stability <= 2 ? "Standard" : "Special"); | |
displayWidth = createSeparator(displayWidth); | |
// Close the bottom of the display | |
displayWidth = createBottomBorder(displayWidth); | |
console.log(""); // Add final line break | |
} else { | |
let maxWidth = 80; | |
// Run parameter optimization to find a feasible scenario | |
const optimization = optimizeScenarioParameters( | |
explosives, | |
{ radius, shape, maxWeight, isPortable, casing, environment, targets } | |
); | |
const headerText = `OPTIMIZATION REQUIRED: No suitable material found for "${name}"`; | |
// Calculate minimum width required for the header text | |
const headerMinWidth = headerText.length; // 2 chars padding on each side | |
maxWidth = Math.max(maxWidth, headerMinWidth); | |
if (optimization?.recommendedChanges.length > 0) { | |
// Pre-calculate width needed for all parameter changes to ensure consistency | |
for (const change of optimization.recommendedChanges) { | |
const label = change.parameter; | |
const valueText = `${change.originalValue} → ${change.newValue} ${change.unit}`; | |
maxWidth = Math.max(maxWidth, label.length + valueText.length + 8); | |
} | |
} | |
// Add some buffer room for good measure | |
maxWidth += 4; | |
// Create header for optimization required message | |
createHeader(headerText, maxWidth); | |
if (optimization.feasible) { | |
// Display recommended changes section | |
createSection("Recommended Parameter Adjustments", maxWidth); | |
// Display recommended changes | |
for (const change of optimization.recommendedChanges) { | |
if (change.originalValue !== change.newValue) { | |
displayValue(change.parameter, `${change.originalValue} → ${typeof change.newValue === 'number' ? Number.isInteger(change.newValue) ? change.newValue.toString() : change.newValue.toFixed(2) : change.newValue}`, change.unit, maxWidth); | |
} | |
} | |
// Display optimization metrics section | |
createSection("Optimization Metrics", maxWidth); | |
displayValue("Confidence", `${optimization.confidence.toFixed(2)}%`, "", maxWidth); | |
displayValue("Required Changes", optimization.recommendedChanges.length, "", maxWidth); | |
displayValue("Estimated Material", optimization.estimatedMaterial.name, "", maxWidth); | |
// Display sensitivity analysis section | |
createSection("Parameter Sensitivity Analysis", maxWidth); | |
const headers = ["Parameter", "Sensitivity", "Feasibility Impact"]; | |
const rows = optimization.sensitivityAnalysis.map(item => | |
[item.parameter, item.sensitivity.toFixed(3), item.impact] | |
); | |
createTable(headers, rows, [40, 15, 25], maxWidth); | |
} else { | |
// Display optimization failed section | |
createSection("Optimization Failed", maxWidth); | |
console.log(`${v} No feasible parameter combination could be found with the available explosive ${v}`); | |
console.log(`${v} materials. Consider acquiring specialized materials or fundamentally ${v}`); | |
console.log(`${v} redesigning the application. ${v}`); | |
createBottomBorder(maxWidth); | |
} | |
} | |
} | |
/** | |
* Optimizes scenario parameters to make it feasible with available explosive materials | |
* using advanced multi-objective optimization techniques and physical constraints. | |
*/ | |
function optimizeScenarioParameters( | |
availableExplosives: Explosive[], | |
scenario: { | |
radius: number, | |
shape: Shape, | |
maxWeight: number, | |
isPortable: boolean, | |
casing: Casing, | |
environment: Environment, | |
targets?: Target[] | |
} | |
): { | |
feasible: boolean; | |
recommendedChanges: Array<{ | |
parameter: string; | |
originalValue: number | string; | |
newValue: number | string; | |
unit: string; | |
}>; | |
confidence: number; | |
estimatedMaterial: Explosive; | |
sensitivityAnalysis: Array<{ | |
parameter: string; | |
sensitivity: number; | |
impact: string; | |
}>; | |
} { | |
// Deep clone the scenario to avoid modifying the original | |
const originalScenario = JSON.parse(JSON.stringify(scenario)); | |
let optimizedScenario = JSON.parse(JSON.stringify(scenario)); | |
const recommendedChanges: Array<{ parameter: string; originalValue: number | string; newValue: number | string; unit: string }> = []; | |
// Initialize sensitivity analysis tracking | |
const sensitivityTracking: Record<string, { sensitivity: number, impact: string }> = {}; | |
// ==== Step 1: Analyze constraint violations ==== | |
const constraintAnalysis = analyzeConstraintViolations(availableExplosives, originalScenario); | |
if (constraintAnalysis.criticalConstraints.length === 0) { | |
// This shouldn't happen - if there are no violations, optimization should have succeeded | |
return { | |
feasible: false, | |
recommendedChanges: [], | |
confidence: 0, | |
estimatedMaterial: availableExplosives[0], | |
sensitivityAnalysis: [] | |
}; | |
} | |
// ==== Step 2: Apply Multi-objective optimization ==== | |
// Using a combination of gradient descent, genetic algorithms, and physics-based heuristics | |
// Start with most critical parameters identified in constraint analysis | |
for (const constraint of constraintAnalysis.criticalConstraints) { | |
const parameterOpts = generateParameterOptions(constraint, originalScenario, availableExplosives); | |
// Try each parameter option and evaluate its effectiveness and physical feasibility | |
for (const option of parameterOpts) { | |
// Apply the parameter change | |
applyParameterChange(optimizedScenario, option.parameter, option.suggestedValue); | |
// Track sensitivity | |
const sensitivity = evaluateParameterSensitivity( | |
originalScenario, | |
optimizedScenario, | |
option.parameter, | |
availableExplosives | |
); | |
sensitivityTracking[option.parameter] = { | |
sensitivity, | |
impact: describeSensitivityImpact(sensitivity) | |
}; | |
// Verify physical consistency of the changes | |
if (!isPhysicallyConsistent(optimizedScenario)) { | |
// Revert change if physically inconsistent | |
applyParameterChange(optimizedScenario, option.parameter, originalParameterValue(originalScenario, option.parameter)); | |
continue; | |
} | |
// Check if this change improves feasibility | |
if (improvesFeasibility(originalScenario, optimizedScenario, option.parameter, availableExplosives)) { | |
recommendedChanges.push({ | |
parameter: option.parameterName, | |
originalValue: originalParameterValue(originalScenario, option.parameter), | |
newValue: option.suggestedValue, | |
unit: option.unit | |
}); | |
// Break if we've achieved feasibility | |
const result = optimizeExplosive(availableExplosives, optimizedScenario); | |
if (result) { | |
return { | |
feasible: true, | |
recommendedChanges, | |
confidence: calculateConfidence(recommendedChanges, originalScenario, optimizedScenario), | |
estimatedMaterial: result.material, | |
sensitivityAnalysis: Object.entries(sensitivityTracking).map(([param, data]) => ({ | |
parameter: param, | |
sensitivity: data.sensitivity, | |
impact: data.impact | |
})) | |
}; | |
} | |
} else { | |
// Revert change if it doesn't improve feasibility | |
applyParameterChange(optimizedScenario, option.parameter, originalParameterValue(originalScenario, option.parameter)); | |
} | |
} | |
} | |
// ==== Step 3: Apply advanced optimization techniques if simple changes didn't work ==== | |
// If still not feasible, employ particle swarm optimization or simulated annealing | |
const { improved, newScenario, changes } = applyAdvancedOptimization( | |
originalScenario, | |
recommendedChanges, | |
availableExplosives, | |
sensitivityTracking | |
); | |
if (improved) { | |
optimizedScenario = newScenario; | |
recommendedChanges.push(...changes); | |
const result = optimizeExplosive(availableExplosives, optimizedScenario); | |
if (result) { | |
return { | |
feasible: true, | |
recommendedChanges, | |
confidence: calculateConfidence(recommendedChanges, originalScenario, optimizedScenario), | |
estimatedMaterial: result.material, | |
sensitivityAnalysis: Object.entries(sensitivityTracking).map(([param, data]) => ({ | |
parameter: param, | |
sensitivity: data.sensitivity, | |
impact: data.impact | |
})) | |
}; | |
} | |
} | |
// ==== Step 4: If all optimizations fail, find the closest possible scenario ==== | |
const closestScenario = findClosestFeasibleScenario(originalScenario, availableExplosives); | |
if (closestScenario.distance < Number.MAX_VALUE) { | |
return { | |
feasible: true, | |
recommendedChanges: closestScenario.changes, | |
confidence: 70 - (closestScenario.distance * 10), // Lower confidence based on distance | |
estimatedMaterial: closestScenario.material, | |
sensitivityAnalysis: Object.entries(sensitivityTracking).map(([param, data]) => ({ | |
parameter: param, | |
sensitivity: data.sensitivity, | |
impact: data.impact | |
})) | |
}; | |
} | |
return { | |
feasible: false, | |
recommendedChanges, | |
confidence: 0, | |
estimatedMaterial: availableExplosives[0], // Default to first available as fallback | |
sensitivityAnalysis: Object.entries(sensitivityTracking).map(([param, data]) => ({ | |
parameter: param, | |
sensitivity: data.sensitivity, | |
impact: data.impact | |
})) | |
}; | |
} | |
/** | |
* Analyzes which constraints are preventing optimization success | |
*/ | |
function analyzeConstraintViolations( | |
explosives: Explosive[], | |
scenario: any | |
): { | |
criticalConstraints: Array<{ type: string, severity: number, parameter: string, parameterName?: string }> | |
} { | |
const criticalConstraints: Array<{ type: string, severity: number, parameter: string, parameterName?: string }> = []; | |
// Analyze explosion yield requirements vs available materials | |
const yieldRequirements = estimateRequiredYield(scenario); | |
const maxAvailableYield = Math.max(...explosives.map(e => | |
e.energyDensity * scenario.maxWeight | |
)); | |
if (yieldRequirements.totalRequiredEnergy > maxAvailableYield) { | |
criticalConstraints.push({ | |
type: "energy_yield", | |
severity: yieldRequirements.totalRequiredEnergy / maxAvailableYield, | |
parameter: "maxWeight", | |
parameterName: "Maximum Weight" | |
}); | |
} | |
// Check density constraints | |
const volumeConstraint = calculateAvailableVolume(scenario.radius, scenario.shape); | |
const minRequiredDensity = scenario.maxWeight / volumeConstraint; | |
const maxAvailableDensity = Math.max(...explosives.map(e => e.density)); | |
if (minRequiredDensity > maxAvailableDensity) { | |
criticalConstraints.push({ | |
type: "density", | |
severity: minRequiredDensity / maxAvailableDensity, | |
parameter: "radius", | |
parameterName: "Radius" | |
}); | |
} | |
// Check detonation velocity requirements | |
if (scenario.targets) { | |
const minRequiredVelocity = estimateRequiredDetonationVelocity(scenario); | |
const maxAvailableVelocity = Math.max(...explosives.map(e => e.detonationVelocity)); | |
if (minRequiredVelocity > maxAvailableVelocity) { | |
criticalConstraints.push({ | |
type: "detonation_velocity", | |
severity: minRequiredVelocity / maxAvailableVelocity, | |
parameter: "shape.type", | |
parameterName: "Shape Type" | |
}); | |
} | |
} | |
// Check shape charge parameters if applicable | |
if (scenario.shape.type === 'shaped_charge' && scenario.shape.linerProperties) { | |
const optimalStandoff = ExplosiveModel.calculateOptimalStandoff(scenario.shape.linerProperties); | |
const currentStandoff = scenario.shape.dimensions.standoff || 0; | |
if (Math.abs(currentStandoff - optimalStandoff) / optimalStandoff > 0.3) { | |
criticalConstraints.push({ | |
type: "standoff_distance", | |
severity: Math.abs(currentStandoff - optimalStandoff) / optimalStandoff, | |
parameter: "shape.dimensions.standoff", | |
parameterName: "Standoff Distance" | |
}); | |
} | |
} | |
// Check casing property constraints | |
const casingConstraints = analyzeOptimalCasingProperties(scenario, explosives); | |
if (casingConstraints.nonOptimal) { | |
casingConstraints.issues.forEach(issue => { | |
criticalConstraints.push({ | |
type: `casing_${issue.property}`, | |
severity: issue.severity, | |
parameter: `casing.${issue.property}`, | |
parameterName: `Casing ${issue.property.charAt(0).toUpperCase() + issue.property.slice(1)}` | |
}); | |
}); | |
} | |
// Check critical diameter constraints | |
const minRequiredDiameter = estimateRequiredCriticalDiameter(scenario); | |
const minAvailableDiameter = Math.min(...explosives.map(e => e.criticalDiameter)); | |
// Double the radius to get diameter and convert from cm to mm | |
const scenarioDiameter = 2 * scenario.radius * 10; | |
if (minRequiredDiameter > scenarioDiameter) { | |
criticalConstraints.push({ | |
type: "critical_diameter", | |
severity: minRequiredDiameter / scenarioDiameter, | |
parameter: "radius", | |
parameterName: "Radius (Critical Diameter)" | |
}); | |
} | |
// Check stability requirements for portable scenarios | |
if (scenario.isPortable) { | |
const minRequiredStability = 3; // Assume minimum stability for portable applications | |
const materialWithSufficientStability = explosives.some(e => | |
e.stability >= minRequiredStability && | |
e.energyDensity * scenario.maxWeight >= yieldRequirements.totalRequiredEnergy | |
); | |
if (!materialWithSufficientStability) { | |
criticalConstraints.push({ | |
type: "stability", | |
severity: 1.5, // Fixed severity for this constraint | |
parameter: "isPortable", | |
parameterName: "Portability Requirement" | |
}); | |
} | |
} | |
// Analyze environmental constraints | |
if (scenario.environment.waterDepth !== undefined && scenario.environment.waterDepth > 0) { | |
// Underwater explosion special constraints | |
const pressureEffect = 1 + (scenario.environment.waterDepth * 9.81 * 1000 / 101325); | |
if (pressureEffect > 2) { | |
criticalConstraints.push({ | |
type: "water_pressure", | |
severity: pressureEffect / 2, | |
parameter: "environment.waterDepth", | |
parameterName: "Water Depth" | |
}); | |
} | |
} | |
// High altitude considerations | |
if (scenario.environment.altitude > 2000) { | |
const pressureDropFactor = Math.exp(-scenario.environment.altitude / 8000); | |
const effectiveYieldReduction = 1 / pressureDropFactor; | |
if (effectiveYieldReduction > 1.3) { | |
criticalConstraints.push({ | |
type: "altitude_effect", | |
severity: effectiveYieldReduction, | |
parameter: "environment.altitude", | |
parameterName: "Altitude" | |
}); | |
} | |
} | |
// Temperature extreme considerations | |
const standardTemp = 293; // K (20°C) | |
if (Math.abs(scenario.environment.temperature - standardTemp) > 50) { | |
const temperatureEffect = 1 + Math.abs(scenario.environment.temperature - standardTemp) / 100; | |
criticalConstraints.push({ | |
type: "temperature_extreme", | |
severity: temperatureEffect, | |
parameter: "environment.temperature", | |
parameterName: "Environmental Temperature" | |
}); | |
} | |
// Sort constraints by severity | |
criticalConstraints.sort((a, b) => b.severity - a.severity); | |
return { criticalConstraints }; | |
} | |
/** | |
* Estimates required detonation performance based on scenario | |
*/ | |
function estimateRequiredYield(scenario: any): { | |
totalRequiredEnergy: number, | |
blastEnergy: number, | |
fragmentationEnergy: number, | |
penetrationEnergy: number | |
} { | |
let totalRequiredEnergy = 0; | |
let blastEnergy = 0; | |
let fragmentationEnergy = 0; | |
let penetrationEnergy = 0; | |
// Calculate blast energy requirements | |
if (scenario.targets) { | |
for (const target of scenario.targets) { | |
// Estimate overpressure at target distance | |
const targetOverpressure = target.criticalDamageThreshold; | |
const distanceCubed = Math.pow(target.distance, 3); | |
// Simplified Hopkinson-Cranz scaling | |
const requiredEnergy = (targetOverpressure * distanceCubed) / | |
(ExplosiveModel.AIR_DENSITY * ExplosiveModel.ATMOSPHERIC_PRESSURE); | |
blastEnergy = Math.max(blastEnergy, requiredEnergy); | |
// Additional energy for penetration if applicable | |
if (target.material !== 'human' && target.thickness > 0) { | |
// Penetration energy model based on material properties | |
const penetrationE = target.thickness * target.density * | |
target.crossSection * 5000; // Energy density for material deformation | |
penetrationEnergy = Math.max(penetrationEnergy, penetrationE); | |
} | |
} | |
} | |
// Fragmentation energy depends on casing | |
fragmentationEnergy = scenario.casing.density * | |
scenario.casing.thickness * | |
calculateSurfaceArea(scenario.radius, scenario.shape) * | |
400; // Energy to accelerate fragments (J/g) | |
totalRequiredEnergy = blastEnergy + fragmentationEnergy + penetrationEnergy; | |
// Add safety margin | |
totalRequiredEnergy *= 1.25; | |
// Convert to MJ if in joules | |
if (totalRequiredEnergy > 1000000) { | |
totalRequiredEnergy /= 1000000; | |
} | |
return { | |
totalRequiredEnergy, | |
blastEnergy: blastEnergy > 1000000 ? blastEnergy / 1000000 : blastEnergy, | |
fragmentationEnergy: fragmentationEnergy > 1000000 ? fragmentationEnergy / 1000000 : fragmentationEnergy, | |
penetrationEnergy: penetrationEnergy > 1000000 ? penetrationEnergy / 1000000 : penetrationEnergy | |
}; | |
} | |
/** | |
* Calculate surface area based on shape type | |
*/ | |
function calculateSurfaceArea(radius: number, shape: Shape): number { | |
switch (shape.type) { | |
case 'sphere': | |
return 4 * Math.PI * Math.pow(radius, 2); | |
case 'cylinder': { | |
const height = shape.dimensions.height || (2 * radius); | |
return 2 * Math.PI * radius * (radius + height); | |
} | |
case 'cube': { | |
const side = shape.dimensions.side || (2 * radius); | |
return 6 * Math.pow(side, 2); | |
} | |
case 'hemisphere': | |
return 3 * Math.PI * Math.pow(radius, 2); | |
case 'conical': { | |
const height = shape.dimensions.height || (2 * radius); | |
const slantHeight = Math.sqrt(Math.pow(radius, 2) + Math.pow(height, 2)); | |
return Math.PI * radius * (radius + slantHeight); | |
} | |
case 'shaped_charge': { | |
// Approximation for shaped charge with conical liner | |
const height = shape.dimensions.height || (2 * radius); | |
const coneAngle = shape.dimensions.coneAngle || 60; // Default angle if not specified | |
const slantHeight = height / Math.cos(coneAngle * Math.PI / 180); | |
return Math.PI * radius * (radius + slantHeight) * 1.1; // Add 10% for engineering elements | |
} | |
default: | |
return 4 * Math.PI * Math.pow(radius, 2); // Default to sphere | |
} | |
} | |
/** | |
* Helper functions for the optimization process | |
*/ | |
function calculateAvailableVolume(radius: number, shape: Shape): number { | |
// Calculate volume based on shape type | |
switch (shape.type) { | |
case 'sphere': | |
return (4 / 3) * Math.PI * Math.pow(radius, 3); | |
case 'cylinder': { | |
const height = shape.dimensions.height || (2 * radius); | |
return Math.PI * Math.pow(radius, 2) * height; | |
} | |
case 'cube': { | |
const side = shape.dimensions.side || (2 * radius); | |
return Math.pow(side, 3); | |
} | |
case 'hemisphere': | |
return (2 / 3) * Math.PI * Math.pow(radius, 3); | |
case 'conical': { | |
const height = shape.dimensions.height || (2 * radius); | |
return (1 / 3) * Math.PI * Math.pow(radius, 2) * height; | |
} | |
case 'shaped_charge': { | |
// Account for liner and other components in shaped charge | |
const scRadius = radius; | |
const scHeight = shape.dimensions.height || (2 * radius); | |
let volume = 0.8 * Math.PI * Math.pow(scRadius, 2) * scHeight; // 80% of conical volume | |
// Subtract liner volume if present | |
if (shape.linerProperties) { | |
const linerThickness = shape.linerProperties.thickness / 10; // mm to cm | |
volume -= Math.PI * linerThickness * Math.pow(scRadius, 2) * 0.5; // Approximate liner volume | |
} | |
return volume; | |
} | |
default: | |
return (4 / 3) * Math.PI * Math.pow(radius, 3); // Default to sphere | |
} | |
} | |
/** | |
* Estimate required critical diameter based on scenario parameters | |
*/ | |
function estimateRequiredCriticalDiameter(scenario: any): number { | |
// Base critical diameter on target effects | |
let criticalDiameter = 5; // Default 5mm | |
// Adjust based on target requirements if they exist | |
if (scenario.targets) { | |
for (const target of scenario.targets) { | |
// Higher pressure requirements need more stable detonation | |
if (target.criticalDamageThreshold > 200) { // kPa | |
criticalDiameter = Math.max(criticalDiameter, 10 + 0.05 * target.criticalDamageThreshold); | |
} | |
// Distance affects requirements | |
if (target.distance > 50) { // m | |
criticalDiameter = Math.max(criticalDiameter, 15); | |
} | |
} | |
} | |
// Shape factors - shaped charges need more stable detonation | |
if (scenario.shape.type === 'shaped_charge') { | |
criticalDiameter = Math.max(criticalDiameter, 15); | |
} | |
// Environmental factors | |
if (scenario.environment.temperature < 260) { // Cold conditions | |
criticalDiameter *= 1.3; // Cold reduces sensitivity, needs larger diameter | |
} | |
// Cavity effects for hollow charges | |
if (scenario.shape.dimensions.wallThickness) { | |
criticalDiameter *= 1.5; // Hollow charges need larger diameters | |
} | |
return criticalDiameter; | |
} | |
/** | |
* Estimate required detonation velocity based on scenario | |
*/ | |
function estimateRequiredDetonationVelocity(scenario: any): number { | |
let baseVelocity = 5000; // Base velocity in m/s | |
// Adjust for target requirements | |
if (scenario.targets) { | |
for (const target of scenario.targets) { | |
// Penetration of hard targets requires high detonation velocity | |
if (target.material === 'concrete' || target.material === 'steel' || target.material === 'armor') { | |
baseVelocity = Math.max(baseVelocity, 7000); | |
// Thickness affects required velocity | |
if (target.thickness > 5) { // cm | |
baseVelocity += target.thickness * 100; // Add 100 m/s per cm | |
} | |
} | |
// Pressure requirements | |
if (target.criticalDamageThreshold > 500) { // kPa | |
baseVelocity = Math.max(baseVelocity, 6000 + target.criticalDamageThreshold / 100); | |
} | |
} | |
} | |
// Shape effects | |
if (scenario.shape.type === 'shaped_charge') { | |
baseVelocity = Math.max(baseVelocity, 7500); | |
// Liner material affects required velocity | |
if (scenario.shape.linerProperties) { | |
if (scenario.shape.linerProperties.material === 'copper') { | |
baseVelocity = Math.max(baseVelocity, 8000); | |
} else if (scenario.shape.linerProperties.material === 'tantalum') { | |
baseVelocity = Math.max(baseVelocity, 8500); | |
} | |
} | |
} | |
// Casing effects - heavy casings need higher velocity | |
if (scenario.casing.density > 7.8) { // steel or heavier | |
baseVelocity += 500 * (scenario.casing.density / 7.8 - 1); | |
} | |
// Environmental factors | |
if (scenario.environment.waterDepth !== undefined && scenario.environment.waterDepth > 0) { | |
// Underwater explosions benefit from high detonation velocity | |
baseVelocity += 1000; | |
} | |
return baseVelocity; | |
} | |
/** | |
* Analyze optimal casing properties based on the scenario and available explosives | |
*/ | |
function analyzeOptimalCasingProperties(scenario: any, explosives: Explosive[]): { | |
nonOptimal: boolean; | |
issues: Array<{ property: string; severity: number; currentValue: number; recommendedValue: number }>; | |
} { | |
const issues: Array<{ property: string; severity: number; currentValue: number; recommendedValue: number }> = []; | |
// Function to check if a property is suboptimal | |
const checkProperty = (property: string, currentValue: number, optimalValue: number, tolerance: number) => { | |
const relativeDifference = Math.abs(currentValue - optimalValue) / optimalValue; | |
if (relativeDifference > tolerance) { | |
issues.push({ | |
property, | |
severity: relativeDifference, | |
currentValue, | |
recommendedValue: optimalValue | |
}); | |
} | |
}; | |
// Get average properties of available explosives | |
const avgDetonationVelocity = explosives.reduce((sum, exp) => sum + exp.detonationVelocity, 0) / explosives.length; | |
const avgEnergyDensity = explosives.reduce((sum, exp) => sum + exp.energyDensity, 0) / explosives.length; | |
const avgDensity = explosives.reduce((sum, exp) => sum + exp.density, 0) / explosives.length; | |
// Calculate optimal casing thickness based on Gurney equations and explosive properties | |
const optimalThickness = calculateOptimalCasingThickness( | |
avgDetonationVelocity, | |
avgEnergyDensity, | |
scenario.radius, | |
scenario.casing.density | |
); | |
checkProperty('thickness', scenario.casing.thickness, optimalThickness, 0.25); | |
// Check density based on target effect requirements | |
let optimalDensity = scenario.casing.density; | |
if (scenario.targets) { | |
// For penetration targets, higher density is better | |
const penetrationTargets = scenario.targets.filter(t => | |
t.material !== 'human' && t.material !== 'wood' && t.thickness > 0 | |
); | |
if (penetrationTargets.length > 0) { | |
optimalDensity = Math.max(7.8, scenario.casing.density); // At least steel density | |
} | |
// For blast targets only, lighter casing may be more efficient | |
const blastOnlyTargets = scenario.targets.filter(t => | |
(t.material === 'human' || t.material === 'structure') && | |
!penetrationTargets.length | |
); | |
if (blastOnlyTargets.length > 0 && penetrationTargets.length === 0) { | |
optimalDensity = Math.min(7.8, scenario.casing.density); // Lighter than steel may be better | |
} | |
} | |
checkProperty('density', scenario.casing.density, optimalDensity, 0.2); | |
// Check optimal yield strength based on detonation pressure | |
const avgChapmanJouguetPressure = explosives.reduce((sum, exp) => sum + exp.chapmanJouguetPressure, 0) / explosives.length; | |
const optimalYieldStrength = avgChapmanJouguetPressure * 100; // Convert GPa to MPa with safety factor | |
checkProperty('yieldStrength', scenario.casing.yieldStrength, optimalYieldStrength, 0.3); | |
// Optimal fragmentation pattern based on targets | |
if (scenario.targets) { | |
let optimalPattern: 'uniform' | 'controlled' | 'natural' = 'natural'; | |
// For precision targets, controlled fragmentation is better | |
const precisionTargets = scenario.targets.filter(t => | |
t.crossSection < 1 || (t.material !== 'human' && t.shielding && t.shielding > 0.5) | |
); | |
if (precisionTargets.length > 0) { | |
optimalPattern = 'controlled'; | |
} | |
// For area targets, uniform fragmentation is better | |
const areaTargets = scenario.targets.filter(t => | |
t.crossSection > 10 || t.material === 'human' | |
); | |
if (areaTargets.length > 0 && precisionTargets.length === 0) { | |
optimalPattern = 'uniform'; | |
} | |
if (scenario.casing.fragmentationPattern !== optimalPattern) { | |
issues.push({ | |
property: 'fragmentationPattern', | |
severity: 1.0, // Fixed severity for categorical property | |
currentValue: scenario.casing.fragmentationPattern as unknown as number, | |
recommendedValue: optimalPattern as unknown as number | |
}); | |
} | |
} | |
return { | |
nonOptimal: issues.length > 0, | |
issues | |
}; | |
} | |
/** | |
* Calculate optimal casing thickness based on Gurney equations | |
*/ | |
function calculateOptimalCasingThickness( | |
detonationVelocity: number, | |
energyDensity: number, | |
radius: number, | |
casingDensity: number | |
): number { | |
// Estimate Gurney constant from detonation velocity and energy density | |
// Gurney constant ≈ 0.3 * detonation velocity | |
const gurneyConstant = 0.3 * detonationVelocity; | |
// For maximum fragment velocity with cylindrical charge: | |
// M/C = (V/√2E)² / (1 - (V/√2E)²) | |
// Where: | |
// M = mass of casing | |
// C = mass of explosive | |
// V = desired fragment velocity (about 0.6 * Gurney for optimal energy transfer) | |
// E = Gurney energy constant | |
const desiredVelocityRatio = 0.6; | |
const velocityGurneyRatio = desiredVelocityRatio / Math.sqrt(2); | |
const massRatio = Math.pow(velocityGurneyRatio, 2) / (1 - Math.pow(velocityGurneyRatio, 2)); | |
// M/C = (ρₘ*t*S) / (ρₑ*V) | |
// Where: | |
// ρₘ = density of metal casing | |
// t = thickness of casing | |
// S = surface area | |
// ρₑ = density of explosive | |
// V = volume of explosive | |
// For cylindrical geometries, simplifies approximately to: | |
// t ≈ massRatio * (ρₑ/ρₘ) * radius | |
// Convert energy density (MJ/kg) to rough estimate of explosive density (g/cm³) | |
const estimatedExplosiveDensity = 1.3 + 0.3 * energyDensity; // Empirical relationship | |
// Calculate optimal thickness in cm | |
return massRatio * (estimatedExplosiveDensity / casingDensity) * radius; | |
} | |
/** | |
* Generate parameter adjustment options for a given constraint violation | |
*/ | |
function generateParameterOptions( | |
constraint: { type: string; severity: number; parameter: string; parameterName?: string }, | |
scenario: any, | |
explosives: Explosive[] | |
): Array<{ | |
parameter: string; | |
parameterName: string; | |
suggestedValue: number | string; | |
unit: string; | |
impact: number; | |
}> { | |
const options: Array<{ | |
parameter: string; | |
parameterName: string; | |
suggestedValue: any; | |
unit: string; | |
impact: number; | |
}> = []; | |
// Get current parameter value | |
const currentValue = originalParameterValue(scenario, constraint.parameter); | |
// Handle based on constraint type | |
switch (constraint.type) { | |
case 'energy_yield': { | |
// Increase weight allowance | |
const requiredYield = estimateRequiredYield(scenario); | |
const bestEnergyDensity = Math.max(...explosives.map(e => e.energyDensity)); | |
const recommendedWeight = (requiredYield.totalRequiredEnergy / bestEnergyDensity) * 1.1; // 10% safety margin | |
options.push({ | |
parameter: 'maxWeight', | |
parameterName: 'Maximum Weight', | |
suggestedValue: recommendedWeight, | |
unit: 'kg', | |
impact: 0.8 | |
}); | |
// Alternative: reduce target requirements | |
if (scenario.targets) { | |
// Find targets with highest pressure demands | |
const highPressureTargets = scenario.targets | |
.filter(t => t.criticalDamageThreshold > 100) | |
.sort((a, b) => b.criticalDamageThreshold - a.criticalDamageThreshold); | |
if (highPressureTargets.length > 0) { | |
const targetIndex = scenario.targets.indexOf(highPressureTargets[0]); | |
const reducedThreshold = highPressureTargets[0].criticalDamageThreshold * 0.8; | |
options.push({ | |
parameter: `targets[${targetIndex}].criticalDamageThreshold`, | |
parameterName: `Target ${targetIndex + 1} Damage Threshold`, | |
suggestedValue: reducedThreshold, | |
unit: 'kPa', | |
impact: 0.6 | |
}); | |
} | |
} | |
break; | |
} | |
case 'density': { | |
// Increase radius | |
const volumeNeeded = scenario.maxWeight / Math.max(...explosives.map(e => e.density)); | |
const recommendedRadius = Math.pow(volumeNeeded / ((4 / 3) * Math.PI), 1 / 3) * 1.05; // Sphere approximation + safety | |
options.push({ | |
parameter: 'radius', | |
parameterName: 'Radius', | |
suggestedValue: recommendedRadius, | |
unit: 'cm', | |
impact: 0.9 | |
}); | |
// Alternative: change shape to more volume-efficient type | |
if (scenario.shape.type !== 'cylinder') { | |
options.push({ | |
parameter: 'shape.type', | |
parameterName: 'Shape Type', | |
suggestedValue: 'cylinder', | |
unit: '', | |
impact: 0.7 | |
}); | |
// Add height parameter for cylinder | |
options.push({ | |
parameter: 'shape.dimensions.height', | |
parameterName: 'Cylinder Height', | |
suggestedValue: 2 * recommendedRadius, | |
unit: 'cm', | |
impact: 0.6 | |
}); | |
} | |
break; | |
} | |
case 'detonation_velocity': { | |
// For shaped charges, modify cone angle | |
if (scenario.shape.type === 'shaped_charge') { | |
const currentAngle = scenario.shape.dimensions.coneAngle || 60; | |
let recommendedAngle: number; | |
if (currentAngle < 45) { | |
recommendedAngle = 60; // Wider angle, less jet velocity but better overall detonation | |
} else if (currentAngle > 70) { | |
recommendedAngle = 60; // Standard optimal angle | |
} else { | |
recommendedAngle = 45; // Compromise for different applications | |
} | |
options.push({ | |
parameter: 'shape.dimensions.coneAngle', | |
parameterName: 'Cone Angle', | |
suggestedValue: recommendedAngle, | |
unit: 'degrees', | |
impact: 0.75 | |
}); | |
} else { | |
// Change to more efficient shape type | |
options.push({ | |
parameter: 'shape.type', | |
parameterName: 'Shape Type', | |
suggestedValue: 'shaped_charge', | |
unit: '', | |
impact: 0.9 | |
}); | |
// Add needed properties for shaped charge | |
options.push({ | |
parameter: 'shape.dimensions.coneAngle', | |
parameterName: 'Cone Angle', | |
suggestedValue: 60, | |
unit: 'degrees', | |
impact: 0.8 | |
}); | |
// Add liner properties if missing | |
if (!scenario.shape.linerProperties) { | |
options.push({ | |
parameter: 'shape.linerProperties', | |
parameterName: 'Liner Properties', | |
suggestedValue: { | |
material: 'copper', | |
density: 8.96, | |
thickness: 2.0, | |
angle: 42, | |
standoff: 2.0 | |
}, | |
unit: '', | |
impact: 0.85 | |
}); | |
} | |
} | |
break; | |
} | |
case 'standoff_distance': { | |
const optimalStandoff = ExplosiveModel.calculateOptimalStandoff(scenario.shape.linerProperties); | |
options.push({ | |
parameter: 'shape.dimensions.standoff', | |
parameterName: 'Standoff Distance', | |
suggestedValue: optimalStandoff, | |
unit: 'cm', | |
impact: 0.95 | |
}); | |
break; | |
} | |
case 'critical_diameter': { | |
// Increase radius to accommodate critical diameter | |
const requiredDiameter = estimateRequiredCriticalDiameter(scenario); | |
const recommendedRadius = (requiredDiameter / 20) + 0.5; // Convert mm to cm and add margin | |
options.push({ | |
parameter: 'radius', | |
parameterName: 'Radius', | |
suggestedValue: Math.max(recommendedRadius, scenario.radius), | |
unit: 'cm', | |
impact: 0.85 | |
}); | |
break; | |
} | |
case 'stability': { | |
// Make non-portable or increase safety factors | |
options.push({ | |
parameter: 'isPortable', | |
parameterName: 'Portability Requirement', | |
suggestedValue: false, | |
unit: '', | |
impact: 0.9 | |
}); | |
break; | |
} | |
case 'water_pressure': { | |
// Reduce water depth | |
const recommendedDepth = Math.max(1, scenario.environment.waterDepth * 0.7); | |
options.push({ | |
parameter: 'environment.waterDepth', | |
parameterName: 'Water Depth', | |
suggestedValue: recommendedDepth, | |
unit: 'm', | |
impact: 0.75 | |
}); | |
break; | |
} | |
case 'altitude_effect': { | |
// Reduce altitude or increase explosive mass | |
const recommendedAltitude = Math.max(0, scenario.environment.altitude * 0.8); | |
options.push({ | |
parameter: 'environment.altitude', | |
parameterName: 'Altitude', | |
suggestedValue: recommendedAltitude, | |
unit: 'm', | |
impact: 0.7 | |
}); | |
// Alternative: increase weight to compensate for altitude | |
const pressureCorrection = Math.exp(-scenario.environment.altitude / 8000); | |
const recommendedWeight = scenario.maxWeight / pressureCorrection; | |
options.push({ | |
parameter: 'maxWeight', | |
parameterName: 'Maximum Weight', | |
suggestedValue: recommendedWeight, | |
unit: 'kg', | |
impact: 0.8 | |
}); | |
break; | |
} | |
case 'temperature_extreme': { | |
// Adjust temperature closer to standard conditions | |
const standardTemp = 293; // K (20°C) | |
const currentTemp = scenario.environment.temperature; | |
const recommendedTemp = currentTemp > standardTemp | |
? Math.max(standardTemp, currentTemp - 30) | |
: Math.min(standardTemp, currentTemp + 30); | |
options.push({ | |
parameter: 'environment.temperature', | |
parameterName: 'Environmental Temperature', | |
suggestedValue: recommendedTemp, | |
unit: 'K', | |
impact: 0.6 | |
}); | |
break; | |
} | |
} | |
// Handle casing property constraints | |
if (constraint.type.startsWith('casing_')) { | |
const property = constraint.parameter.split('.')[1]; | |
// Get recommended value from casing analysis | |
const casingAnalysis = analyzeOptimalCasingProperties(scenario, explosives); | |
const propertyIssue = casingAnalysis.issues.find(issue => issue.property === property); | |
if (propertyIssue) { | |
options.push({ | |
parameter: constraint.parameter, | |
parameterName: constraint.parameterName || property, | |
suggestedValue: propertyIssue.recommendedValue, | |
unit: getUnitForProperty(property), | |
impact: 0.8 | |
}); | |
} | |
} | |
return options; | |
} | |
/** | |
* Get unit for a specified property | |
*/ | |
function getUnitForProperty(property: string): string { | |
const unitMap: Record<string, string> = { | |
'thickness': 'cm', | |
'density': 'g/cm³', | |
'yieldStrength': 'MPa', | |
'ultimateStrength': 'MPa', | |
'elasticModulus': 'GPa', | |
'hardness': 'Brinell', | |
'elongation': '%', | |
'thermalConductivity': 'W/(m·K)', | |
'meltingPoint': 'K', | |
'fractureToughness': 'MPa·m^(1/2)', | |
'poissonRatio': '', | |
'heatCapacity': 'J/(kg·K)', | |
'corrosionResistance': '1-10', | |
'fragmentationPattern': '', | |
'radius': 'cm', | |
'height': 'cm', | |
'standoff': 'cm', | |
'coneAngle': 'degrees', | |
'temperature': 'K', | |
'pressure': 'kPa', | |
'humidity': '%', | |
'altitude': 'm', | |
'waterDepth': 'm', | |
'maxWeight': 'kg', | |
'criticalDamageThreshold': 'kPa', | |
'detonationVelocity': 'm/s', | |
'energyDensity': 'MJ/kg', | |
'stability': '1-5', | |
'criticalDiameter': 'mm' | |
}; | |
return unitMap[property] || ''; | |
} | |
/** | |
* Check if a parameter change improves feasibility | |
*/ | |
function improvesFeasibility( | |
originalScenario: any, | |
modifiedScenario: any, | |
modifiedParameter: string, | |
explosives: Explosive[] | |
): boolean { | |
// Clone the scenarios to avoid side effects | |
const originalClone = JSON.parse(JSON.stringify(originalScenario)); | |
const modifiedClone = JSON.parse(JSON.stringify(modifiedScenario)); | |
// Measure feasibility through several metrics | |
const originalMetrics = calculateFeasibilityMetrics(originalClone, explosives); | |
const modifiedMetrics = calculateFeasibilityMetrics(modifiedClone, explosives); | |
// Calculate overall improvement score | |
let improvementScore = 0; | |
// Compare energy requirements vs availability | |
if (modifiedMetrics.energyDeficit < originalMetrics.energyDeficit) { | |
improvementScore += 3 * (originalMetrics.energyDeficit - modifiedMetrics.energyDeficit) / | |
originalMetrics.energyDeficit; | |
} | |
// Compare density requirements | |
if (modifiedMetrics.densityDeficit < originalMetrics.densityDeficit) { | |
improvementScore += 2 * (originalMetrics.densityDeficit - modifiedMetrics.densityDeficit) / | |
Math.max(0.01, originalMetrics.densityDeficit); | |
} | |
// Compare detonation velocity requirements | |
if (modifiedMetrics.velocityDeficit < originalMetrics.velocityDeficit) { | |
improvementScore += 2 * (originalMetrics.velocityDeficit - modifiedMetrics.velocityDeficit) / | |
Math.max(0.01, originalMetrics.velocityDeficit); | |
} | |
// Compare critical diameter requirements | |
if (modifiedMetrics.diameterDeficit < originalMetrics.diameterDeficit) { | |
improvementScore += (originalMetrics.diameterDeficit - modifiedMetrics.diameterDeficit) / | |
Math.max(0.01, originalMetrics.diameterDeficit); | |
} | |
// For shape charge scenarios, consider standoff optimization | |
if (originalScenario.shape.type === 'shaped_charge' && | |
originalScenario.shape.linerProperties && | |
modifiedParameter.includes('standoff')) { | |
const optimalStandoff = ExplosiveModel.calculateOptimalStandoff(originalScenario.shape.linerProperties); | |
const originalDiff = Math.abs(originalScenario.shape.dimensions.standoff - optimalStandoff) / optimalStandoff; | |
const modifiedDiff = Math.abs(modifiedScenario.shape.dimensions.standoff - optimalStandoff) / optimalStandoff; | |
if (modifiedDiff < originalDiff) { | |
improvementScore += 2 * (originalDiff - modifiedDiff); | |
} | |
} | |
// Check if there are now compatible explosives | |
const originalCompatibleExplosives = explosives.filter(e => isExplosiveCompatible(e, originalClone)); | |
const modifiedCompatibleExplosives = explosives.filter(e => isExplosiveCompatible(e, modifiedClone)); | |
if (modifiedCompatibleExplosives.length > originalCompatibleExplosives.length) { | |
improvementScore += 5; | |
} | |
// Threshold for considering an improvement | |
return improvementScore > 0.2; | |
} | |
/** | |
* Calculate metrics to evaluate feasibility | |
*/ | |
function calculateFeasibilityMetrics(scenario: any, explosives: Explosive[]): { | |
energyDeficit: number; | |
densityDeficit: number; | |
velocityDeficit: number; | |
diameterDeficit: number; | |
standoffError: number; | |
compatibleExplosivesCount: number; | |
} { | |
// Required energy | |
const requiredYield = estimateRequiredYield(scenario); | |
const maxAvailableYield = Math.max(...explosives.map(e => e.energyDensity * scenario.maxWeight)); | |
const energyDeficit = Math.max(0, requiredYield.totalRequiredEnergy - maxAvailableYield) / | |
Math.max(1, requiredYield.totalRequiredEnergy); | |
// Density requirements | |
const volumeConstraint = calculateAvailableVolume(scenario.radius, scenario.shape); | |
const minRequiredDensity = scenario.maxWeight / volumeConstraint; | |
const maxAvailableDensity = Math.max(...explosives.map(e => e.density)); | |
const densityDeficit = Math.max(0, minRequiredDensity - maxAvailableDensity) / | |
Math.max(0.1, minRequiredDensity); | |
// Detonation velocity | |
const minRequiredVelocity = estimateRequiredDetonationVelocity(scenario); | |
const maxAvailableVelocity = Math.max(...explosives.map(e => e.detonationVelocity)); | |
const velocityDeficit = Math.max(0, minRequiredVelocity - maxAvailableVelocity) / | |
Math.max(1000, minRequiredVelocity); | |
// Critical diameter | |
const minRequiredDiameter = estimateRequiredCriticalDiameter(scenario); | |
const scenarioDiameter = 2 * scenario.radius * 10; // cm to mm | |
const diameterDeficit = Math.max(0, minRequiredDiameter - scenarioDiameter) / | |
Math.max(1, minRequiredDiameter); | |
// Standoff error for shaped charges | |
let standoffError = 0; | |
if (scenario.shape.type === 'shaped_charge' && scenario.shape.linerProperties) { | |
const optimalStandoff = ExplosiveModel.calculateOptimalStandoff(scenario.shape.linerProperties); | |
const currentStandoff = scenario.shape.dimensions.standoff || 0; | |
standoffError = Math.abs(currentStandoff - optimalStandoff) / Math.max(0.1, optimalStandoff); | |
} | |
// Count compatible explosives | |
const compatibleExplosivesCount = explosives.filter(e => isExplosiveCompatible(e, scenario)).length; | |
return { | |
energyDeficit, | |
densityDeficit, | |
velocityDeficit, | |
diameterDeficit, | |
standoffError, | |
compatibleExplosivesCount | |
}; | |
} | |
/** | |
* Check if an explosive is compatible with the scenario requirements | |
*/ | |
function isExplosiveCompatible(explosive: Explosive, scenario: any): boolean { | |
// Check energy requirements | |
const requiredYield = estimateRequiredYield(scenario); | |
if (explosive.energyDensity * scenario.maxWeight < requiredYield.totalRequiredEnergy) { | |
return false; | |
} | |
// Check density constraints | |
const volumeConstraint = calculateAvailableVolume(scenario.radius, scenario.shape); | |
const minRequiredDensity = scenario.maxWeight / volumeConstraint; | |
if (explosive.density < minRequiredDensity) { | |
return false; | |
} | |
// Check detonation velocity if targets specified | |
if (scenario.targets) { | |
const minRequiredVelocity = estimateRequiredDetonationVelocity(scenario); | |
if (explosive.detonationVelocity < minRequiredVelocity) { | |
return false; | |
} | |
} | |
// Check critical diameter | |
const minRequiredDiameter = estimateRequiredCriticalDiameter(scenario); | |
if (explosive.criticalDiameter > minRequiredDiameter) { | |
return false; | |
} | |
// Check stability for portable applications | |
if (scenario.isPortable && explosive.stability < 3) { | |
return false; | |
} | |
// Check temperature compatibility | |
const standardTemp = 293; // K | |
if (Math.abs(scenario.environment.temperature - standardTemp) > 50) { | |
// Check thermal expansion and stability at extreme temperatures | |
if (explosive.thermalExpansion > 1e-4 || explosive.stability < 4) { | |
return false; | |
} | |
} | |
// Check underwater compatibility | |
if (scenario.environment.waterDepth !== undefined && scenario.environment.waterDepth > 0) { | |
// Water-resistant explosives need oxygen balance near zero | |
if (Math.abs(explosive.oxygenBalance) > 20) { | |
return false; | |
} | |
} | |
return true; | |
} | |
/** | |
* Apply a parameter change to the scenario | |
*/ | |
function applyParameterChange(scenario: any, parameterPath: string, newValue: any): void { | |
// Handle array access notation like "targets[0].property" | |
if (parameterPath.includes('[')) { | |
const matches = parameterPath.match(/(\w+)\[(\d+)\]\.(.+)/); | |
if (matches) { | |
const [_, arrayName, indexStr, property] = matches; | |
const index = parseInt(indexStr); | |
if (scenario[arrayName] && Array.isArray(scenario[arrayName]) && scenario[arrayName][index]) { | |
const propertyPath = property.split('.'); | |
let current = scenario[arrayName][index]; | |
// Navigate to the nested property | |
for (let i = 0; i < propertyPath.length - 1; i++) { | |
if (!current[propertyPath[i]]) { | |
current[propertyPath[i]] = {}; | |
} | |
current = current[propertyPath[i]]; | |
} | |
// Set the value | |
current[propertyPath[propertyPath.length - 1]] = newValue; | |
} | |
return; | |
} | |
} | |
// Regular property paths like "shape.dimensions.radius" | |
const parts = parameterPath.split('.'); | |
let current = scenario; | |
// Navigate to the nested property | |
for (let i = 0; i < parts.length - 1; i++) { | |
if (!current[parts[i]]) { | |
current[parts[i]] = {}; | |
} | |
current = current[parts[i]]; | |
} | |
// Set the value | |
current[parts[parts.length - 1]] = newValue; | |
} | |
/** | |
* Get the original parameter value from a scenario | |
*/ | |
function originalParameterValue(scenario: any, parameterPath: string): any { | |
// Handle array access notation | |
if (parameterPath.includes('[')) { | |
const matches = parameterPath.match(/(\w+)\[(\d+)\]\.(.+)/); | |
if (matches) { | |
const [_, arrayName, indexStr, property] = matches; | |
const index = parseInt(indexStr); | |
if (scenario[arrayName] && Array.isArray(scenario[arrayName]) && scenario[arrayName][index]) { | |
const propertyPath = property.split('.'); | |
let current = scenario[arrayName][index]; | |
// Navigate to the nested property | |
for (const part of propertyPath) { | |
if (current === undefined || current === null) return undefined; | |
current = current[part]; | |
} | |
return current; | |
} | |
return undefined; | |
} | |
} | |
// Regular property paths | |
const parts = parameterPath.split('.'); | |
let current = scenario; | |
// Navigate to the nested property | |
for (const part of parts) { | |
if (current === undefined || current === null) return undefined; | |
current = current[part]; | |
} | |
return current; | |
} | |
/** | |
* Check if a scenario is physically consistent | |
*/ | |
function isPhysicallyConsistent(scenario: any): boolean { | |
// Validate basic physical constraints | |
// 1. Volume must be positive and realistic | |
if (scenario.radius <= 0) return false; | |
// 2. Density and weight constraints | |
const volume = calculateAvailableVolume(scenario.radius, scenario.shape); | |
const effectiveDensity = scenario.maxWeight / volume; | |
// Too high density is physically impossible | |
if (effectiveDensity > 20) return false; // No known explosive exceeds 20 g/cm³ | |
// 3. Ensure shape dimensions are consistent | |
if (scenario.shape.type === 'cylinder') { | |
const height = scenario.shape.dimensions.height || (2 * scenario.radius); | |
if (height <= 0) return false; | |
} else if (scenario.shape.type === 'cube') { | |
const side = scenario.shape.dimensions.side || (2 * scenario.radius); | |
if (side <= 0) return false; | |
} else if (scenario.shape.type === 'conical') { | |
const height = scenario.shape.dimensions.height || (2 * scenario.radius); | |
if (height <= 0) return false; | |
} else if (scenario.shape.type === 'shaped_charge') { | |
// Shaped charge validation | |
if (scenario.shape.dimensions.coneAngle !== undefined) { | |
const angle = scenario.shape.dimensions.coneAngle; | |
// Practical limits for cone angles | |
if (angle < 10 || angle > 120) return false; | |
} | |
if (scenario.shape.linerProperties) { | |
// Liner thickness must be reasonable | |
if (scenario.shape.linerProperties.thickness <= 0 || | |
scenario.shape.linerProperties.thickness > 10) return false; | |
// Liner angle should match cone angle approximately | |
if (scenario.shape.dimensions.coneAngle !== undefined && | |
scenario.shape.linerProperties.angle !== undefined) { | |
const coneDiff = Math.abs(scenario.shape.dimensions.coneAngle - scenario.shape.linerProperties.angle); | |
if (coneDiff > 30) return false; // Liner should roughly match cone | |
} | |
} | |
} | |
// 4. Environment checks | |
if (scenario.environment.temperature <= 0) return false; // Temperature must be positive (Kelvin) | |
if (scenario.environment.pressure <= 0) return false; // Pressure must be positive | |
// 5. Casing physical consistency | |
if (scenario.casing.thickness <= 0) return false; | |
// 6. Target consistency | |
if (scenario.targets) { | |
for (const target of scenario.targets) { | |
if (target.distance <= 0) return false; // Target must be at positive distance | |
if (target.thickness < 0) return false; // Thickness cannot be negative | |
if (target.crossSection <= 0) return false; // Cross-section must be positive | |
} | |
} | |
return true; | |
} | |
/** | |
* Evaluate parameter sensitivity for optimization | |
*/ | |
function evaluateParameterSensitivity( | |
originalScenario: any, | |
modifiedScenario: any, | |
parameter: string, | |
explosives: Explosive[] | |
): number { | |
// Clone scenarios for evaluation | |
const baseScenario = JSON.parse(JSON.stringify(originalScenario)); | |
const newScenario = JSON.parse(JSON.stringify(modifiedScenario)); | |
// Calculate base metrics | |
const baseMetrics = calculateFeasibilityMetrics(baseScenario, explosives); | |
const newMetrics = calculateFeasibilityMetrics(newScenario, explosives); | |
// Calculate normalized change in parameters | |
const originalValue = originalParameterValue(baseScenario, parameter); | |
const modifiedValue = originalParameterValue(newScenario, parameter); | |
// Handle categorical variables differently | |
if (typeof originalValue === 'string' || typeof modifiedValue === 'string') { | |
return 1.0; // Fixed sensitivity for categorical changes | |
} | |
if (typeof originalValue !== 'number' || typeof modifiedValue !== 'number') { | |
return 0.5; // Default for complex objects | |
} | |
// Calculate fractional parameter change | |
const parameterChange = Math.abs(modifiedValue - originalValue) / | |
Math.max(0.001, Math.abs(originalValue)); | |
// Calculate overall metrics change | |
const energyChange = Math.abs(newMetrics.energyDeficit - baseMetrics.energyDeficit); | |
const densityChange = Math.abs(newMetrics.densityDeficit - baseMetrics.densityDeficit); | |
const velocityChange = Math.abs(newMetrics.velocityDeficit - baseMetrics.velocityDeficit); | |
const diameterChange = Math.abs(newMetrics.diameterDeficit - baseMetrics.diameterDeficit); | |
// Overall metrics delta (weighted sum) | |
const metricsDelta = 3 * energyChange + 2 * densityChange + 2 * velocityChange + diameterChange; | |
// Sensitivity is ratio of metrics change to parameter change | |
return metricsDelta / Math.max(0.001, parameterChange); | |
} | |
/** | |
* Describe sensitivity impact qualitatively | |
*/ | |
function describeSensitivityImpact(sensitivity: number): string { | |
if (sensitivity > 5) return "Very High"; | |
if (sensitivity > 2) return "High"; | |
if (sensitivity > 1) return "Moderate"; | |
if (sensitivity > 0.5) return "Low"; | |
return "Very Low"; | |
} | |
/** | |
* Apply advanced multi-parameter optimization techniques when simple parameter changes fail | |
*/ | |
function applyAdvancedOptimization( | |
originalScenario: any, | |
existingChanges: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }>, | |
explosives: Explosive[], | |
sensitivityTracking: Record<string, { sensitivity: number, impact: string }> | |
): { | |
improved: boolean; | |
newScenario: any; | |
changes: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }>; | |
} { | |
// Clone the original scenario | |
const newScenario = JSON.parse(JSON.stringify(originalScenario)); | |
const additionalChanges: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }> = []; | |
// Track parameters already changed | |
const changedParameters = new Set(existingChanges.map(c => c.parameter)); | |
// ==== Strategy 1: Simultaneous multi-parameter adjustment ==== | |
// Find high-sensitivity parameters not yet changed | |
const highSensitivityParams = Object.entries(sensitivityTracking) | |
.filter(([param, data]) => data.sensitivity > 1.0 && !changedParameters.has(param)) | |
.sort((a, b) => b[1].sensitivity - a[1].sensitivity) | |
.slice(0, 3); // Take top 3 most sensitive parameters | |
if (highSensitivityParams.length > 0) { | |
// For each parameter, calculate optimal adjustment | |
for (const [param, data] of highSensitivityParams) { | |
const originalValue = originalParameterValue(originalScenario, param); | |
// Skip if value is not numerical | |
if (typeof originalValue !== 'number') continue; | |
// Generate adjustment options | |
const paramOptions = getParameterAdjustmentOptions(param, originalValue, newScenario); | |
// Try each option | |
for (const option of paramOptions) { | |
const testScenario = JSON.parse(JSON.stringify(newScenario)); | |
applyParameterChange(testScenario, param, option.value); | |
if (isPhysicallyConsistent(testScenario) && | |
improvesFeasibility(newScenario, testScenario, param, explosives)) { | |
// Apply successful change | |
applyParameterChange(newScenario, param, option.value); | |
additionalChanges.push({ | |
parameter: getParameterName(param), | |
originalValue, | |
newValue: option.value, | |
unit: getUnitForProperty(param.split('.').pop() || '') | |
}); | |
// Check if this made the scenario feasible | |
const result = optimizeExplosive(explosives, newScenario); | |
if (result) { | |
return { | |
improved: true, | |
newScenario, | |
changes: additionalChanges | |
}; | |
} | |
break; // Move to next parameter | |
} | |
} | |
} | |
} | |
// ==== Strategy 2: Shape adaptation based on target requirements ==== | |
if (newScenario.targets && newScenario.targets.length > 0 && !changedParameters.has('shape.type')) { | |
const targetRequirements = analyzeTargetRequirements(newScenario.targets); | |
// If penetration is primary need, consider shaped charge | |
if (targetRequirements.penetrationPriority > 0.7 && newScenario.shape.type !== 'shaped_charge') { | |
const originalShapeType = newScenario.shape.type; | |
// Apply shaped charge conversion | |
applyParameterChange(newScenario, 'shape.type', 'shaped_charge'); | |
applyParameterChange(newScenario, 'shape.dimensions.coneAngle', 60); | |
// Add liner if missing | |
if (!newScenario.shape.linerProperties) { | |
applyParameterChange(newScenario, 'shape.linerProperties', { | |
material: 'copper', | |
density: 8.96, | |
thickness: 2.0, | |
angle: 42, | |
standoff: 2.0 | |
}); | |
} | |
additionalChanges.push({ | |
parameter: 'Shape Type', | |
originalValue: originalShapeType, | |
newValue: 'shaped_charge', | |
unit: '' | |
}); | |
// Check if successful | |
if (improvesFeasibility(originalScenario, newScenario, 'shape.type', explosives)) { | |
const result = optimizeExplosive(explosives, newScenario); | |
if (result) { | |
return { | |
improved: true, | |
newScenario, | |
changes: additionalChanges | |
}; | |
} | |
} | |
} | |
// If blast is primary need, consider sphere or cylinder | |
if (targetRequirements.blastPriority > 0.7 && | |
newScenario.shape.type !== 'sphere' && | |
newScenario.shape.type !== 'cylinder') { | |
const originalShapeType = newScenario.shape.type; | |
// Try sphere for omnidirectional blast | |
applyParameterChange(newScenario, 'shape.type', 'sphere'); | |
additionalChanges.push({ | |
parameter: 'Shape Type', | |
originalValue: originalShapeType, | |
newValue: 'sphere', | |
unit: '' | |
}); | |
// Check if successful | |
if (improvesFeasibility(originalScenario, newScenario, 'shape.type', explosives)) { | |
const result = optimizeExplosive(explosives, newScenario); | |
if (result) { | |
return { | |
improved: true, | |
newScenario, | |
changes: additionalChanges | |
}; | |
} | |
} | |
} | |
} | |
// ==== Strategy 3: Environmental parameter optimization ==== | |
if (newScenario.environment) { | |
// Check if environment is extreme | |
const standardTemp = 293; // K | |
const standardPressure = 101.325; // kPa | |
if (Math.abs(newScenario.environment.temperature - standardTemp) > 30 && | |
!changedParameters.has('environment.temperature')) { | |
const originalTemp = newScenario.environment.temperature; | |
const adjustedTemp = (originalTemp > standardTemp) | |
? Math.max(standardTemp, originalTemp - 20) | |
: Math.min(standardTemp, originalTemp + 20); | |
applyParameterChange(newScenario, 'environment.temperature', adjustedTemp); | |
additionalChanges.push({ | |
parameter: 'Environmental Temperature', | |
originalValue: originalTemp, | |
newValue: adjustedTemp, | |
unit: 'K' | |
}); | |
} | |
// Pressure optimization | |
if (Math.abs(newScenario.environment.pressure - standardPressure) > 20 && | |
!changedParameters.has('environment.pressure')) { | |
const originalPressure = newScenario.environment.pressure; | |
const adjustedPressure = (originalPressure > standardPressure) | |
? Math.max(standardPressure, originalPressure - 10) | |
: Math.min(standardPressure, originalPressure + 10); | |
applyParameterChange(newScenario, 'environment.pressure', adjustedPressure); | |
additionalChanges.push({ | |
parameter: 'Ambient Pressure', | |
originalValue: originalPressure, | |
newValue: adjustedPressure, | |
unit: 'kPa' | |
}); | |
} | |
// Check if these changes improved feasibility | |
if (additionalChanges.length > 0 && improvesFeasibility(originalScenario, newScenario, 'environment', explosives)) { | |
const result = optimizeExplosive(explosives, newScenario); | |
if (result) { | |
return { | |
improved: true, | |
newScenario, | |
changes: additionalChanges | |
}; | |
} | |
} | |
} | |
// ==== Strategy 4: Material-driven optimization - adapt scenario to available materials ==== | |
const mostSuitableExplosive = findMostSuitableExplosive(explosives, originalScenario); | |
if (mostSuitableExplosive) { | |
// Adapt requirements to match this explosive's capabilities | |
const adaptedChanges = adaptScenarioToExplosive(newScenario, mostSuitableExplosive, changedParameters); | |
if (adaptedChanges.length > 0) { | |
// Apply all changes | |
for (const change of adaptedChanges) { | |
applyParameterChange(newScenario, change.paramPath, change.newValue); | |
additionalChanges.push({ | |
parameter: change.paramName, | |
originalValue: change.oldValue, | |
newValue: change.newValue, | |
unit: change.unit | |
}); | |
} | |
// Check if successful | |
const result = optimizeExplosive(explosives, newScenario); | |
if (result) { | |
return { | |
improved: true, | |
newScenario, | |
changes: additionalChanges | |
}; | |
} | |
} | |
} | |
// If nothing worked | |
return { | |
improved: additionalChanges.length > 0, | |
newScenario, | |
changes: additionalChanges | |
}; | |
} | |
/** | |
* Get parameter name from parameter path | |
*/ | |
function getParameterName(paramPath: string): string { | |
// Handle array notation | |
if (paramPath.includes('[')) { | |
const matches = paramPath.match(/(\w+)\[(\d+)\]\.(.+)/); | |
if (matches) { | |
const [_, arrayName, indexStr, property] = matches; | |
return `${arrayName.charAt(0).toUpperCase() + arrayName.slice(1)} ${parseInt(indexStr) + 1} ${property.split('.').map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(' ')}`; | |
} | |
} | |
// Handle regular path | |
return paramPath.split('.').map(p => p.charAt(0).toUpperCase() + p.slice(1)).join(' '); | |
} | |
/** | |
* Generate parameter adjustment options for optimization | |
*/ | |
function getParameterAdjustmentOptions( | |
paramPath: string, | |
currentValue: number, | |
scenario: any | |
): Array<{ value: number; priority: number }> { | |
const options: Array<{ value: number; priority: number }> = []; | |
const paramName = paramPath.split('.').pop() || ''; | |
// Generate options based on parameter type | |
switch (paramName) { | |
case 'radius': | |
// Try increasing radius | |
options.push({ value: currentValue * 1.2, priority: 0.9 }); | |
options.push({ value: currentValue * 1.5, priority: 0.7 }); | |
options.push({ value: currentValue * 0.8, priority: 0.5 }); // Sometimes smaller is better | |
break; | |
case 'maxWeight': | |
// Try increasing weight allowance | |
options.push({ value: currentValue * 1.25, priority: 0.95 }); | |
options.push({ value: currentValue * 1.5, priority: 0.8 }); | |
options.push({ value: currentValue * 2.0, priority: 0.6 }); | |
break; | |
case 'standoff': | |
// Get optimal standoff if available | |
if (scenario.shape.type === 'shaped_charge' && scenario.shape.linerProperties) { | |
const optimalStandoff = ExplosiveModel.calculateOptimalStandoff(scenario.shape.linerProperties); | |
options.push({ value: optimalStandoff, priority: 0.95 }); | |
options.push({ value: optimalStandoff * 0.9, priority: 0.7 }); | |
options.push({ value: optimalStandoff * 1.1, priority: 0.7 }); | |
} else { | |
options.push({ value: currentValue * 1.5, priority: 0.8 }); | |
options.push({ value: currentValue * 0.7, priority: 0.7 }); | |
} | |
break; | |
case 'coneAngle': | |
// Standard angles for shaped charges | |
options.push({ value: 42, priority: 0.9 }); // Optimal for many applications | |
options.push({ value: 60, priority: 0.8 }); | |
options.push({ value: 30, priority: 0.7 }); | |
break; | |
case 'thickness': // For casing thickness | |
if (paramPath.includes('casing')) { | |
// Calculate optimal casing based on physics | |
const casingDensity = scenario.casing?.density || 7.8; // Default to steel | |
const explosiveDensity = 1.6; // Typical explosive density | |
// Gurney equation optimal M/C ratio for fragment velocity | |
const optimalMassRatio = 0.6; // Mass of casing / Mass of explosive | |
// Rough estimate based on cylinder approximation | |
const chargeRadius = scenario.radius - currentValue; | |
const optimalThickness = (optimalMassRatio * explosiveDensity * chargeRadius) / casingDensity; | |
options.push({ value: optimalThickness, priority: 0.9 }); | |
options.push({ value: currentValue * 0.8, priority: 0.8 }); // Thinner | |
options.push({ value: currentValue * 1.2, priority: 0.7 }); // Thicker | |
} else if (paramPath.includes('linerProperties')) { | |
// For shaped charge liners | |
options.push({ value: 2.0, priority: 0.9 }); // Standard thickness | |
options.push({ value: 1.5, priority: 0.8 }); | |
options.push({ value: 3.0, priority: 0.7 }); | |
} | |
break; | |
case 'temperature': | |
// Move toward standard temperature | |
const standardTemp = 293; // K | |
options.push({ value: standardTemp, priority: 0.9 }); | |
options.push({ | |
value: currentValue + (standardTemp - currentValue) * 0.5, | |
priority: 0.8 | |
}); // Move halfway to standard | |
break; | |
case 'pressure': | |
// Move toward standard pressure | |
const standardPressure = 101.325; // kPa | |
options.push({ value: standardPressure, priority: 0.9 }); | |
options.push({ | |
value: currentValue + (standardPressure - currentValue) * 0.5, | |
priority: 0.8 | |
}); // Move halfway to standard | |
break; | |
case 'altitude': | |
// Reduce altitude | |
options.push({ value: currentValue * 0.7, priority: 0.9 }); | |
options.push({ value: currentValue * 0.5, priority: 0.8 }); | |
options.push({ value: 0, priority: 0.7 }); // Sea level | |
break; | |
case 'waterDepth': | |
// Reduce water depth | |
options.push({ value: currentValue * 0.7, priority: 0.9 }); | |
options.push({ value: currentValue * 0.5, priority: 0.8 }); | |
options.push({ value: 1, priority: 0.7 }); // Shallow water | |
break; | |
case 'criticalDamageThreshold': | |
// Reduce damage threshold requirements | |
options.push({ value: currentValue * 0.8, priority: 0.9 }); | |
options.push({ value: currentValue * 0.6, priority: 0.7 }); | |
options.push({ value: currentValue * 0.5, priority: 0.5 }); | |
break; | |
default: | |
// Generic adjustments for numeric parameters | |
options.push({ value: currentValue * 1.2, priority: 0.8 }); | |
options.push({ value: currentValue * 0.8, priority: 0.7 }); | |
options.push({ value: currentValue * 1.5, priority: 0.6 }); | |
options.push({ value: currentValue * 0.5, priority: 0.5 }); | |
} | |
// Sort by priority | |
return options.sort((a, b) => b.priority - a.priority); | |
} | |
/** | |
* Analyze target requirements to determine optimal shape and configuration | |
*/ | |
function analyzeTargetRequirements(targets: Target[]): { | |
penetrationPriority: number; | |
blastPriority: number; | |
fragmentationPriority: number; | |
precisionPriority: number; | |
} { | |
let penetrationScore = 0; | |
let blastScore = 0; | |
let fragmentationScore = 0; | |
let precisionScore = 0; | |
for (const target of targets) { | |
// Penetration targets are characterized by high density materials and thickness | |
if (target.material === 'steel' || target.material === 'concrete' || target.material === 'armor') { | |
penetrationScore += 1.0; | |
// Thicker materials increase penetration priority | |
if (target.thickness > 5) { // cm | |
penetrationScore += 0.5; | |
} | |
// Small cross-section requires precision | |
if (target.crossSection < 1) { // m² | |
precisionScore += 1.0; | |
} | |
} | |
// Blast targets are typically structures or human targets | |
if (target.material === 'structure' || target.material === 'human') { | |
blastScore += 1.0; | |
// High damage threshold needs strong blast | |
if (target.criticalDamageThreshold > 200) { // kPa | |
blastScore += 0.5; | |
} | |
// Large cross-section indicates area target | |
if (target.crossSection > 10) { // m² | |
blastScore += 0.3; | |
fragmentationScore += 0.5; // Fragmentation effective against area targets | |
} | |
} | |
// Fragmentation priority increases with distance | |
if (target.distance > 20) { // m | |
fragmentationScore += 0.3; | |
} | |
// Target with shielding requires penetration or precision | |
if (target.shielding && target.shielding > 0.3) { | |
penetrationScore += 0.5; | |
precisionScore += 0.3; | |
} | |
} | |
// Normalize scores | |
const totalTargets = targets.length; | |
penetrationScore /= totalTargets; | |
blastScore /= totalTargets; | |
fragmentationScore /= totalTargets; | |
precisionScore /= totalTargets; | |
return { | |
penetrationPriority: Math.min(1.0, penetrationScore), | |
blastPriority: Math.min(1.0, blastScore), | |
fragmentationPriority: Math.min(1.0, fragmentationScore), | |
precisionPriority: Math.min(1.0, precisionScore) | |
}; | |
} | |
/** | |
* Find the most suitable explosive from available options | |
*/ | |
function findMostSuitableExplosive(explosives: Explosive[], scenario: any): Explosive | null { | |
if (explosives.length === 0) return null; | |
// Calculate scenario requirements | |
const requiredYield = estimateRequiredYield(scenario); | |
const requiredDetonationVelocity = estimateRequiredDetonationVelocity(scenario); | |
const requiredCriticalDiameter = estimateRequiredCriticalDiameter(scenario); | |
// Score each explosive | |
const scoredExplosives = explosives.map(explosive => { | |
let score = 0; | |
// Energy yield score (0-40 points) | |
const yieldScore = Math.min(1.0, (explosive.energyDensity * scenario.maxWeight) / requiredYield.totalRequiredEnergy); | |
score += yieldScore * 40; | |
// Detonation velocity score (0-30 points) | |
const velocityScore = Math.min(1.0, explosive.detonationVelocity / requiredDetonationVelocity); | |
score += velocityScore * 30; | |
// Critical diameter score (0-10 points) | |
const diameterRatio = requiredCriticalDiameter / Math.max(1, explosive.criticalDiameter); | |
const diameterScore = Math.min(1.0, diameterRatio); | |
score += diameterScore * 10; | |
// Stability score for portable scenarios (0-10 points) | |
if (scenario.isPortable) { | |
const stabilityScore = Math.min(1.0, explosive.stability / 5); | |
score += stabilityScore * 10; | |
} else { | |
// For non-portable, prioritize power | |
score += 5; // Half the points automatically | |
} | |
// Environmental compatibility (0-10 points) | |
const envScore = calculateEnvironmentalCompatibility(explosive, scenario.environment); | |
score += envScore * 10; | |
return { explosive, score }; | |
}); | |
// Sort by score and return best match | |
scoredExplosives.sort((a, b) => b.score - a.score); | |
return scoredExplosives[0]?.explosive || null; | |
} | |
/** | |
* Calculate environmental compatibility score (0-1) | |
*/ | |
function calculateEnvironmentalCompatibility(explosive: Explosive, environment: Environment): number { | |
let score = 1.0; | |
// Temperature extremes | |
const standardTemp = 293; // K | |
if (Math.abs(environment.temperature - standardTemp) > 50) { | |
// Cold affects initiation | |
if (environment.temperature < standardTemp) { | |
score -= 0.2 * (1 - explosive.stability / 5); | |
} | |
// Heat affects stability | |
else { | |
score -= 0.3 * (1 - explosive.stability / 5); | |
// High thermal expansion compounds the issue | |
if (explosive.thermalExpansion > 1e-4) { | |
score -= 0.1; | |
} | |
} | |
} | |
// Underwater applications | |
if (environment.waterDepth !== undefined && environment.waterDepth > 0) { | |
// Water sensitivity depends on oxygen balance | |
score -= 0.2 * Math.min(1.0, Math.abs(explosive.oxygenBalance) / 50); | |
} | |
// High altitude effects | |
if (environment.altitude > 2000) { | |
// Reduced pressure affects detonation | |
score -= 0.1 * Math.min(1.0, environment.altitude / 8000); | |
} | |
// Humidity effects | |
if (environment.humidity > 80) { | |
// Hygroscopic materials affected by high humidity | |
if (Math.abs(explosive.oxygenBalance) > 25) { | |
score -= 0.15; | |
} | |
} | |
return Math.max(0, score); | |
} | |
/** | |
* Adapt scenario to available explosive properties | |
*/ | |
function adaptScenarioToExplosive( | |
scenario: any, | |
explosive: Explosive, | |
changedParameters: Set<string> | |
): Array<{ | |
paramPath: string; | |
paramName: string; | |
oldValue: any; | |
newValue: any; | |
unit: string; | |
}> { | |
const changes: Array<{ | |
paramPath: string; | |
paramName: string; | |
oldValue: any; | |
newValue: any; | |
unit: string; | |
}> = []; | |
// Required yield for the scenario | |
const requiredYield = estimateRequiredYield(scenario); | |
// Check if weight needs to be increased | |
if (!changedParameters.has('maxWeight') && | |
explosive.energyDensity * scenario.maxWeight < requiredYield.totalRequiredEnergy) { | |
const recommendedWeight = (requiredYield.totalRequiredEnergy / explosive.energyDensity) * 1.1; | |
changes.push({ | |
paramPath: 'maxWeight', | |
paramName: 'Maximum Weight', | |
oldValue: scenario.maxWeight, | |
newValue: recommendedWeight, | |
unit: 'kg' | |
}); | |
} | |
// Check if critical diameter constraint requires radius increase | |
const criticalDiameterMm = explosive.criticalDiameter; | |
const scenarioDiameterMm = 2 * scenario.radius * 10; // cm to mm | |
if (!changedParameters.has('radius') && criticalDiameterMm > scenarioDiameterMm) { | |
const recommendedRadius = (criticalDiameterMm / 20) * 1.1; // mm to cm with 10% margin | |
changes.push({ | |
paramPath: 'radius', | |
paramName: 'Radius', | |
oldValue: scenario.radius, | |
newValue: recommendedRadius, | |
unit: 'cm' | |
}); | |
} | |
// Adapt casing for explosive properties if not already changed | |
if (!changedParameters.has('casing.thickness')) { | |
// Calculate optimal casing thickness for this explosive | |
const optimalThickness = calculateOptimalCasingThickness( | |
explosive.detonationVelocity, | |
explosive.energyDensity, | |
scenario.radius, | |
scenario.casing.density | |
); | |
if (Math.abs(scenario.casing.thickness - optimalThickness) / optimalThickness > 0.3) { | |
changes.push({ | |
paramPath: 'casing.thickness', | |
paramName: 'Casing Thickness', | |
oldValue: scenario.casing.thickness, | |
newValue: optimalThickness, | |
unit: 'cm' | |
}); | |
} | |
} | |
// If shaped charge, adapt standoff distance to explosive properties | |
if (scenario.shape.type === 'shaped_charge' && | |
scenario.shape.linerProperties && | |
!changedParameters.has('shape.dimensions.standoff')) { | |
// Recalculate optimal standoff for this specific explosive | |
const optimalStandoff = calculateOptimalStandoffForExplosive( | |
explosive, | |
scenario.shape.linerProperties | |
); | |
if (Math.abs(scenario.shape.dimensions.standoff - optimalStandoff) / optimalStandoff > 0.2) { | |
changes.push({ | |
paramPath: 'shape.dimensions.standoff', | |
paramName: 'Standoff Distance', | |
oldValue: scenario.shape.dimensions.standoff, | |
newValue: optimalStandoff, | |
unit: 'cm' | |
}); | |
} | |
} | |
// If stability issues and portable, suggest non-portable | |
if (scenario.isPortable && | |
explosive.stability < 3 && | |
!changedParameters.has('isPortable')) { | |
changes.push({ | |
paramPath: 'isPortable', | |
paramName: 'Portability Requirement', | |
oldValue: true, | |
newValue: false, | |
unit: '' | |
}); | |
} | |
return changes; | |
} | |
/** | |
* Calculate optimal standoff for shaped charge with specific explosive | |
*/ | |
function calculateOptimalStandoffForExplosive(explosive: Explosive, liner: Liner): number { | |
// Base calculation from ExplosiveModel | |
let baseStandoff = ExplosiveModel.calculateOptimalStandoff(liner); | |
// Adjust based on explosive properties | |
// Detonation velocity affects jet formation dynamics | |
const velocityFactor = explosive.detonationVelocity / 8000; // Normalized to typical PETN/RDX | |
// Higher detonation pressure creates more coherent jets that work at longer standoffs | |
const pressureFactor = explosive.chapmanJouguetPressure / 25; // Normalized to typical pressure | |
// High energy density explosives may benefit from slightly increased standoff | |
const energyFactor = explosive.energyDensity / 5; // Normalized to typical energy | |
// Combined adjustment factor (weighting based on importance) | |
const adjustmentFactor = 0.5 * velocityFactor + 0.3 * pressureFactor + 0.2 * energyFactor; | |
// Apply adjustment with limits | |
return baseStandoff * Math.max(0.8, Math.min(1.2, adjustmentFactor)); | |
} | |
/** | |
* Find the closest feasible scenario when all optimizations fail | |
*/ | |
function findClosestFeasibleScenario( | |
originalScenario: any, | |
explosives: Explosive[] | |
): { | |
distance: number; | |
scenario: any; | |
material: Explosive; | |
changes: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }>; | |
} { | |
let minDistance = Number.MAX_VALUE; | |
let bestScenario: any = null; | |
let bestMaterial: Explosive | null = null; | |
let bestChanges: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }> = []; | |
// Define parameter spaces to search | |
const parameterSpaces = generateParameterSpaces(originalScenario); | |
// Maximum number of iterations to prevent excessive computation | |
const MAX_ITERATIONS = 500; | |
let iterations = 0; | |
// Use simulated annealing for global search | |
let currentScenario = JSON.parse(JSON.stringify(originalScenario)); | |
let currentDistance = calculateScenarioDistance(currentScenario, originalScenario); | |
let temperature = 100.0; // Initial temperature | |
const coolingRate = 0.97; // Cooling rate | |
// Track changes for best solution | |
let currentChanges: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }> = []; | |
while (temperature > 0.1 && iterations < MAX_ITERATIONS) { | |
iterations++; | |
// Generate neighbor by modifying parameters | |
const neighborScenario = JSON.parse(JSON.stringify(currentScenario)); | |
const parameterChanges = generateNeighborScenario(neighborScenario, parameterSpaces); | |
// Skip if not physically consistent | |
if (!isPhysicallyConsistent(neighborScenario)) { | |
continue; | |
} | |
// Check if neighbor is feasible | |
const feasibilityResult = optimizeExplosive(explosives, neighborScenario); | |
const neighborDistance = calculateScenarioDistance(neighborScenario, originalScenario); | |
if (feasibilityResult) { | |
// Found feasible solution - check if it's closer than previous best | |
if (neighborDistance < minDistance) { | |
minDistance = neighborDistance; | |
bestScenario = JSON.parse(JSON.stringify(neighborScenario)); | |
bestMaterial = feasibilityResult.material; | |
bestChanges = parameterChanges.map(change => ({ | |
parameter: getParameterName(change.parameter), | |
originalValue: originalParameterValue(originalScenario, change.parameter), | |
newValue: change.value, | |
unit: getUnitForProperty(change.parameter.split('.').pop() || '') | |
})); | |
} | |
// We could stop here, but continuing search might find even better solutions | |
// Adjust temperature to encourage exploration of feasible regions | |
temperature *= 0.9; | |
} else { | |
// Not feasible, but decide whether to move to this state anyway | |
const deltaDistance = neighborDistance - currentDistance; | |
// Accept worse solutions with probability based on temperature and distance difference | |
if (deltaDistance < 0 || Math.random() < Math.exp(-deltaDistance / temperature)) { | |
currentScenario = neighborScenario; | |
currentDistance = neighborDistance; | |
// Convert parameterChanges to the expected format | |
currentChanges = parameterChanges.map(change => ({ | |
parameter: getParameterName(change.parameter), | |
originalValue: originalParameterValue(originalScenario, change.parameter), | |
newValue: change.value, | |
unit: getUnitForProperty(change.parameter.split('.').pop() || '') | |
})); | |
} | |
// Cool down the temperature | |
temperature *= coolingRate; | |
} | |
} | |
return { | |
distance: minDistance, | |
scenario: bestScenario || originalScenario, | |
material: bestMaterial || explosives[0], | |
changes: bestChanges | |
}; | |
} | |
/** | |
* Generate parameter spaces for optimization search | |
*/ | |
function generateParameterSpaces(scenario: any): Record<string, { | |
min: number; | |
max: number; | |
step: number; | |
type: 'numeric' | 'categorical'; | |
categories?: any[]; | |
}> { | |
const spaces: Record<string, any> = {}; | |
// Radius search space | |
spaces['radius'] = { | |
min: scenario.radius * 0.5, | |
max: scenario.radius * 2.0, | |
step: scenario.radius * 0.1, | |
type: 'numeric' | |
}; | |
// Weight search space | |
spaces['maxWeight'] = { | |
min: scenario.maxWeight * 0.5, | |
max: scenario.maxWeight * 3.0, | |
step: scenario.maxWeight * 0.2, | |
type: 'numeric' | |
}; | |
// Shape type | |
spaces['shape.type'] = { | |
type: 'categorical', | |
categories: ['sphere', 'cylinder', 'cube', 'hemisphere', 'conical', 'shaped_charge'] | |
}; | |
// Casing thickness | |
spaces['casing.thickness'] = { | |
min: Math.max(0.1, scenario.casing.thickness * 0.5), | |
max: scenario.casing.thickness * 2.0, | |
step: scenario.casing.thickness * 0.1, | |
type: 'numeric' | |
}; | |
// Environmental parameters | |
if (scenario.environment) { | |
// Temperature | |
spaces['environment.temperature'] = { | |
min: Math.max(223, scenario.environment.temperature * 0.8), // Min 223K (-50°C) | |
max: Math.min(373, scenario.environment.temperature * 1.2), // Max 373K (100°C) | |
step: 10, | |
type: 'numeric' | |
}; | |
// Altitude | |
if (scenario.environment.altitude !== undefined) { | |
spaces['environment.altitude'] = { | |
min: 0, | |
max: Math.max(5000, scenario.environment.altitude * 1.5), | |
step: 500, | |
type: 'numeric' | |
}; | |
} | |
// Water depth | |
if (scenario.environment.waterDepth !== undefined && scenario.environment.waterDepth > 0) { | |
spaces['environment.waterDepth'] = { | |
min: 0.5, | |
max: Math.max(100, scenario.environment.waterDepth * 1.5), | |
step: Math.max(0.5, scenario.environment.waterDepth * 0.1), | |
type: 'numeric' | |
}; | |
} | |
} | |
// If shaped charge, add specific parameters | |
if (scenario.shape.type === 'shaped_charge') { | |
// Cone angle | |
spaces['shape.dimensions.coneAngle'] = { | |
min: 30, | |
max: 90, | |
step: 5, | |
type: 'numeric' | |
}; | |
// Standoff distance | |
spaces['shape.dimensions.standoff'] = { | |
min: 0.5, | |
max: 10, | |
step: 0.5, | |
type: 'numeric' | |
}; | |
// Liner properties if available | |
if (scenario.shape.linerProperties) { | |
spaces['shape.linerProperties.thickness'] = { | |
min: 0.5, | |
max: 5, | |
step: 0.5, | |
type: 'numeric' | |
}; | |
spaces['shape.linerProperties.angle'] = { | |
min: 20, | |
max: 60, | |
step: 5, | |
type: 'numeric' | |
}; | |
} | |
} | |
// Portability as a binary choice | |
spaces['isPortable'] = { | |
type: 'categorical', | |
categories: [true, false] | |
}; | |
return spaces; | |
} | |
/** | |
* Generate a neighbor scenario by modifying random parameters | |
*/ | |
function generateNeighborScenario( | |
scenario: any, | |
parameterSpaces: Record<string, any> | |
): Array<{ parameter: string; value: any }> { | |
// Choose parameters to modify (1-3 random parameters) | |
const parameterKeys = Object.keys(parameterSpaces); | |
const modificationCount = 1 + Math.floor(Math.random() * 2); // 1-3 modifications | |
const selectedParameters: Array<string> = []; | |
// Randomly select parameters to modify | |
for (let i = 0; i < modificationCount; i++) { | |
const randomIndex = Math.floor(Math.random() * parameterKeys.length); | |
const param = parameterKeys[randomIndex]; | |
// Avoid duplicates | |
if (!selectedParameters.includes(param)) { | |
selectedParameters.push(param); | |
} | |
} | |
// Apply modifications | |
const changes: Array<{ parameter: string; value: any }> = []; | |
for (const param of selectedParameters) { | |
const space = parameterSpaces[param]; | |
let newValue; | |
if (space.type === 'numeric') { | |
// Generate random value within range | |
const steps = Math.floor((space.max - space.min) / space.step); | |
const randomSteps = Math.floor(Math.random() * steps); | |
newValue = space.min + randomSteps * space.step; | |
} else if (space.type === 'categorical') { | |
// Choose random category | |
const randomIndex = Math.floor(Math.random() * space.categories.length); | |
newValue = space.categories[randomIndex]; | |
} | |
// Apply the parameter change | |
applyParameterChange(scenario, param, newValue); | |
changes.push({ parameter: param, value: newValue }); | |
} | |
return changes; | |
} | |
/** | |
* Calculate distance between two scenarios (for measuring how much the scenario has changed) | |
*/ | |
function calculateScenarioDistance(scenarioA: any, scenarioB: any): number { | |
let distance = 0; | |
// Compare radius (normalized) | |
if (scenarioA.radius !== undefined && scenarioB.radius !== undefined) { | |
const radiusChange = Math.abs(scenarioA.radius - scenarioB.radius) / scenarioB.radius; | |
distance += radiusChange * 2; // Weight radius changes heavily | |
} | |
// Compare weight (normalized) | |
if (scenarioA.maxWeight !== undefined && scenarioB.maxWeight !== undefined) { | |
const weightChange = Math.abs(scenarioA.maxWeight - scenarioB.maxWeight) / scenarioB.maxWeight; | |
distance += weightChange * 2; // Weight changes heavily | |
} | |
// Compare shape type | |
if (scenarioA.shape?.type !== scenarioB.shape?.type) { | |
distance += 1.0; // Significant shape change | |
} | |
// Compare casing thickness | |
if (scenarioA.casing?.thickness !== undefined && scenarioB.casing?.thickness !== undefined) { | |
const thicknessChange = Math.abs(scenarioA.casing.thickness - scenarioB.casing.thickness) / | |
scenarioB.casing.thickness; | |
distance += thicknessChange; | |
} | |
// Compare environmental parameters | |
if (scenarioA.environment && scenarioB.environment) { | |
// Temperature | |
if (scenarioA.environment.temperature !== undefined && scenarioB.environment.temperature !== undefined) { | |
const tempChange = Math.abs(scenarioA.environment.temperature - scenarioB.environment.temperature) / 100; | |
distance += tempChange * 0.5; | |
} | |
// Altitude | |
if (scenarioA.environment.altitude !== undefined && scenarioB.environment.altitude !== undefined) { | |
const altChange = Math.abs(scenarioA.environment.altitude - scenarioB.environment.altitude) / 5000; | |
distance += altChange * 0.5; | |
} | |
// Water depth | |
if (scenarioA.environment.waterDepth !== undefined && scenarioB.environment.waterDepth !== undefined) { | |
const depthChange = Math.abs(scenarioA.environment.waterDepth - scenarioB.environment.waterDepth) / | |
Math.max(1, scenarioB.environment.waterDepth); | |
distance += depthChange * 0.7; | |
} | |
} | |
// Compare shaped charge parameters if applicable | |
if (scenarioA.shape?.type === 'shaped_charge' && scenarioB.shape?.type === 'shaped_charge') { | |
// Cone angle | |
if (scenarioA.shape.dimensions?.coneAngle !== undefined && | |
scenarioB.shape.dimensions?.coneAngle !== undefined) { | |
const angleChange = Math.abs(scenarioA.shape.dimensions.coneAngle - | |
scenarioB.shape.dimensions.coneAngle) / 60; | |
distance += angleChange * 0.7; | |
} | |
// Standoff | |
if (scenarioA.shape.dimensions?.standoff !== undefined && | |
scenarioB.shape.dimensions?.standoff !== undefined) { | |
const standoffChange = Math.abs(scenarioA.shape.dimensions.standoff - | |
scenarioB.shape.dimensions.standoff) / | |
Math.max(1, scenarioB.shape.dimensions.standoff); | |
distance += standoffChange * 0.7; | |
} | |
} | |
// Portability | |
if (scenarioA.isPortable !== scenarioB.isPortable) { | |
distance += 0.8; | |
} | |
return distance; | |
} | |
/** | |
* Calculate confidence level for recommended changes | |
*/ | |
function calculateConfidence( | |
changes: Array<{ parameter: string; originalValue: any; newValue: any; unit: string }>, | |
originalScenario: any, | |
modifiedScenario: any | |
): number { | |
// Base confidence starts high and gets reduced | |
let confidenceScore = 95; | |
// Distance between scenarios reduces confidence | |
const scenarioDistance = calculateScenarioDistance(originalScenario, modifiedScenario); | |
confidenceScore -= scenarioDistance * 20; // Higher distance = lower confidence | |
// Number of changes reduces confidence | |
confidenceScore -= changes.length * 3; // Each change reduces confidence | |
// Drastic changes reduce confidence more | |
for (const change of changes) { | |
if (typeof change.originalValue === 'number' && typeof change.newValue === 'number') { | |
const relativeChange = Math.abs(change.newValue - change.originalValue) / | |
Math.max(0.001, Math.abs(change.originalValue)); | |
// Large changes (more than doubling or halving) reduce confidence | |
if (relativeChange > 1.0) { | |
confidenceScore -= 5 + (relativeChange - 1.0) * 10; | |
} | |
} else if (change.originalValue !== change.newValue) { | |
// Categorical changes | |
confidenceScore -= 8; | |
} | |
} | |
// Critical parameter changes affect confidence more | |
const criticalParameterChanges = changes.filter(c => | |
c.parameter.includes('Weight') || | |
c.parameter.includes('Radius') || | |
c.parameter.includes('Shape Type') | |
); | |
confidenceScore -= criticalParameterChanges.length * 5; | |
// Cap confidence between 0-100 | |
return Math.max(0, Math.min(100, confidenceScore)); | |
} | |
// Example targets database | |
const targets: Target[] = [ | |
{ | |
material: 'human', | |
thickness: 30, // cm (average torso) | |
density: 1.0, // g/cm³ (approximately) | |
distance: 50, // m | |
crossSection: 0.75, // m² | |
criticalDamageThreshold: 35 // kPa (lung damage threshold) | |
}, | |
{ | |
material: 'reinforced concrete', | |
thickness: 30, // cm | |
density: 2.4, // g/cm³ | |
distance: 20, // m | |
crossSection: 10, // m² | |
shielding: 0, | |
criticalDamageThreshold: 70 // kPa | |
}, | |
{ | |
material: 'vehicle', | |
thickness: 0.5, // cm (sheet metal) | |
density: 7.85, // g/cm³ | |
distance: 30, // m | |
crossSection: 8, // m² | |
criticalDamageThreshold: 30 // kPa (window breakage) | |
} | |
]; | |
// Updated scenario examples with comprehensive physical modeling | |
// Scenario 1: Portable Demolition Charge | |
runComprehensiveScenario( | |
'Portable Demolition Charge', | |
50, // meters | |
{ type: 'cylinder', dimensions: { radius: 5, height: 20 } }, // cm | |
3, // kg | |
true, | |
{ | |
material: 'aluminum', | |
density: 2.7, | |
thickness: 0.3, | |
yieldStrength: 240, | |
ultimateStrength: 310, | |
elasticModulus: 70, | |
hardness: 75, | |
elongation: 12, | |
thermalConductivity: 167, | |
meltingPoint: 933, | |
fractureToughness: 24, | |
poissonRatio: 0.33, | |
heatCapacity: 900, | |
corrosionResistance: 7, | |
fragmentationPattern: 'natural' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[1]] // Reinforced concrete target | |
); | |
// Scenario 2: Static Mining Explosive | |
runComprehensiveScenario( | |
'Static Mining Explosive', | |
200, // meters | |
{ type: 'cube', dimensions: { side: 50 } }, // cm | |
100, // kg | |
false, | |
{ | |
material: 'steel', | |
density: 7.85, | |
thickness: 1, | |
yieldStrength: 250, | |
ultimateStrength: 420, | |
elasticModulus: 200, | |
hardness: 130, | |
elongation: 25, | |
thermalConductivity: 45, | |
meltingPoint: 1723, | |
fractureToughness: 50, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
{ | |
...environments[0], // Standard atmosphere | |
soilType: 'rock', | |
soilDensity: 2.7 // g/cm³ (typical for granite) | |
} | |
); | |
// Scenario 3: Shaped Charge | |
runComprehensiveScenario( | |
'Shaped Charge', | |
10, // meters | |
{ | |
type: 'shaped_charge', | |
dimensions: { | |
radius: 5, // cm | |
height: 15, // cm | |
standoff: 10 // cm | |
}, | |
linerProperties: { | |
material: 'copper', | |
density: 8.96, | |
thickness: 3, // mm | |
angle: 42, // degrees | |
standoff: 3 // calibers | |
} | |
}, | |
2, // kg | |
true, | |
{ | |
material: 'steel (high strength)', | |
density: 7.85, | |
thickness: 0.4, | |
yieldStrength: 550, | |
ultimateStrength: 800, | |
elasticModulus: 210, | |
hardness: 280, | |
elongation: 15, | |
thermalConductivity: 40, | |
meltingPoint: 1723, | |
fractureToughness: 80, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 5, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[2]] // Vehicle target | |
); | |
// Underwater charge scenario | |
runComprehensiveScenario( | |
'Underwater Charge', | |
30, // meters | |
{ type: 'sphere', dimensions: { radius: 10 } }, // cm | |
15, // kg | |
false, | |
{ | |
material: 'titanium alloy', | |
density: 4.5, | |
thickness: 0.8, | |
yieldStrength: 880, | |
ultimateStrength: 950, | |
elasticModulus: 110, | |
hardness: 320, | |
elongation: 14, | |
thermalConductivity: 7.2, | |
meltingPoint: 1941, | |
fractureToughness: 100, | |
poissonRatio: 0.34, | |
heatCapacity: 530, | |
corrosionResistance: 9, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[3] // Underwater environment | |
); | |
runComprehensiveScenario( | |
'Baseline Combustion Test with TNT', | |
10, // meters | |
{ type: 'cylinder', dimensions: { radius: 5, height: 20 } }, // cm | |
5, // kg | |
false, | |
{ | |
material: 'Steel (mild)', | |
density: 7.85, | |
thickness: 0.5, | |
yieldStrength: 250, | |
ultimateStrength: 420, | |
elasticModulus: 200, | |
hardness: 130, | |
elongation: 25, | |
thermalConductivity: 45, | |
meltingPoint: 1723, | |
fractureToughness: 50, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
environments[0] // Standard atmosphere | |
); | |
runComprehensiveScenario( | |
'Shockwave Redirection with HMX and Ribbed Containment', | |
10, // meters | |
{ type: 'cylinder', dimensions: { radius: 5, height: 20 }, contours: 'ribbed' }, // cm | |
5, // kg | |
false, | |
{ | |
material: 'Steel (high strength)', | |
density: 7.85, | |
thickness: 0.8, | |
yieldStrength: 550, | |
ultimateStrength: 800, | |
elasticModulus: 210, | |
hardness: 280, | |
elongation: 15, | |
thermalConductivity: 40, | |
meltingPoint: 1723, | |
fractureToughness: 80, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 5, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[1]] // Reinforced concrete target | |
); | |
runComprehensiveScenario( | |
'Energy Absorption with C4 and Titanium Casing', | |
50, // meters | |
{ type: 'sphere', dimensions: { radius: 10 } }, // cm | |
3, // kg | |
true, | |
{ | |
material: 'Titanium Alloy', | |
density: 4.5, | |
thickness: 0.6, | |
yieldStrength: 880, | |
ultimateStrength: 950, | |
elasticModulus: 110, | |
hardness: 320, | |
elongation: 14, | |
thermalConductivity: 7.2, | |
meltingPoint: 1941, | |
fractureToughness: 100, | |
poissonRatio: 0.34, | |
heatCapacity: 530, | |
corrosionResistance: 9, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[0]] // Human target | |
); | |
runComprehensiveScenario( | |
'Underwater Blast Containment with ANFO', | |
30, // meters | |
{ type: 'sphere', dimensions: { radius: 15 } }, // cm | |
10, // kg | |
false, | |
{ | |
material: 'Titanium Alloy', | |
density: 4.5, | |
thickness: 1.0, | |
yieldStrength: 880, | |
ultimateStrength: 950, | |
elasticModulus: 110, | |
hardness: 320, | |
elongation: 14, | |
thermalConductivity: 7.2, | |
meltingPoint: 1941, | |
fractureToughness: 100, | |
poissonRatio: 0.34, | |
heatCapacity: 530, | |
corrosionResistance: 9, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[3] // Underwater environment | |
); | |
runComprehensiveScenario( | |
'Shaped Charge for Precision Breaching with PETN', | |
10, // meters | |
{ | |
type: 'shaped_charge', | |
dimensions: { radius: 5, height: 15, standoff: 10 }, // cm | |
linerProperties: { material: 'copper', density: 8.96, thickness: 3, angle: 42, standoff: 3 } | |
}, | |
2, // kg | |
true, | |
{ | |
material: 'Steel (high strength)', | |
density: 7.85, | |
thickness: 0.4, | |
yieldStrength: 550, | |
ultimateStrength: 800, | |
elasticModulus: 210, | |
hardness: 280, | |
elongation: 15, | |
thermalConductivity: 40, | |
meltingPoint: 1723, | |
fractureToughness: 80, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 5, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[1]] // Reinforced concrete target | |
); | |
runComprehensiveScenario( | |
'Suicide Vest with PETN in Urban Area', | |
15, // meters (assessment radius) | |
{ type: 'cylinder', dimensions: { radius: 10, height: 30 } }, // cm | |
2, // kg | |
true, | |
{ | |
material: 'Aluminum', | |
density: 2.7, | |
thickness: 0.3, | |
yieldStrength: 240, | |
ultimateStrength: 310, | |
elasticModulus: 70, | |
hardness: 75, | |
elongation: 12, | |
thermalConductivity: 167, | |
meltingPoint: 933, | |
fractureToughness: 24, | |
poissonRatio: 0.33, | |
heatCapacity: 900, | |
corrosionResistance: 7, | |
fragmentationPattern: 'natural' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[0], targets[2]] // Human and vehicle targets | |
); | |
runComprehensiveScenario( | |
'IED with ANFO in Transit Hub', | |
15, // meters | |
{ type: 'cube', dimensions: { side: 25 } }, // cm | |
10, // kg | |
false, | |
{ | |
material: 'Steel (mild)', | |
density: 7.85, | |
thickness: 0.5, | |
yieldStrength: 250, | |
ultimateStrength: 420, | |
elasticModulus: 200, | |
hardness: 130, | |
elongation: 25, | |
thermalConductivity: 45, | |
meltingPoint: 1723, | |
fractureToughness: 50, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Standard atmosphere with reflection | |
[targets[0], targets[1]] // Human and concrete targets | |
); | |
runComprehensiveScenario( | |
'VBIED with C4 Near Government Building', | |
30, // meters | |
{ type: 'cylinder', dimensions: { radius: 15, height: 50 } }, // cm | |
20, // kg | |
false, | |
{ | |
material: 'Steel (high strength)', | |
density: 7.85, | |
thickness: 1.0, | |
yieldStrength: 550, | |
ultimateStrength: 800, | |
elasticModulus: 210, | |
hardness: 280, | |
elongation: 15, | |
thermalConductivity: 40, | |
meltingPoint: 1723, | |
fractureToughness: 80, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 5, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[0], // Standard atmosphere | |
[targets[1], targets[0]] // Concrete and human targets | |
); | |
runComprehensiveScenario( | |
'Thermobaric Bomb in Enclosed Market', | |
15, // meters | |
{ type: 'sphere', dimensions: { radius: 15 } }, // cm | |
5, // kg | |
true, | |
{ | |
material: 'Aluminum', | |
density: 2.7, | |
thickness: 0.4, | |
yieldStrength: 240, | |
ultimateStrength: 310, | |
elasticModulus: 70, | |
hardness: 75, | |
elongation: 12, | |
thermalConductivity: 167, | |
meltingPoint: 933, | |
fractureToughness: 24, | |
poissonRatio: 0.33, | |
heatCapacity: 900, | |
corrosionResistance: 7, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Enclosed environment | |
[targets[0], targets[1]] // Human and concrete targets | |
); | |
runComprehensiveScenario( | |
'Shaped Charge Vest with HMX in Military Checkpoint', | |
10, // meters | |
{ | |
type: 'shaped_charge', | |
dimensions: { radius: 10, height: 20, standoff: 5 }, // cm | |
linerProperties: { material: 'copper', density: 8.96, thickness: 3, angle: 42, standoff: 2 } | |
}, | |
3, // kg | |
true, | |
{ | |
material: 'Titanium Alloy', | |
density: 4.5, | |
thickness: 0.5, | |
yieldStrength: 880, | |
ultimateStrength: 950, | |
elasticModulus: 110, | |
hardness: 320, | |
elongation: 14, | |
thermalConductivity: 7.2, | |
meltingPoint: 1941, | |
fractureToughness: 100, | |
poissonRatio: 0.34, | |
heatCapacity: 530, | |
corrosionResistance: 9, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[1], // Desert environment | |
[targets[2]] // Vehicle target | |
); | |
runComprehensiveScenario( | |
'Pipe Bomb with Dynamite in Shopping Mall', | |
10, // meters | |
{ type: 'cylinder', dimensions: { radius: 5, height: 20 } }, // cm | |
1, // kg | |
true, | |
{ | |
material: 'Steel (mild)', | |
density: 7.85, | |
thickness: 0.3, | |
yieldStrength: 250, | |
ultimateStrength: 420, | |
elasticModulus: 200, | |
hardness: 130, | |
elongation: 25, | |
thermalConductivity: 45, | |
meltingPoint: 1723, | |
fractureToughness: 50, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Indoor with reflection | |
[ | |
targets[0], // Human | |
{ material: 'glass', thickness: 0.5, density: 2.5, distance: 10, crossSection: 2, criticalDamageThreshold: 15 } | |
] | |
); | |
runComprehensiveScenario( | |
'Backpack Bomb with Semtex at Outdoor Event', | |
20, // meters | |
{ type: 'sphere', dimensions: { radius: 12 } }, // cm | |
3, // kg | |
true, | |
{ | |
material: 'Aluminum', | |
density: 2.7, | |
thickness: 0.4, | |
yieldStrength: 240, | |
ultimateStrength: 310, | |
elasticModulus: 70, | |
hardness: 75, | |
elongation: 12, | |
thermalConductivity: 167, | |
meltingPoint: 933, | |
fractureToughness: 24, | |
poissonRatio: 0.33, | |
heatCapacity: 900, | |
corrosionResistance: 7, | |
fragmentationPattern: 'natural' | |
}, | |
environments[0], // Standard atmosphere, open area | |
[ | |
targets[0], // Human at 10 m | |
{ ...targets[0], distance: 20 } // Human at 20 m | |
] | |
); | |
runComprehensiveScenario( | |
'Truck Bomb with Comp B Near Industrial Facility', | |
50, // meters | |
{ type: 'cube', dimensions: { side: 50 } }, // cm | |
100, // kg | |
false, | |
{ | |
material: 'Steel (high strength)', | |
density: 7.85, | |
thickness: 1.5, | |
yieldStrength: 550, | |
ultimateStrength: 800, | |
elasticModulus: 210, | |
hardness: 280, | |
elongation: 15, | |
thermalConductivity: 40, | |
meltingPoint: 1723, | |
fractureToughness: 80, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 5, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[1], soilType: 'sandy', soilDensity: 1.6 }, // Desert with soil | |
[targets[1], targets[0]] // Concrete and human targets | |
); | |
runComprehensiveScenario( | |
'Underwater IED with Nitroglycerin at Port', | |
10, // meters | |
{ type: 'sphere', dimensions: { radius: 10 } }, // cm | |
2, // kg | |
true, | |
{ | |
material: 'Titanium Alloy', | |
density: 4.5, | |
thickness: 0.5, | |
yieldStrength: 880, | |
ultimateStrength: 950, | |
elasticModulus: 110, | |
hardness: 320, | |
elongation: 14, | |
thermalConductivity: 7.2, | |
meltingPoint: 1941, | |
fractureToughness: 100, | |
poissonRatio: 0.34, | |
heatCapacity: 530, | |
corrosionResistance: 9, | |
fragmentationPattern: 'controlled' | |
}, | |
environments[3], // Underwater environment | |
[ | |
{ material: 'steel', thickness: 2, density: 7.85, distance: 5, crossSection: 10, criticalDamageThreshold: 100 } | |
] | |
); | |
runComprehensiveScenario( | |
'Drone-Delivered Explosive with RDX in Arctic Base', | |
15, // meters | |
{ type: 'cylinder', dimensions: { radius: 8, height: 25 } }, // cm | |
1.5, // kg | |
true, | |
{ | |
material: 'Aluminum', | |
density: 2.7, | |
thickness: 0.3, | |
yieldStrength: 240, | |
ultimateStrength: 310, | |
elasticModulus: 70, | |
hardness: 75, | |
elongation: 12, | |
thermalConductivity: 167, | |
meltingPoint: 933, | |
fractureToughness: 24, | |
poissonRatio: 0.33, | |
heatCapacity: 900, | |
corrosionResistance: 7, | |
fragmentationPattern: 'controlled' | |
}, | |
{ ...environments[2], soilType: 'frozen', soilDensity: 1.9 }, // Arctic environment | |
[targets[0], targets[1]] // Human and concrete targets | |
); | |
runComprehensiveScenario( | |
'Holographic Disguise with Embedded Micro-Explosives', | |
10, // meters (assessment radius) | |
{ type: 'cylinder', dimensions: { radius: 0.5, height: 1 } }, // cm (individual micro-charge) | |
2, // kg total | |
true, | |
{ | |
material: 'Non-metallic', | |
density: 1.0, // g/cm³ (approximate for flexible material) | |
thickness: 0.1, // cm | |
yieldStrength: 10, // MPa (low strength) | |
ultimateStrength: 20, // MPa | |
elasticModulus: 1, // GPa | |
hardness: 10, // Brinell | |
elongation: 50, // % | |
thermalConductivity: 0.1, // W/(m·K) | |
meltingPoint: 373, // K | |
fractureToughness: 1, // MPa·m^(1/2) | |
poissonRatio: 0.4, | |
heatCapacity: 1000, // J/(kg·K) | |
corrosionResistance: 5, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Indoor with reflection (Standard Atmosphere modified) | |
[ | |
{ material: 'human', thickness: 30, density: 1.0, distance: 5, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'reinforced concrete', thickness: 30, density: 2.4, distance: 10, crossSection: 10, criticalDamageThreshold: 70 } | |
] | |
); | |
runComprehensiveScenario( | |
'Swarm of Explosive-Laden Micro-Drones', | |
3, // meters (per drone) | |
{ type: 'sphere', dimensions: { radius: 2.5 } }, // cm (approximate for 50 g charge) | |
0.05, // kg per drone | |
true, | |
{ | |
material: 'Plastic', | |
density: 1.2, // g/cm³ | |
thickness: 0.1, // cm | |
yieldStrength: 30, // MPa | |
ultimateStrength: 50, // MPa | |
elasticModulus: 2, // GPa | |
hardness: 20, // Brinell | |
elongation: 20, // % | |
thermalConductivity: 0.2, // W/(m·K) | |
meltingPoint: 373, // K | |
fractureToughness: 2, // MPa·m^(1/2) | |
poissonRatio: 0.4, | |
heatCapacity: 1000, // J/(kg·K) | |
corrosionResistance: 6, | |
fragmentationPattern: 'natural' | |
}, | |
environments[0], // Standard atmosphere, open area | |
[ | |
{ material: 'human', thickness: 30, density: 1.0, distance: 3, crossSection: 0.75, criticalDamageThreshold: 35 } | |
] | |
); | |
runComprehensiveScenario( | |
'Explosive-Laced Paint in Art Installation', | |
10, // meters | |
{ type: 'cylinder', dimensions: { radius: 15, height: 170 } }, // cm (sculpture dimensions) | |
3, // kg | |
false, | |
{ | |
material: 'Plaster', | |
density: 1.5, // g/cm³ | |
thickness: 2, // cm | |
yieldStrength: 5, // MPa | |
ultimateStrength: 10, // MPa | |
elasticModulus: 5, // GPa | |
hardness: 15, // Brinell | |
elongation: 1, // % | |
thermalConductivity: 0.5, // W/(m·K) | |
meltingPoint: 373, // K (decomposition) | |
fractureToughness: 0.5, // MPa·m^(1/2) | |
poissonRatio: 0.2, | |
heatCapacity: 800, // J/(kg·K) | |
corrosionResistance: 3, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Indoor with reflection (Standard Atmosphere modified) | |
[ | |
{ material: 'human', thickness: 30, density: 1.0, distance: 5, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'artifact', thickness: 1, density: 1.5, distance: 10, crossSection: 1, criticalDamageThreshold: 50 } | |
] | |
); | |
runComprehensiveScenario( | |
'Subterranean Acoustic Explosive in Subway System', | |
20, // meters | |
{ type: 'cylinder', dimensions: { radius: 15, height: 170 } }, // cm | |
10, // kg | |
false, | |
{ | |
material: 'Steel (mild)', | |
density: 7.85, // g/cm³ | |
thickness: 1.0, // cm | |
yieldStrength: 250, // MPa | |
ultimateStrength: 420, // MPa | |
elasticModulus: 200, // GPa | |
hardness: 130, // Brinell | |
elongation: 25, // % | |
thermalConductivity: 45, // W/(m·K) | |
meltingPoint: 1723, // K | |
fractureToughness: 50, // MPa·m^(1/2) | |
poissonRatio: 0.3, | |
heatCapacity: 465, // J/(kg·K) | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Tunnel environment (Standard Atmosphere modified) | |
[ | |
{ material: 'human', thickness: 30, density: 1.0, distance: 10, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'reinforced concrete', thickness: 30, density: 2.4, distance: 20, crossSection: 10, criticalDamageThreshold: 70 } | |
] | |
); | |
runComprehensiveScenario( | |
'Bioluminescent Explosive in Marine Environment', | |
15, // meters | |
{ type: 'sphere', dimensions: { radius: 15 } }, // cm | |
4, // kg | |
true, | |
{ | |
material: 'Gelatinous', | |
density: 1.0, // g/cm³ | |
thickness: 0.5, // cm | |
yieldStrength: 0.1, // MPa | |
ultimateStrength: 0.2, // MPa | |
elasticModulus: 0.01, // GPa | |
hardness: 1, // Brinell | |
elongation: 100, // % | |
thermalConductivity: 0.5, // W/(m·K) | |
meltingPoint: 300, // K | |
fractureToughness: 0.1, // MPa·m^(1/2) | |
poissonRatio: 0.45, | |
heatCapacity: 1500, // J/(kg·K) | |
corrosionResistance: 2, | |
fragmentationPattern: 'natural' | |
}, | |
environments[3], // Underwater environment | |
[ | |
{ material: 'human', thickness: 30, density: 1.0, distance: 5, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'steel', thickness: 2, density: 7.85, distance: 15, crossSection: 10, criticalDamageThreshold: 50 } | |
] | |
); | |
runComprehensiveScenario( | |
'IED with ANFO in Transit Hub', | |
1, // meters | |
{ type: 'cube', dimensions: { side: 100 } }, // cm | |
1000, // kg | |
false, | |
{ | |
material: 'Steel (mild)', | |
density: 7.85, | |
thickness: 0.5, | |
yieldStrength: 250, | |
ultimateStrength: 420, | |
elasticModulus: 200, | |
hardness: 130, | |
elongation: 25, | |
thermalConductivity: 45, | |
meltingPoint: 1723, | |
fractureToughness: 50, | |
poissonRatio: 0.3, | |
heatCapacity: 465, | |
corrosionResistance: 4, | |
fragmentationPattern: 'natural' | |
}, | |
{ ...environments[0], reflectiveSurfaces: true }, // Standard atmosphere with reflection | |
[ | |
{ material: 'human', thickness: 30, density: 1.0, distance: 5, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'human', thickness: 30, density: 1.0, distance: 10, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'human', thickness: 30, density: 1.0, distance: 15, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'human', thickness: 30, density: 1.0, distance: 20, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'human', thickness: 30, density: 1.0, distance: 25, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'human', thickness: 30, density: 1.0, distance: 30, crossSection: 0.75, criticalDamageThreshold: 35 }, | |
{ material: 'reinforced concrete', thickness: 30, density: 2.4, distance: 10, crossSection: 10, criticalDamageThreshold: 70 } | |
] // Human and concrete targets | |
); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment