推荐算法ComputeEffectiveRadius用高斯公式,BuildFireSchedule去掉算法逻辑,129测试通过

This commit is contained in:
tian 2026-06-12 11:03:30 +08:00
parent c060030e0a
commit 6a184d9bf8
8 changed files with 52 additions and 110 deletions

View File

@ -87,10 +87,10 @@ namespace CounterDrone.Core.Algorithms
var r0 = 3.3f * (float)Math.Pow(Math.Max(0.01, (float)ammo.BurstChargeKg), 0.32);
var k = (float)ammo.TurbulentExpansionK;
var maxDur = (float)ammo.MaxDuration;
// 无人机到达中点时云团已膨胀了 halfTime 秒
var halfTime = totalFlightTime / 2f;
var effectiveR = r0 + k * (float)Math.Sqrt(Math.Min(halfTime, 30f)); // 不超过 Phase 2
if (halfTime > 30f) effectiveR += k * (float)Math.Sqrt(halfTime - 30f) * 0.3f; // Phase 3 增速变慢
var windSpeed = (float)threat.Environment.WindSpeed;
var weather = (WeatherType)threat.Environment.WeatherType;
var effectiveR = ComputeEffectiveRadius(r0, k, halfTime, windSpeed, weather, ammo);
var crossTime = (2f * effectiveR) / avgSpeed;
var neededExposure = aerosolType == AerosolType.ActiveMaterial ? 2f : 6f;
@ -188,5 +188,26 @@ namespace CounterDrone.Core.Algorithms
}
private static Vector3 ToV3(Waypoint wp) => new((float)wp.PosX, (float)wp.PosY, (float)wp.PosZ);
/// <summary>计算云团在目标时刻的有效半径</summary>
private static float ComputeEffectiveRadius(float r0, float k, float halfTime,
float windSpeed, WeatherType weather, AmmunitionSpec ammo)
{
// Phase 2 结束时的半径
var rPhase2 = r0 + k * (float)Math.Sqrt(Math.Min(halfTime, 30f));
if (halfTime <= 30f) return rPhase2;
// Phase 3用高斯扩散公式计算浓度场下的有效半径
var x = Math.Max(1f, windSpeed * (halfTime - 30f));
var cls = Kinematics.GetStabilityClass(weather, windSpeed);
var sY = Kinematics.SigmaY(cls, x);
var sZ = Kinematics.SigmaZ(cls, x);
var peakC = Kinematics.GaussianPeakConcentration((float)ammo.SourceStrength, sY, sZ);
var threshold = (float)ammo.EffectiveConcentration;
if (peakC <= threshold) return rPhase2;
var sigma = (sY + sZ) / 2f;
return rPhase2 + sigma * (float)Math.Sqrt(2f * Math.Log(peakC / threshold));
}
}
}

View File

@ -89,7 +89,7 @@ namespace CounterDrone.Core.Simulation
{
_ammoSpec = ammoDb.Table<AmmunitionSpec>()
.FirstOrDefault(a => a.AerosolType == config.Cloud.AerosolType)
?? new AmmunitionSpec { InitialRadius = 50, CoreDensity = 1.0f, DispersionRateBase = 5, MaxRadius = 200, MaxDuration = 60 };
?? new AmmunitionSpec { InitialRadius = 50, CoreDensity = 1.0f, SourceStrength = 1.0, MaxRadius = 200, MaxDuration = 60 };
}
// 无人机

View File

@ -49,7 +49,7 @@ namespace CounterDrone.Unity
db.Insert(new AmmunitionSpec
{
AerosolType = (int)AerosolType.InertGas, InitialRadius = 50,
CoreDensity = 1.0, DispersionRateBase = 5, MaxRadius = 500,
CoreDensity = 1.0, SourceStrength = 50.0, DispersionRateBase = 3.0, MaxRadius = 500,
MaxDuration = 120, EffectiveConcentration = 0.05,
});
var ammoCatalog = db.Table<AmmunitionSpec>().ToList();

View File

@ -37,7 +37,7 @@ namespace CounterDrone.Unity
db.Insert(new AmmunitionSpec
{
AerosolType = (int)AerosolType.InertGas, Name = "惰性气体弹",
InitialRadius = 50, CoreDensity = 1.0, DispersionRateBase = 5,
InitialRadius = 50, CoreDensity = 1.0, SourceStrength = 50.0, DispersionRateBase = 3.0,
MaxRadius = 500, MaxDuration = 120, EffectiveConcentration = 0.05,
});

View File

@ -186,48 +186,30 @@ namespace CounterDrone.Unity
var cloud = detail.Cloud;
if (!cloud.RecommendedTiming.HasValue || cloud.RecommendedTiming <= 0) return schedule;
var platforms = detail.Equipment
.FindAll(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
// 每个发射平台打一发,目标云团位置(平台已由推荐算法部署到最佳位置)
var platforms = detail.Equipment.FindAll(e => e.EquipmentRole == (int)EquipmentRole.LaunchPlatform);
if (platforms.Count == 0) return schedule;
var ammo = ammoCatalog.Find(a => a.AerosolType == cloud.AerosolType);
if (ammo == null) return schedule;
var target = detail.Targets[0];
var avgSpeed = (float)target.TypicalSpeed / 3.6f;
var neededExposure = cloud.AerosolType == (int)AerosolType.ActiveMaterial ? 2f : 6f;
var spacing = (float)ammo.InitialRadius * 1.5f;
var diameter = 2f * (float)ammo.InitialRadius;
var roundsNeeded = Mathf.Max(1, Mathf.CeilToInt((neededExposure * avgSpeed - diameter) / spacing) + 1);
var p0 = platforms[0];
var mv = (float)(p0.MuzzleVelocity ?? 800);
var releaseAlt = (float)cloud.DisperseHeight;
if (releaseAlt < 10) releaseAlt = 300f;
for (int r = 0; r < roundsNeeded; r++)
for (int i = 0; i < platforms.Count; i++)
{
var offset = (r - (roundsNeeded - 1) / 2f) * spacing;
var tx = (float)cloud.PositionX + offset; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ;
var dx = tx - (float)p0.PositionX; var dz = tz - (float)p0.PositionZ;
var p = platforms[i];
var mv = (float)(p.MuzzleVelocity ?? 800);
var tx = (float)cloud.PositionX; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ;
var dx = tx - (float)p.PositionX; var dz = tz - (float)p.PositionZ;
var dist = Mathf.Sqrt(dx * dx + dz * dz);
var angle = Kinematics.CalculateLaunchAngle(dist, mv, releaseAlt);
var sinA = Mathf.Sin(angle);
var tUp = mv * sinA / 9.81f;
var peakH = mv * mv * sinA * sinA / (2f * 9.81f);
var dropH = Mathf.Max(0, peakH - releaseAlt);
var flightTime = tUp + Mathf.Sqrt(2f * dropH / 9.81f);
var fireTime = (float)cloud.RecommendedTiming.Value - flightTime;
var ft = tUp + Mathf.Sqrt(2f * dropH / 9.81f);
var fireTime = (float)cloud.RecommendedTiming.Value - ft;
if (fireTime < 0) fireTime = 0.1f;
schedule.Add(new FireEvent
{
FireTime = fireTime, PlatformIndex = r % platforms.Count,
TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv,
});
schedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = i, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv });
}
return schedule;
}
}

View File

@ -25,17 +25,7 @@ namespace CounterDrone.Core.Tests
var paths = new TestPathProvider(_testDir);
var dbm = new DatabaseManager(paths);
_db = dbm.OpenMainDb();
_db.Insert(new AmmunitionSpec
{
AerosolType = (int)AerosolType.InertGas,
InitialRadius = 50,
CoreDensity = 1.0,
SourceStrength = 50.0, DispersionRateBase = 3.0,
MaxRadius = 500,
MaxDuration = 120,
EffectiveConcentration = 0.05,
});
foreach (var a in DefaultAmmunition.GetAll()) _db.Insert(a);
_scenario = new ScenarioService(
new SimTaskRepository(_db), new CombatSceneRepository(_db),

View File

@ -21,7 +21,6 @@ namespace CounterDrone.Core.Tests
private readonly FrameDataStore _frameStore;
private readonly SQLiteConnection _mainDb;
private List<AmmunitionSpec> _ammoCatalog;
private DefenseRecommendation _lastRecommendation;
private string _taskId = string.Empty;
public FullPipelineTests()
@ -31,44 +30,10 @@ namespace CounterDrone.Core.Tests
var dbm = new DatabaseManager(_paths);
_mainDb = dbm.OpenMainDb();
_mainDb.Insert(new AmmunitionSpec
{
Id = "ammo-inert",
AerosolType = (int)AerosolType.InertGas,
Name = "惰性气体弹",
InitialRadius = 50,
CoreDensity = 1.0,
SourceStrength = 500.0,
MaxRadius = 500,
MaxDuration = 120,
EffectiveConcentration = 0.05,
});
_mainDb.Insert(new AmmunitionSpec
{
Id = "ammo-active",
AerosolType = (int)AerosolType.ActiveMaterial,
Name = "活性材料弹",
InitialRadius = 30,
CoreDensity = 2.0,
SourceStrength = 500.0,
MaxRadius = 400,
MaxDuration = 90,
EffectiveConcentration = 0.1,
});
_mainDb.Insert(new AmmunitionSpec
{
Id = "ammo-fuel",
AerosolType = (int)AerosolType.ActiveFuel,
Name = "活性燃料弹",
InitialRadius = 40,
CoreDensity = 1.5,
SourceStrength = 500.0,
MaxRadius = 450,
MaxDuration = 100,
EffectiveConcentration = 0.03,
});
// 使用默认弹药规格
foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a);
_ammoCatalog = _mainDb.Table<AmmunitionSpec>().ToList();
_ammoCatalog = new List<AmmunitionSpec>(DefaultAmmunition.GetAll());
_scenario = new ScenarioService(
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),
@ -122,30 +87,26 @@ namespace CounterDrone.Core.Tests
.ToList();
if (platforms.Count == 0) return schedule;
// 直接从推荐方案读取齐射参数
var roundsNeeded = _lastRecommendation.Best.SalvoRounds;
var spacing = _lastRecommendation.Best.SalvoSpacing;
var p = platforms[0];
var muzzleV = (float)(p.MuzzleVelocity ?? 800);
// 每个部署平台打一发(平台数量由推荐算法确定)
var releaseAlt = (float)cloud.DisperseHeight;
if (releaseAlt < 10) releaseAlt = 300f;
for (int r = 0; r < roundsNeeded; r++)
for (int i = 0; i < platforms.Count; i++)
{
var offset = (r - (roundsNeeded - 1) / 2f) * spacing;
var tx = (float)cloud.PositionX + offset; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ;
var p = platforms[i];
var mv = (float)(p.MuzzleVelocity ?? 800);
var tx = (float)cloud.PositionX; var ty = (float)cloud.PositionY; var tz = (float)cloud.PositionZ;
var dx = tx - (float)p.PositionX; var dz = tz - (float)p.PositionZ;
var dist = (float)Math.Sqrt(dx * dx + dz * dz);
var angle = Kinematics.CalculateLaunchAngle(dist, muzzleV, releaseAlt);
var angle = Kinematics.CalculateLaunchAngle(dist, mv, releaseAlt);
var sinA = (float)Math.Sin(angle);
var tUp = muzzleV * sinA / 9.81f;
var peakH = muzzleV * muzzleV * sinA * sinA / (2f * 9.81f);
var tUp = mv * sinA / 9.81f;
var peakH = mv * mv * sinA * sinA / (2f * 9.81f);
var dropH = Math.Max(0, peakH - releaseAlt);
var ft = tUp + (float)Math.Sqrt(2f * dropH / 9.81f);
var fireTime = (float)cloud.RecommendedTiming!.Value - ft;
if (fireTime < 0) fireTime = 0.1f;
schedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = r % platforms.Count, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = muzzleV });
schedule.Add(new FireEvent { FireTime = fireTime, PlatformIndex = i, TargetX = tx, TargetY = ty, TargetZ = tz, MuzzleVelocity = mv });
}
return schedule;
}
@ -192,7 +153,6 @@ namespace CounterDrone.Core.Tests
});
_scenario.SaveDeployment(_taskId, equips);
_lastRecommendation = rec;
return rec;
}

View File

@ -27,18 +27,7 @@ namespace CounterDrone.Core.Tests
var paths = new TestPathProvider(_testDir);
var dbManager = new DatabaseManager(paths);
_mainDb = dbManager.OpenMainDb();
_mainDb.Insert(new AmmunitionSpec
{
AerosolType = (int)AerosolType.InertGas,
Name = "Test Ammo",
InitialRadius = 50,
CoreDensity = 1.0,
SourceStrength = 50.0, DispersionRateBase = 3.0,
MaxRadius = 500,
MaxDuration = 120,
EffectiveConcentration = 0.05,
});
foreach (var a in DefaultAmmunition.GetAll()) _mainDb.Insert(a);
_scenarioService = new ScenarioService(
new SimTaskRepository(_mainDb), new CombatSceneRepository(_mainDb),