|
|
@@ -0,0 +1,2225 @@
|
|
|
+using System;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Linq;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using wms.service.Extensions.LayerPacking.model;
|
|
|
+
|
|
|
+namespace wms.service.Extensions.LayerPacking
|
|
|
+{
|
|
|
+ /// <summary>
|
|
|
+ /// 层配装箱服务
|
|
|
+ /// </summary>
|
|
|
+ public class LayerPackingService
|
|
|
+ {
|
|
|
+ /// <summary>
|
|
|
+ /// 每层产品数
|
|
|
+ /// </summary>
|
|
|
+ private const int PRODUCTS_PER_LAYER = 12;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 每箱层数
|
|
|
+ /// </summary>
|
|
|
+ private readonly int LAYERS_PER_BOX;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 每箱产品数
|
|
|
+ /// </summary>
|
|
|
+ private readonly int PRODUCTS_PER_BOX;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 可用于装箱的产品
|
|
|
+ /// </summary>
|
|
|
+ private readonly List<LayerPackingProduct> _productPool;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 约束条件
|
|
|
+ /// </summary>
|
|
|
+ private readonly LayerPackingConstraints _constraints;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 随机数
|
|
|
+ /// </summary>
|
|
|
+ private readonly Random _random = new Random();
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 动态大极值阈值
|
|
|
+ /// </summary>
|
|
|
+ private decimal _dynamicHighThreshold;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 动态小极值阈值
|
|
|
+ /// </summary>
|
|
|
+ private decimal _dynamicLowThreshold;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 取前后15%作为极值
|
|
|
+ /// </summary>
|
|
|
+ private const decimal EXTREME_PERCENTAGE = 0.15m;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 最少取5%
|
|
|
+ /// </summary>
|
|
|
+ private const decimal MIN_EXTREME_PERCENTAGE = 0.05m;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 最多取25%
|
|
|
+ /// </summary>
|
|
|
+ private const decimal MAX_EXTREME_PERCENTAGE = 0.25m;
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构造函数
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="products">产品数</param>
|
|
|
+ /// <param name="constraints">约束条件</param>
|
|
|
+ public LayerPackingService(List<LayerPackingProduct> products, LayerPackingConstraints constraints)
|
|
|
+ {
|
|
|
+ _productPool = products.Where(p => !p.IsUsed).ToList();
|
|
|
+ _constraints = constraints;
|
|
|
+
|
|
|
+ // 从约束中获取每箱/层产品数
|
|
|
+ PRODUCTS_PER_BOX = constraints.ProductsPerBox;
|
|
|
+ LAYERS_PER_BOX = constraints.LayersPerBox;
|
|
|
+
|
|
|
+ // 根据产品池动态更新目标值
|
|
|
+ _constraints.UpdateTargetValues(_productPool);
|
|
|
+
|
|
|
+ // 计算动态极值阈值
|
|
|
+ CalculateDynamicExtremeThresholds();
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 动态计算极值阈值
|
|
|
+ /// </summary>
|
|
|
+ private void CalculateDynamicExtremeThresholds()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+
|
|
|
+ // 按扭转值排序
|
|
|
+ var sortedValues = availableProducts.Select(p => p.TorsChkValue).OrderBy(v => v).ToList();
|
|
|
+
|
|
|
+ // 计算要取的极值数量
|
|
|
+ int totalCount = sortedValues.Count;
|
|
|
+ int extremeCount = (int)(totalCount * EXTREME_PERCENTAGE);
|
|
|
+
|
|
|
+ // 确保至少有一定数量的极值
|
|
|
+ extremeCount = Math.Max((int)(totalCount * MIN_EXTREME_PERCENTAGE), extremeCount);
|
|
|
+ extremeCount = Math.Min((int)(totalCount * MAX_EXTREME_PERCENTAGE), extremeCount);
|
|
|
+ extremeCount = Math.Max(1, extremeCount); // 至少1个
|
|
|
+
|
|
|
+ // 计算阈值
|
|
|
+ if (extremeCount < totalCount)
|
|
|
+ {
|
|
|
+ _dynamicLowThreshold = sortedValues[extremeCount - 1];
|
|
|
+ _dynamicHighThreshold = sortedValues[totalCount - extremeCount];
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 如果产品太少,使用中位数划分
|
|
|
+ decimal median = sortedValues[totalCount / 2];
|
|
|
+ _dynamicLowThreshold = sortedValues.First();
|
|
|
+ _dynamicHighThreshold = sortedValues.Last();
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据约束条件进行调整
|
|
|
+ AdjustThresholdsBasedOnConstraints(availableProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 根据约束条件调整阈值(基于正态分布特性)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="availableProducts">有效产品池</param>
|
|
|
+ private void AdjustThresholdsBasedOnConstraints(List<LayerPackingProduct> availableProducts)
|
|
|
+ {
|
|
|
+ if (availableProducts == null || !availableProducts.Any()) return;
|
|
|
+
|
|
|
+ // 计算产品池的统计信息
|
|
|
+ var sortedValues = availableProducts.Select(p => p.TorsChkValue).OrderBy(v => v).ToList();
|
|
|
+ decimal avgValue = sortedValues.Average();
|
|
|
+ int count = sortedValues.Count;
|
|
|
+
|
|
|
+ // 计算标准差
|
|
|
+ double variance = sortedValues.Sum(v => Math.Pow((double)(v - avgValue), 2)) / count;
|
|
|
+ decimal stdDev = (decimal)Math.Sqrt(variance);
|
|
|
+
|
|
|
+ // 计算分位数(用于理解分布)
|
|
|
+ decimal p10 = GetPercentile(sortedValues, 0.10m); // 第10百分位
|
|
|
+ decimal p25 = GetPercentile(sortedValues, 0.25m); // 第一四分位数
|
|
|
+ decimal p50 = GetPercentile(sortedValues, 0.50m); // 中位数
|
|
|
+ decimal p75 = GetPercentile(sortedValues, 0.75m); // 第三四分位数
|
|
|
+ decimal p90 = GetPercentile(sortedValues, 0.90m); // 第90百分位
|
|
|
+
|
|
|
+ // 使用单个产品目标值
|
|
|
+ decimal idealProductValue = _constraints.P;
|
|
|
+
|
|
|
+ // 基于正态分布的特性调整阈值
|
|
|
+ // 在正态分布中:
|
|
|
+ // - 约68%的数据在均值±1σ范围内
|
|
|
+ // - 约95%的数据在均值±2σ范围内
|
|
|
+ // - 约99.7%的数据在均值±3σ范围内
|
|
|
+
|
|
|
+ // 根据产品池平均值与目标值的偏差来调整
|
|
|
+ decimal avgToTargetDiff = avgValue - idealProductValue;
|
|
|
+
|
|
|
+ if (Math.Abs(avgToTargetDiff) > stdDev * 0.5m)
|
|
|
+ {
|
|
|
+ // 产品池显著偏离目标值
|
|
|
+ if (avgToTargetDiff > 0)
|
|
|
+ {
|
|
|
+ // 产品池偏高,需要更多使用高值产品(降低高阈值)
|
|
|
+ // 使用1.5σ作为基准(约86.6%的数据在此范围内)
|
|
|
+ _dynamicHighThreshold = avgValue + stdDev * 1.5m;
|
|
|
+
|
|
|
+ // 低阈值可以相对严格一些,使用2σ
|
|
|
+ _dynamicLowThreshold = avgValue - stdDev * 2.0m;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 产品池偏低,需要更多使用低值产品(提高低阈值)
|
|
|
+ _dynamicLowThreshold = avgValue - stdDev * 1.5m;
|
|
|
+
|
|
|
+ // 高阈值可以相对严格一些
|
|
|
+ _dynamicHighThreshold = avgValue + stdDev * 2.0m;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 产品池接近目标值,使用标准的正态分布阈值
|
|
|
+ // 使用2σ原则,这样约95%的产品在正常范围内,5%为极值
|
|
|
+ _dynamicHighThreshold = avgValue + stdDev * 2.0m;
|
|
|
+ _dynamicLowThreshold = avgValue - stdDev * 2.0m;
|
|
|
+
|
|
|
+ // 如果标准差太小(数据过于集中),适当放宽
|
|
|
+ if (stdDev < 0.2m)
|
|
|
+ {
|
|
|
+ _dynamicHighThreshold = avgValue + 0.4m;
|
|
|
+ _dynamicLowThreshold = avgValue - 0.4m;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 根据实际分布的百分位数进行微调
|
|
|
+ // 确保至少有5-15%的产品被识别为极值
|
|
|
+ int currentHighCount = availableProducts.Count(p => p.TorsChkValue >= _dynamicHighThreshold);
|
|
|
+ int currentLowCount = availableProducts.Count(p => p.TorsChkValue <= _dynamicLowThreshold);
|
|
|
+
|
|
|
+ // 目标:每端有5-15%的极值
|
|
|
+ int minExtremeCount = Math.Max(1, (int)(count * 0.05m));
|
|
|
+ int maxExtremeCount = Math.Max(2, (int)(count * 0.15m));
|
|
|
+
|
|
|
+ // 如果极高值太少,使用百分位数调整
|
|
|
+ if (currentHighCount < minExtremeCount)
|
|
|
+ {
|
|
|
+ // 使用第85-90百分位作为高阈值
|
|
|
+ _dynamicHighThreshold = p90;
|
|
|
+ }
|
|
|
+ else if (currentHighCount > maxExtremeCount)
|
|
|
+ {
|
|
|
+ // 极高值太多,收紧标准,使用2.5σ
|
|
|
+ _dynamicHighThreshold = avgValue + stdDev * 2.5m;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果极低值太少,使用百分位数调整
|
|
|
+ if (currentLowCount < minExtremeCount)
|
|
|
+ {
|
|
|
+ // 使用第10-15百分位作为低阈值
|
|
|
+ _dynamicLowThreshold = p10;
|
|
|
+ }
|
|
|
+ else if (currentLowCount > maxExtremeCount)
|
|
|
+ {
|
|
|
+ // 极低值太多,收紧标准
|
|
|
+ _dynamicLowThreshold = avgValue - stdDev * 2.5m;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 基于正态分布特性确保阈值间隔
|
|
|
+ // 正态分布中,极值应该分布在两端,中间应有足够间隔
|
|
|
+ decimal minGap = Math.Max(stdDev * 3.0m, 1.0m); // 至少3个标准差或1.0
|
|
|
+
|
|
|
+ if (_dynamicHighThreshold - _dynamicLowThreshold < minGap)
|
|
|
+ {
|
|
|
+ // 基于均值对称调整
|
|
|
+ decimal halfGap = minGap / 2;
|
|
|
+ _dynamicHighThreshold = avgValue + halfGap;
|
|
|
+ _dynamicLowThreshold = avgValue - halfGap;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最终边界检查(基于正态分布的3σ原则)
|
|
|
+ decimal maxBound = avgValue + stdDev * 3.0m;
|
|
|
+ decimal minBound = avgValue - stdDev * 3.0m;
|
|
|
+
|
|
|
+ _dynamicHighThreshold = Math.Min(_dynamicHighThreshold, maxBound);
|
|
|
+ _dynamicLowThreshold = Math.Max(_dynamicLowThreshold, minBound);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算百分位数
|
|
|
+ /// </summary>
|
|
|
+ private decimal GetPercentile(List<decimal> sortedValues, decimal percentile)
|
|
|
+ {
|
|
|
+ if (sortedValues.Count == 0) return 0;
|
|
|
+
|
|
|
+ if (sortedValues.Count == 1) return sortedValues[0];
|
|
|
+
|
|
|
+ decimal index = percentile * (sortedValues.Count - 1);
|
|
|
+ int lower = (int)Math.Floor(index);
|
|
|
+ int upper = (int)Math.Ceiling(index);
|
|
|
+
|
|
|
+ if (lower == upper) return sortedValues[lower];
|
|
|
+
|
|
|
+ decimal weight = index - lower;
|
|
|
+ return sortedValues[lower] * (1 - weight) + sortedValues[upper] * weight;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 更新极值阈值(在每次装箱后调用) --线上版本不会触发
|
|
|
+ /// </summary>
|
|
|
+ private void UpdateExtremeThresholds()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+
|
|
|
+ // 如果剩余产品数量显著减少,重新计算阈值
|
|
|
+ if (availableProducts.Count < _productPool.Count * 0.7 || availableProducts.Count < 200)
|
|
|
+ {
|
|
|
+ CalculateDynamicExtremeThresholds();
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 装箱
|
|
|
+ /// </summary>
|
|
|
+ public LayerPackingResult PackBoxe()
|
|
|
+ {
|
|
|
+ var result = new LayerPackingResult();
|
|
|
+ var startTime = DateTime.Now;
|
|
|
+ int boxNumber = 1;
|
|
|
+
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+
|
|
|
+ // 检查是否有足够的产品
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX)
|
|
|
+ {
|
|
|
+ result.RemainingProducts = availableProducts.Count;
|
|
|
+ result.RemainingSolderProducts = availableProducts.Count(p => p.IsSolderProduct);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 每次装箱前更新目标值(基于当前可用产品)
|
|
|
+ _constraints.UpdateTargetValues(availableProducts);
|
|
|
+
|
|
|
+ // 记录当前产品池平均值
|
|
|
+ decimal currentPoolAverage = availableProducts.Average(p => p.TorsChkValue);
|
|
|
+
|
|
|
+ // 尝试装箱
|
|
|
+ var box = FindOptimalBox();
|
|
|
+
|
|
|
+ if (box != null)
|
|
|
+ {
|
|
|
+ box.BoxId = $"BOX{boxNumber:D4}";
|
|
|
+
|
|
|
+ // 记录装箱时的约束信息
|
|
|
+ box.MinBoxTarget = _constraints.MinBoxTotal;
|
|
|
+ box.MaxBoxTarget = _constraints.MaxBoxTotal;
|
|
|
+ box.BoxTarget = _constraints.X;
|
|
|
+ box.ProductPoolAverage = currentPoolAverage;
|
|
|
+
|
|
|
+ // 为每层记录约束信息
|
|
|
+ foreach (var layer in box.Layers)
|
|
|
+ {
|
|
|
+ layer.MinLayerTarget = _constraints.MinLayerTotal;
|
|
|
+ layer.MaxLayerTarget = _constraints.MaxLayerTotal;
|
|
|
+ layer.LayerTarget = _constraints.C;
|
|
|
+ }
|
|
|
+
|
|
|
+ result.Boxes.Add(box);
|
|
|
+
|
|
|
+ // 标记产品为已使用
|
|
|
+ MarkProductsAsUsed(box);
|
|
|
+
|
|
|
+ Console.WriteLine($" ✓ 第 {boxNumber} 箱装配成功,扭转值总和: {box.TotalTorsion:F2},焊点盘数: {box.SolderProductCount},焊点总数: {box.TotalSolderJoints}");
|
|
|
+ boxNumber++;
|
|
|
+
|
|
|
+ // 更新极值阈值
|
|
|
+ if (boxNumber % 5 == 0) // 每5箱更新一次
|
|
|
+ {
|
|
|
+ UpdateExtremeThresholds();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ Console.WriteLine($" ✗ 无法继续装箱,剩余产品不满足约束条件");
|
|
|
+ result.RemainingProducts = availableProducts.Count;
|
|
|
+ result.RemainingSolderProducts = availableProducts.Count(p => p.IsSolderProduct);
|
|
|
+ }
|
|
|
+
|
|
|
+ result.TotalProductsUsed = result.Boxes.Sum(b => b.GetTotalProductCount());
|
|
|
+ result.TotalSolderProductsUsed = result.Boxes.Sum(b => b.SolderProductCount);
|
|
|
+ result.TotalProcessingTime = DateTime.Now - startTime;
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 标记箱中的产品为已使用
|
|
|
+ /// </summary>
|
|
|
+ private void MarkProductsAsUsed(LayerPackingBoxInfo box)
|
|
|
+ {
|
|
|
+ foreach (var layer in box.Layers)
|
|
|
+ {
|
|
|
+ foreach (var product in layer.Products)
|
|
|
+ {
|
|
|
+ var originalProduct = _productPool.First(p => p.Id == product.Id);
|
|
|
+ originalProduct.IsUsed = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 主优化方法 - 找到最优装箱方案(极值必须成对+尽量靠近目标值)
|
|
|
+ /// </summary>
|
|
|
+ public LayerPackingBoxInfo FindOptimalBox()
|
|
|
+ {
|
|
|
+ LayerPackingBoxInfo bestBox = null;
|
|
|
+ decimal bestScore = decimal.MaxValue;
|
|
|
+
|
|
|
+ // 获取当前可用产品
|
|
|
+ var currentAvailableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (currentAvailableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 策略1:目标导向装箱策略 - 最高优先级
|
|
|
+ var targetOrientedBox = TargetOrientedPacking();
|
|
|
+ if (targetOrientedBox != null && targetOrientedBox.IsValid(_constraints) && targetOrientedBox.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ var score = CalculateBoxScore(targetOrientedBox);
|
|
|
+ score *= 0.6m; // 最高优先级
|
|
|
+ if (score < bestScore)
|
|
|
+ {
|
|
|
+ bestScore = score;
|
|
|
+ bestBox = targetOrientedBox;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 策略2:极值优先成箱策略(同时考虑目标值)
|
|
|
+ var extremeBox = ExtremeValuePackingWithGuarantee();
|
|
|
+ if (extremeBox != null && extremeBox.IsValid(_constraints) && extremeBox.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ var score = CalculateBoxScore(extremeBox);
|
|
|
+ score *= 0.65m;
|
|
|
+ if (score < bestScore)
|
|
|
+ {
|
|
|
+ bestScore = score;
|
|
|
+ bestBox = extremeBox;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 策略3:智能极值策略
|
|
|
+ var smartExtremeBox = SmartExtremeValuePacking();
|
|
|
+ if (smartExtremeBox != null && smartExtremeBox.IsValid(_constraints) && smartExtremeBox.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ var score = CalculateBoxScore(smartExtremeBox);
|
|
|
+ score *= 0.7m;
|
|
|
+ if (score < bestScore)
|
|
|
+ {
|
|
|
+ bestScore = score;
|
|
|
+ bestBox = smartExtremeBox;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 策略4:精确贪心策略
|
|
|
+ var preciseGreedyBox = PreciseGreedyPacking();
|
|
|
+ if (preciseGreedyBox != null && preciseGreedyBox.IsValid(_constraints) && preciseGreedyBox.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ var score = CalculateBoxScore(preciseGreedyBox);
|
|
|
+ score *= 0.7m;
|
|
|
+ if (score < bestScore)
|
|
|
+ {
|
|
|
+ bestScore = score;
|
|
|
+ bestBox = preciseGreedyBox;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 策略5:快速随机尝试(目标导向)
|
|
|
+ int maxAttempts = Math.Min(500, Math.Max(200, currentAvailableProducts.Count / 3));
|
|
|
+ var lockObj = new object();
|
|
|
+
|
|
|
+ Parallel.For(0, maxAttempts, i =>
|
|
|
+ {
|
|
|
+ try
|
|
|
+ {
|
|
|
+ var box = i % 3 == 0 ? RandomTargetOrientedPacking() : i % 3 == 1 ? RandomExtremeValuePacking() : RandomQuickPacking();
|
|
|
+
|
|
|
+ if (box != null && box.IsValid(_constraints) && box.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ var score = CalculateBoxScore(box);
|
|
|
+ lock (lockObj)
|
|
|
+ {
|
|
|
+ if (score < bestScore)
|
|
|
+ {
|
|
|
+ bestScore = score;
|
|
|
+ bestBox = box;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ catch (Exception ex)
|
|
|
+ {
|
|
|
+ // 记录错误但继续执行
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ // 如果仍未找到解,使用激进搜索
|
|
|
+ if (bestBox == null)
|
|
|
+ {
|
|
|
+ bestBox = AggressiveSearch();
|
|
|
+ }
|
|
|
+
|
|
|
+ return bestBox;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 目标导向装箱策略 - 优先考虑接近目标值
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo TargetOrientedPacking()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 分类产品
|
|
|
+ var extremeHigh = availableProducts.Where(p => p.TorsChkValue >= _dynamicHighThreshold).ToList();
|
|
|
+ var extremeLow = availableProducts.Where(p => p.TorsChkValue <= _dynamicLowThreshold).ToList();
|
|
|
+ var normal = availableProducts.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - _constraints.P)) // 按接近单个产品目标值排序
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedProducts = new HashSet<long>();
|
|
|
+ decimal boxTargetSum = _constraints.X;
|
|
|
+ decimal currentBoxSum = 0;
|
|
|
+
|
|
|
+ // 构建每一层
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ // 计算剩余层数和每层的理想贡献
|
|
|
+ int remainingLayers = LAYERS_PER_BOX - layerIndex;
|
|
|
+ decimal remainingTarget = boxTargetSum - currentBoxSum;
|
|
|
+ decimal idealLayerSum = remainingTarget / remainingLayers;
|
|
|
+
|
|
|
+ // 如果理想层和接近层目标值,使用层目标值
|
|
|
+ if (Math.Abs(idealLayerSum - _constraints.C) < _constraints.C1 * 0.3m)
|
|
|
+ {
|
|
|
+ idealLayerSum = _constraints.C;
|
|
|
+ }
|
|
|
+
|
|
|
+ var layer = BuildTargetOrientedLayer(
|
|
|
+ extremeHigh.Where(p => !usedProducts.Contains(p.Id)).ToList(),
|
|
|
+ extremeLow.Where(p => !usedProducts.Contains(p.Id)).ToList(),
|
|
|
+ normal.Where(p => !usedProducts.Contains(p.Id)).ToList(),
|
|
|
+ idealLayerSum,
|
|
|
+ usedProducts
|
|
|
+ );
|
|
|
+
|
|
|
+ if (layer == null || layer.Products.Count != PRODUCTS_PER_LAYER) return null;
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ currentBoxSum += layer.TotalTorsion;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查并修复焊点约束
|
|
|
+ if (!CheckAndFixSolderConstraints(box, availableProducts)) return null;
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构建目标导向的层
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="extremeHigh">极高值产品列表</param>
|
|
|
+ /// <param name="extremeLow">极低值产品列表</param>
|
|
|
+ /// <param name="normal">普通产品列表</param>
|
|
|
+ /// <param name="targetSum">本层目标总值</param>
|
|
|
+ /// <param name="usedProducts">已用过的产品ID</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingLayerInfo BuildTargetOrientedLayer(List<LayerPackingProduct> extremeHigh, List<LayerPackingProduct> extremeLow, List<LayerPackingProduct> normal, decimal targetSum, HashSet<long> usedProducts)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal currentSum = 0;
|
|
|
+
|
|
|
+ // 决定是否使用极值对
|
|
|
+ bool useExtremePair = false;
|
|
|
+ LayerPackingProduct selectedHigh = null;
|
|
|
+ LayerPackingProduct selectedLow = null;
|
|
|
+
|
|
|
+ if (extremeHigh.Any() && extremeLow.Any())
|
|
|
+ {
|
|
|
+ // 评估所有可能的极值对
|
|
|
+ decimal bestPairScore = decimal.MaxValue;
|
|
|
+
|
|
|
+ foreach (var high in extremeHigh.Take(Math.Min(5, extremeHigh.Count)))
|
|
|
+ {
|
|
|
+ foreach (var low in extremeLow.Take(Math.Min(5, extremeLow.Count)))
|
|
|
+ {
|
|
|
+ decimal pairSum = high.TorsChkValue + low.TorsChkValue;
|
|
|
+
|
|
|
+ // 计算使用这对极值后,剩余产品是否能达到目标
|
|
|
+ decimal remainingTarget = targetSum - pairSum;
|
|
|
+ decimal avgNeeded = remainingTarget / (PRODUCTS_PER_LAYER - 2);
|
|
|
+
|
|
|
+ // 检查是否有足够的普通产品能满足需求
|
|
|
+ var viableNormal = normal.Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .Where(p => Math.Abs(p.TorsChkValue - avgNeeded) < 1.0m)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (viableNormal.Count >= PRODUCTS_PER_LAYER - 2)
|
|
|
+ {
|
|
|
+ decimal score = Math.Abs(pairSum - targetSum * 2 / PRODUCTS_PER_LAYER); // 极值对占层的比例评分
|
|
|
+ if (score < bestPairScore)
|
|
|
+ {
|
|
|
+ bestPairScore = score;
|
|
|
+ selectedHigh = high;
|
|
|
+ selectedLow = low;
|
|
|
+ useExtremePair = true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果决定使用极值对
|
|
|
+ if (useExtremePair && selectedHigh != null && selectedLow != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(selectedHigh);
|
|
|
+ layer.Products.Add(selectedLow);
|
|
|
+ usedProducts.Add(selectedHigh.Id);
|
|
|
+ usedProducts.Add(selectedLow.Id);
|
|
|
+ currentSum += selectedHigh.TorsChkValue + selectedLow.TorsChkValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 使用动态规划思想填充剩余位置
|
|
|
+ int remainingSlots = PRODUCTS_PER_LAYER - layer.Products.Count;
|
|
|
+ var candidates = normal.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+
|
|
|
+ // 贪心选择最接近理想值的产品
|
|
|
+ while (remainingSlots > 0 && candidates.Any())
|
|
|
+ {
|
|
|
+ decimal remainingTarget = targetSum - currentSum;
|
|
|
+ decimal idealValue = remainingTarget / remainingSlots;
|
|
|
+
|
|
|
+ // 选择最接近理想值的产品
|
|
|
+ var best = candidates
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - idealValue))
|
|
|
+ .ThenBy(p => Math.Abs(p.TorsChkValue - _constraints.P)) // 其次考虑接近单个产品目标值
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ if (best != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(best);
|
|
|
+ usedProducts.Add(best.Id);
|
|
|
+ currentSum += best.TorsChkValue;
|
|
|
+ candidates.Remove(best);
|
|
|
+ remainingSlots--;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果还有空位,从所有可用产品中填充
|
|
|
+ if (remainingSlots > 0)
|
|
|
+ {
|
|
|
+ var allAvailable = extremeHigh.Concat(extremeLow).Concat(normal)
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ while (remainingSlots > 0 && allAvailable.Any())
|
|
|
+ {
|
|
|
+ decimal remainingTarget = targetSum - currentSum;
|
|
|
+ decimal idealValue = remainingTarget / remainingSlots;
|
|
|
+
|
|
|
+ var best = allAvailable.OrderBy(p => Math.Abs(p.TorsChkValue - idealValue)).First();
|
|
|
+ layer.Products.Add(best);
|
|
|
+ usedProducts.Add(best.Id);
|
|
|
+ currentSum += best.TorsChkValue;
|
|
|
+ allAvailable.Remove(best);
|
|
|
+ remainingSlots--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return layer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 精确贪心装箱策略
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo PreciseGreedyPacking()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedProducts = new HashSet<long>();
|
|
|
+
|
|
|
+ // 分类产品
|
|
|
+ var extremeHigh = availableProducts.Where(p => p.TorsChkValue >= _dynamicHighThreshold).ToList();
|
|
|
+ var extremeLow = availableProducts.Where(p => p.TorsChkValue <= _dynamicLowThreshold).ToList();
|
|
|
+ var normal = availableProducts.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold).ToList();
|
|
|
+
|
|
|
+ decimal boxTargetSum = _constraints.X;
|
|
|
+ decimal currentBoxSum = 0;
|
|
|
+
|
|
|
+ // 构建每一层
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal layerTargetSum = _constraints.C;
|
|
|
+
|
|
|
+ // 动态调整层目标,使整箱更接近目标
|
|
|
+ if (layerIndex > 0)
|
|
|
+ {
|
|
|
+ decimal remainingBoxTarget = boxTargetSum - currentBoxSum;
|
|
|
+ int remainingLayers = LAYERS_PER_BOX - layerIndex;
|
|
|
+ decimal adjustedLayerTarget = remainingBoxTarget / remainingLayers;
|
|
|
+
|
|
|
+ // 如果调整后的目标在允许范围内,使用它
|
|
|
+ if (adjustedLayerTarget >= _constraints.MinLayerTotal && adjustedLayerTarget <= _constraints.MaxLayerTotal)
|
|
|
+
|
|
|
+ {
|
|
|
+ layerTargetSum = adjustedLayerTarget;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 构建层
|
|
|
+ decimal currentLayerSum = 0;
|
|
|
+
|
|
|
+ // 考虑是否使用极值对
|
|
|
+ bool useExtremePair = extremeHigh.Any(p => !usedProducts.Contains(p.Id)) && extremeLow.Any(p => !usedProducts.Contains(p.Id)) && (layerIndex < 3 || _random.NextDouble() < 0.3);
|
|
|
+
|
|
|
+ if (useExtremePair)
|
|
|
+ {
|
|
|
+ // 选择能使层和最接近目标的极值对
|
|
|
+ var availableHigh = extremeHigh.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+ var availableLow = extremeLow.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+
|
|
|
+ LayerPackingProduct bestHigh = null;
|
|
|
+ LayerPackingProduct bestLow = null;
|
|
|
+ decimal bestScore = decimal.MaxValue;
|
|
|
+
|
|
|
+ foreach (var high in availableHigh.Take(3))
|
|
|
+ {
|
|
|
+ foreach (var low in availableLow.Take(3))
|
|
|
+ {
|
|
|
+ decimal pairSum = high.TorsChkValue + low.TorsChkValue;
|
|
|
+ decimal remainingSlots = PRODUCTS_PER_LAYER - 2;
|
|
|
+ decimal avgNeeded = (layerTargetSum - pairSum) / remainingSlots;
|
|
|
+
|
|
|
+ // 评分:考虑平均值是否在合理范围内
|
|
|
+ decimal score = Math.Abs(avgNeeded - _constraints.P);
|
|
|
+ if (score < bestScore)
|
|
|
+ {
|
|
|
+ bestScore = score;
|
|
|
+ bestHigh = high;
|
|
|
+ bestLow = low;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bestHigh != null && bestLow != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(bestHigh);
|
|
|
+ layer.Products.Add(bestLow);
|
|
|
+ usedProducts.Add(bestHigh.Id);
|
|
|
+ usedProducts.Add(bestLow.Id);
|
|
|
+ currentLayerSum += bestHigh.TorsChkValue + bestLow.TorsChkValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 贪心填充剩余位置
|
|
|
+ var normalAvailable = normal.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+
|
|
|
+ for (int i = layer.Products.Count; i < PRODUCTS_PER_LAYER; i++)
|
|
|
+ {
|
|
|
+ decimal remaining = layerTargetSum - currentLayerSum;
|
|
|
+ decimal slotsLeft = PRODUCTS_PER_LAYER - i;
|
|
|
+ decimal idealValue = remaining / slotsLeft;
|
|
|
+
|
|
|
+ var candidates = normalAvailable.OrderBy(p => Math.Abs(p.TorsChkValue - idealValue)).Take(20).ToList(); // 考虑前20个最接近的
|
|
|
+
|
|
|
+ if (!candidates.Any())
|
|
|
+ {
|
|
|
+ // 如果普通产品不够,从所有产品中选择
|
|
|
+ candidates = availableProducts
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - idealValue))
|
|
|
+ .Take(10)
|
|
|
+ .ToList();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!candidates.Any()) return null;
|
|
|
+
|
|
|
+ // 选择最优的产品(考虑焊点约束)
|
|
|
+ var selected = candidates.First();
|
|
|
+ if (_constraints.SolderMaxCount > 0 &&
|
|
|
+ box.SolderProductCount + layer.SolderProductCount >= _constraints.SolderMaxCount - (LAYERS_PER_BOX - layerIndex - 1))
|
|
|
+ {
|
|
|
+ var nonSolder = candidates.FirstOrDefault(p => !p.IsSolderProduct);
|
|
|
+ if (nonSolder != null)
|
|
|
+ selected = nonSolder;
|
|
|
+ }
|
|
|
+
|
|
|
+ layer.Products.Add(selected);
|
|
|
+ usedProducts.Add(selected.Id);
|
|
|
+ normalAvailable.Remove(selected);
|
|
|
+ currentLayerSum += selected.TorsChkValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证层
|
|
|
+ if (!layer.IsValid(_constraints) || !layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ // 尝试微调
|
|
|
+ if (!OptimizeLayerForTarget(layer, normalAvailable, usedProducts, layerTargetSum)) return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ currentBoxSum += layer.TotalTorsion;
|
|
|
+ }
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 优化层使其更接近目标值
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="layer">需优化的层</param>
|
|
|
+ /// <param name="availableProducts">可用的产品</param>
|
|
|
+ /// <param name="usedProducts">已使用的产品</param>
|
|
|
+ /// <param name="targetSum">层目标值</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private bool OptimizeLayerForTarget(LayerPackingLayerInfo layer, List<LayerPackingProduct> availableProducts, HashSet<long> usedProducts, decimal targetSum)
|
|
|
+ {
|
|
|
+ var currentSum = layer.TotalTorsion;
|
|
|
+ var deviation = Math.Abs(currentSum - targetSum);
|
|
|
+
|
|
|
+ if (deviation <= _constraints.C1) return true;
|
|
|
+
|
|
|
+ // 获取层中的非极值产品
|
|
|
+ var nonExtremeInLayer = layer.Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ // 尝试替换产品以接近目标
|
|
|
+ int maxAttempts = Math.Min(5, nonExtremeInLayer.Count);
|
|
|
+ for (int attempt = 0; attempt < maxAttempts; attempt++)
|
|
|
+ {
|
|
|
+ LayerPackingProduct worstProduct = null;
|
|
|
+
|
|
|
+ if (currentSum > targetSum)
|
|
|
+ {
|
|
|
+ // 找最大的非极值产品
|
|
|
+ worstProduct = nonExtremeInLayer.OrderByDescending(p => p.TorsChkValue).FirstOrDefault();
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 找最小的非极值产品
|
|
|
+ worstProduct = nonExtremeInLayer.OrderBy(p => p.TorsChkValue).FirstOrDefault();
|
|
|
+ }
|
|
|
+
|
|
|
+ if (worstProduct == null) break;
|
|
|
+
|
|
|
+ decimal idealReplacement = targetSum - (currentSum - worstProduct.TorsChkValue);
|
|
|
+
|
|
|
+ var replacement = availableProducts
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .OrderBy(p => Math.Abs(currentSum - worstProduct.TorsChkValue + p.TorsChkValue - targetSum))
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ if (replacement != null)
|
|
|
+ {
|
|
|
+ decimal newSum = currentSum - worstProduct.TorsChkValue + replacement.TorsChkValue;
|
|
|
+ decimal newDeviation = Math.Abs(newSum - targetSum);
|
|
|
+
|
|
|
+ if (newDeviation < deviation)
|
|
|
+ {
|
|
|
+ int index = layer.Products.IndexOf(worstProduct);
|
|
|
+ layer.Products[index] = replacement;
|
|
|
+ usedProducts.Remove(worstProduct.Id);
|
|
|
+ usedProducts.Add(replacement.Id);
|
|
|
+ availableProducts.Add(worstProduct);
|
|
|
+ availableProducts.Remove(replacement);
|
|
|
+ nonExtremeInLayer.Remove(worstProduct);
|
|
|
+ currentSum = newSum;
|
|
|
+ deviation = newDeviation;
|
|
|
+
|
|
|
+ if (deviation <= _constraints.C1) return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return layer.IsValid(_constraints);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 极值优先装箱(保证成箱)- 严格的极值成对策略
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo ExtremeValuePackingWithGuarantee()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 分类产品
|
|
|
+ var extremeHigh = availableProducts.Where(p => p.TorsChkValue >= _dynamicHighThreshold).ToList();
|
|
|
+ var extremeLow = availableProducts.Where(p => p.TorsChkValue <= _dynamicLowThreshold).ToList();
|
|
|
+ var normal = availableProducts.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold).ToList();
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedProducts = new HashSet<long>();
|
|
|
+
|
|
|
+ // 计算可以使用的极值对数量
|
|
|
+ int maxExtremePairs = Math.Min(extremeHigh.Count, extremeLow.Count);
|
|
|
+ int extremePairsToUse = Math.Min(maxExtremePairs, LAYERS_PER_BOX); // 最多每层一对
|
|
|
+
|
|
|
+ // 构建每一层
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ // 决定这一层是否使用极值对
|
|
|
+ int remainingPairs = Math.Min(
|
|
|
+ extremeHigh.Where(p => !usedProducts.Contains(p.Id)).Count(),
|
|
|
+ extremeLow.Where(p => !usedProducts.Contains(p.Id)).Count()
|
|
|
+ );
|
|
|
+
|
|
|
+ bool useExtremePairInThisLayer = remainingPairs > 0 &&
|
|
|
+ (layerIndex < extremePairsToUse || _random.NextDouble() < 0.5);
|
|
|
+
|
|
|
+ var layer = BuildLayerWithStrictExtremePair(
|
|
|
+ extremeHigh.Where(p => !usedProducts.Contains(p.Id)).ToList(),
|
|
|
+ extremeLow.Where(p => !usedProducts.Contains(p.Id)).ToList(),
|
|
|
+ normal.Where(p => !usedProducts.Contains(p.Id)).ToList(),
|
|
|
+ useExtremePairInThisLayer,
|
|
|
+ usedProducts
|
|
|
+ );
|
|
|
+
|
|
|
+ if (layer == null || layer.Products.Count != PRODUCTS_PER_LAYER) return null;
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 检查并修复焊点约束
|
|
|
+ if (!CheckAndFixSolderConstraints(box, availableProducts)) return null;
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构建严格遵循极值成对约束的层
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="extremeHigh">极高值产品列表</param>
|
|
|
+ /// <param name="extremeLow">极低值产品列表</param>
|
|
|
+ /// <param name="normal">普通产品列表</param>
|
|
|
+ /// <param name="useExtremePair">是否本层必须用极值对</param>
|
|
|
+ /// <param name="usedProducts">已用过的产品ID</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingLayerInfo BuildLayerWithStrictExtremePair(List<LayerPackingProduct> extremeHigh, List<LayerPackingProduct> extremeLow, List<LayerPackingProduct> normal, bool useExtremePair, HashSet<long> usedProducts)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal targetSum = _constraints.C;
|
|
|
+ decimal currentSum = 0;
|
|
|
+
|
|
|
+ // 如果使用极值对,必须添加一对高低极值
|
|
|
+ if (useExtremePair && extremeHigh.Any() && extremeLow.Any())
|
|
|
+ {
|
|
|
+ // 选择最合适的极值对
|
|
|
+ LayerPackingProduct selectedHigh = null;
|
|
|
+ LayerPackingProduct selectedLow = null;
|
|
|
+ decimal bestPairScore = decimal.MaxValue;
|
|
|
+
|
|
|
+ // 尝试找到最佳的高低极值对
|
|
|
+ foreach (var high in extremeHigh.Take(Math.Min(3, extremeHigh.Count)))
|
|
|
+ {
|
|
|
+ foreach (var low in extremeLow.Take(Math.Min(3, extremeLow.Count)))
|
|
|
+ {
|
|
|
+ decimal pairSum = high.TorsChkValue + low.TorsChkValue;
|
|
|
+ decimal pairScore = Math.Abs(pairSum - targetSum / 6); // 期望一对占层的1/6
|
|
|
+
|
|
|
+ if (pairScore < bestPairScore)
|
|
|
+ {
|
|
|
+ bestPairScore = pairScore;
|
|
|
+ selectedHigh = high;
|
|
|
+ selectedLow = low;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (selectedHigh != null && selectedLow != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(selectedHigh);
|
|
|
+ layer.Products.Add(selectedLow);
|
|
|
+ usedProducts.Add(selectedHigh.Id);
|
|
|
+ usedProducts.Add(selectedLow.Id);
|
|
|
+ currentSum += selectedHigh.TorsChkValue + selectedLow.TorsChkValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充剩余位置,只使用普通产品
|
|
|
+ int remainingSlots = PRODUCTS_PER_LAYER - layer.Products.Count;
|
|
|
+ var allAvailable = normal.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+
|
|
|
+ // 如果普通产品不够,则从所有非极值产品中选择
|
|
|
+ if (allAvailable.Count < remainingSlots)
|
|
|
+ {
|
|
|
+ // 添加其他可用产品,但要确保不会单独添加极值
|
|
|
+ var otherProducts = extremeHigh.Concat(extremeLow)
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+ allAvailable.AddRange(otherProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ while (remainingSlots > 0 && allAvailable.Any())
|
|
|
+ {
|
|
|
+ decimal idealValue = (targetSum - currentSum) / remainingSlots;
|
|
|
+
|
|
|
+ // 选择最接近理想值的产品
|
|
|
+ var candidate = allAvailable
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - idealValue))
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ if (candidate != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(candidate);
|
|
|
+ usedProducts.Add(candidate.Id);
|
|
|
+ currentSum += candidate.TorsChkValue;
|
|
|
+ allAvailable.Remove(candidate);
|
|
|
+ remainingSlots--;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最终验证:确保层满足极值成对约束
|
|
|
+ if (!layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold)) return null;
|
|
|
+
|
|
|
+ return layer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 智能极值装箱策略(严格的极值成对)
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo SmartExtremeValuePacking()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 智能分组:考虑极值产品的互补性
|
|
|
+ var extremeHigh = availableProducts.Where(p => p.TorsChkValue >= _dynamicHighThreshold).OrderByDescending(p => p.TorsChkValue).ToList();
|
|
|
+ var extremeLow = availableProducts.Where(p => p.TorsChkValue <= _dynamicLowThreshold).OrderBy(p => p.TorsChkValue).ToList();
|
|
|
+ var normalHigh = availableProducts.Where(p => p.TorsChkValue > 0 && p.TorsChkValue < _dynamicHighThreshold).ToList();
|
|
|
+ var normalLow = availableProducts.Where(p => p.TorsChkValue <= 0 && p.TorsChkValue > _dynamicLowThreshold).ToList();
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedProducts = new HashSet<long>();
|
|
|
+
|
|
|
+ // 构建每一层,使用不同的极值配比策略
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ LayerPackingLayerInfo layer = null;
|
|
|
+
|
|
|
+ // 根据层索引使用不同策略
|
|
|
+ if (layerIndex % 3 == 0)
|
|
|
+ {
|
|
|
+ // 策略1:高低极值平衡
|
|
|
+ layer = BuildBalancedExtremeLayer(extremeHigh, extremeLow, normalHigh, normalLow, usedProducts);
|
|
|
+ }
|
|
|
+ else if (layerIndex % 3 == 1)
|
|
|
+ {
|
|
|
+ // 策略2:极值集中
|
|
|
+ layer = BuildConcentratedExtremeLayer(extremeHigh, extremeLow, normalHigh, normalLow, usedProducts);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // 策略3:混合策略
|
|
|
+ layer = BuildMixedExtremeLayer(extremeHigh, extremeLow, normalHigh, normalLow, usedProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (layer == null || layer.Products.Count != PRODUCTS_PER_LAYER) return null;
|
|
|
+
|
|
|
+ // 验证层的极值成对约束
|
|
|
+ if (!layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold)) return null;
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ }
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构建平衡的极值层(严格的极值成对)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="extremeHigh">极高值产品列表</param>
|
|
|
+ /// <param name="extremeLow">极低值产品列表</param>
|
|
|
+ /// <param name="normalHigh">普通高值产品列表</param>
|
|
|
+ /// <param name="normalLow">普通低值产品列表</param>
|
|
|
+ /// <param name="usedProducts">已用过的产品ID</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingLayerInfo BuildBalancedExtremeLayer(List<LayerPackingProduct> extremeHigh, List<LayerPackingProduct> extremeLow, List<LayerPackingProduct> normalHigh, List<LayerPackingProduct> normalLow, HashSet<long> usedProducts)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal targetSum = _constraints.C;
|
|
|
+
|
|
|
+ // 平衡策略:只添加一对高低极值
|
|
|
+ var availableHigh = extremeHigh.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+ var availableLow = extremeLow.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+
|
|
|
+ if (availableHigh.Any() && availableLow.Any())
|
|
|
+ {
|
|
|
+ // 选择最平衡的一对
|
|
|
+ var high = availableHigh.OrderBy(p => Math.Abs(p.TorsChkValue)).First();
|
|
|
+ var low = availableLow.OrderByDescending(p => Math.Abs(p.TorsChkValue)).First();
|
|
|
+
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ usedProducts.Add(high.Id);
|
|
|
+ usedProducts.Add(low.Id);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充剩余位置,只使用普通产品
|
|
|
+ FillRemainingSlots(layer, normalHigh.Concat(normalLow).Where(p => !usedProducts.Contains(p.Id)).ToList(), targetSum, usedProducts);
|
|
|
+
|
|
|
+ return layer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构建集中的极值层(严格的极值成对)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="extremeHigh">极高值产品列表</param>
|
|
|
+ /// <param name="extremeLow">极低值产品列表</param>
|
|
|
+ /// <param name="normalHigh">普通高值产品列表</param>
|
|
|
+ /// <param name="normalLow">普通低值产品列表</param>
|
|
|
+ /// <param name="usedProducts">已用过的产品ID</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingLayerInfo BuildConcentratedExtremeLayer(List<LayerPackingProduct> extremeHigh, List<LayerPackingProduct> extremeLow, List<LayerPackingProduct> normalHigh, List<LayerPackingProduct> normalLow, HashSet<long> usedProducts)
|
|
|
+
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal targetSum = _constraints.C;
|
|
|
+ decimal currentSum = 0;
|
|
|
+
|
|
|
+ // 集中策略:添加一对影响力最大的极值
|
|
|
+ var availableHigh = extremeHigh.Where(p => !usedProducts.Contains(p.Id)).OrderByDescending(p => p.TorsChkValue).ToList();
|
|
|
+
|
|
|
+ var availableLow = extremeLow.Where(p => !usedProducts.Contains(p.Id)).OrderBy(p => p.TorsChkValue).ToList();
|
|
|
+
|
|
|
+ if (availableHigh.Any() && availableLow.Any())
|
|
|
+ {
|
|
|
+ // 选择绝对值最大的一对
|
|
|
+ var high = availableHigh.First();
|
|
|
+ var low = availableLow.First();
|
|
|
+
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ usedProducts.Add(high.Id);
|
|
|
+ usedProducts.Add(low.Id);
|
|
|
+ currentSum += high.TorsChkValue + low.TorsChkValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 用普通产品平衡
|
|
|
+ FillRemainingSlots(layer, normalHigh.Concat(normalLow).Where(p => !usedProducts.Contains(p.Id)).ToList(), targetSum, usedProducts);
|
|
|
+
|
|
|
+ return layer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 构建混合的极值层(严格的极值成对)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="extremeHigh">极高值产品列表</param>
|
|
|
+ /// <param name="extremeLow">极低值产品列表</param>
|
|
|
+ /// <param name="normalHigh">普通高值产品列表</param>
|
|
|
+ /// <param name="normalLow">普通低值产品列表</param>
|
|
|
+ /// <param name="usedProducts">已用过的产品ID</param>x
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingLayerInfo BuildMixedExtremeLayer(List<LayerPackingProduct> extremeHigh, List<LayerPackingProduct> extremeLow, List<LayerPackingProduct> normalHigh, List<LayerPackingProduct> normalLow, HashSet<long> usedProducts)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal targetSum = _constraints.C;
|
|
|
+
|
|
|
+ // 混合策略:50%概率使用一对极值
|
|
|
+ var availableHigh = extremeHigh.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+ var availableLow = extremeLow.Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+
|
|
|
+ if (availableHigh.Any() && availableLow.Any() && _random.NextDouble() < 0.5)
|
|
|
+ {
|
|
|
+ // 随机选择一对
|
|
|
+ var high = availableHigh[_random.Next(availableHigh.Count)];
|
|
|
+ var low = availableLow[_random.Next(availableLow.Count)];
|
|
|
+
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ usedProducts.Add(high.Id);
|
|
|
+ usedProducts.Add(low.Id);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 添加普通产品
|
|
|
+ var availableNormal = normalHigh.Concat(normalLow).Where(p => !usedProducts.Contains(p.Id)).ToList();
|
|
|
+ FillRemainingSlots(layer, availableNormal, targetSum, usedProducts);
|
|
|
+
|
|
|
+ return layer;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 填充剩余位置(确保不会单独添加极值)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="layer">当前正在填充的分层对象</param>
|
|
|
+ /// <param name="candidates">可选产品列表</param>
|
|
|
+ /// <param name="targetSum">本层目标的总目标值</param>
|
|
|
+ /// <param name="usedProducts">已使用过的产品</param>
|
|
|
+ private void FillRemainingSlots(LayerPackingLayerInfo layer, List<LayerPackingProduct> candidates, decimal targetSum, HashSet<long> usedProducts)
|
|
|
+ {
|
|
|
+ decimal currentSum = layer.TotalTorsion;
|
|
|
+ int remainingSlots = PRODUCTS_PER_LAYER - layer.Products.Count;
|
|
|
+
|
|
|
+ // 只选择非极值产品
|
|
|
+ var normalCandidates = candidates.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold).ToList();
|
|
|
+
|
|
|
+ while (remainingSlots > 0 && normalCandidates.Any())
|
|
|
+ {
|
|
|
+ decimal idealValue = (targetSum - currentSum) / remainingSlots;
|
|
|
+ var best = normalCandidates.OrderBy(p => Math.Abs(p.TorsChkValue - idealValue)).First();
|
|
|
+
|
|
|
+ layer.Products.Add(best);
|
|
|
+ usedProducts.Add(best.Id);
|
|
|
+ currentSum += best.TorsChkValue;
|
|
|
+ normalCandidates.Remove(best);
|
|
|
+ remainingSlots--;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果普通产品不够,尝试从其他产品中选择(但仍要避免单独的极值)
|
|
|
+ if (remainingSlots > 0)
|
|
|
+ {
|
|
|
+ var otherCandidates = candidates
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ while (remainingSlots > 0 && otherCandidates.Any())
|
|
|
+ {
|
|
|
+ decimal idealValue = (targetSum - currentSum) / remainingSlots;
|
|
|
+ var best = otherCandidates.OrderBy(p => Math.Abs(p.TorsChkValue - idealValue)).First();
|
|
|
+
|
|
|
+ layer.Products.Add(best);
|
|
|
+ usedProducts.Add(best.Id);
|
|
|
+ currentSum += best.TorsChkValue;
|
|
|
+ otherCandidates.Remove(best);
|
|
|
+ remainingSlots--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 随机目标导向装箱
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo RandomTargetOrientedPacking()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 分类并随机打乱
|
|
|
+ var extremeHigh = availableProducts
|
|
|
+ .Where(p => p.TorsChkValue >= _dynamicHighThreshold)
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var extremeLow = availableProducts
|
|
|
+ .Where(p => p.TorsChkValue <= _dynamicLowThreshold)
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var normal = availableProducts
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedProducts = new HashSet<long>();
|
|
|
+ decimal boxTargetSum = _constraints.X;
|
|
|
+ decimal currentBoxSum = 0;
|
|
|
+
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+
|
|
|
+ // 计算当前层的目标
|
|
|
+ decimal remainingTarget = boxTargetSum - currentBoxSum;
|
|
|
+ int remainingLayers = LAYERS_PER_BOX - layerIndex;
|
|
|
+ decimal layerTarget = remainingTarget / remainingLayers;
|
|
|
+
|
|
|
+ // 确保层目标在允许范围内
|
|
|
+ layerTarget = Math.Max(_constraints.MinLayerTotal, Math.Min(_constraints.MaxLayerTotal, layerTarget));
|
|
|
+
|
|
|
+ decimal currentLayerSum = 0;
|
|
|
+
|
|
|
+ // 随机决定是否使用极值对
|
|
|
+ if (extremeHigh.Any(p => !usedProducts.Contains(p.Id)) &&
|
|
|
+ extremeLow.Any(p => !usedProducts.Contains(p.Id)) &&
|
|
|
+ _random.NextDouble() < 0.4)
|
|
|
+ {
|
|
|
+ var high = extremeHigh.FirstOrDefault(p => !usedProducts.Contains(p.Id));
|
|
|
+ var low = extremeLow.FirstOrDefault(p => !usedProducts.Contains(p.Id));
|
|
|
+
|
|
|
+ if (high != null && low != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ usedProducts.Add(high.Id);
|
|
|
+ usedProducts.Add(low.Id);
|
|
|
+ currentLayerSum += high.TorsChkValue + low.TorsChkValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 随机但目标导向地填充剩余位置
|
|
|
+ var normalShuffled = normal
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ while (layer.Products.Count < PRODUCTS_PER_LAYER && normalShuffled.Any())
|
|
|
+ {
|
|
|
+ decimal remaining = layerTarget - currentLayerSum;
|
|
|
+ decimal slotsLeft = PRODUCTS_PER_LAYER - layer.Products.Count;
|
|
|
+ decimal idealValue = remaining / slotsLeft;
|
|
|
+
|
|
|
+ // 从打乱的列表中选择相对接近理想值的产品
|
|
|
+ var candidates = normalShuffled
|
|
|
+ .Take(Math.Min(30, normalShuffled.Count))
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - idealValue))
|
|
|
+ .Take(5)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (candidates.Any())
|
|
|
+ {
|
|
|
+ var selected = candidates[_random.Next(candidates.Count)];
|
|
|
+ layer.Products.Add(selected);
|
|
|
+ usedProducts.Add(selected.Id);
|
|
|
+ currentLayerSum += selected.TorsChkValue;
|
|
|
+ normalShuffled.Remove(selected);
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (layer.Products.Count != PRODUCTS_PER_LAYER) return null;
|
|
|
+
|
|
|
+ if (!layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold)) return null;
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ currentBoxSum += layer.TotalTorsion;
|
|
|
+ }
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 随机极值装箱(严格的极值成对)
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo RandomExtremeValuePacking()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 分类并打乱
|
|
|
+ var extremeHigh = availableProducts
|
|
|
+ .Where(p => p.TorsChkValue >= _dynamicHighThreshold)
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var extremeLow = availableProducts
|
|
|
+ .Where(p => p.TorsChkValue <= _dynamicLowThreshold)
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var normalProducts = availableProducts
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedProducts = new HashSet<long>();
|
|
|
+
|
|
|
+ // 计算极值对数量
|
|
|
+ int extremePairs = Math.Min(Math.Min(extremeHigh.Count, extremeLow.Count), _random.Next(0, LAYERS_PER_BOX + 1));
|
|
|
+
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+
|
|
|
+ // 如果还有极值对要分配,且这一层被选中
|
|
|
+ if (extremePairs > 0 && _random.NextDouble() < (double)extremePairs / (LAYERS_PER_BOX - layerIndex))
|
|
|
+ {
|
|
|
+ // 添加一对极值
|
|
|
+ var high = extremeHigh.FirstOrDefault(p => !usedProducts.Contains(p.Id));
|
|
|
+ var low = extremeLow.FirstOrDefault(p => !usedProducts.Contains(p.Id));
|
|
|
+
|
|
|
+ if (high != null && low != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ usedProducts.Add(high.Id);
|
|
|
+ usedProducts.Add(low.Id);
|
|
|
+ extremePairs--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充剩余位置,只使用普通产品
|
|
|
+ var allAvailable = normalProducts
|
|
|
+ .Where(p => !usedProducts.Contains(p.Id))
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ while (layer.Products.Count < PRODUCTS_PER_LAYER && allAvailable.Any())
|
|
|
+ {
|
|
|
+ var product = allAvailable.First();
|
|
|
+ layer.Products.Add(product);
|
|
|
+ usedProducts.Add(product.Id);
|
|
|
+ allAvailable.RemoveAt(0);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (layer.Products.Count != PRODUCTS_PER_LAYER) return null;
|
|
|
+
|
|
|
+ // 验证层的极值成对约束
|
|
|
+ if (!layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold)) return null;
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ }
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 随机快速装箱
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo RandomQuickPacking()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 分类产品
|
|
|
+ var extremeHigh = availableProducts.Where(p => p.TorsChkValue >= _dynamicHighThreshold).ToList();
|
|
|
+ var extremeLow = availableProducts.Where(p => p.TorsChkValue <= _dynamicLowThreshold).ToList();
|
|
|
+ var normal = availableProducts.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold).ToList();
|
|
|
+
|
|
|
+ var shuffled = normal.OrderBy(x => _random.Next()).ToList();
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var usedIndices = new HashSet<int>();
|
|
|
+
|
|
|
+ for (int layerIndex = 0; layerIndex < LAYERS_PER_BOX; layerIndex++)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+ decimal targetSum = _constraints.C;
|
|
|
+ decimal currentSum = 0;
|
|
|
+
|
|
|
+ // 随机决定是否使用极值对
|
|
|
+ if (extremeHigh.Any(p => !usedIndices.Contains(availableProducts.IndexOf(p))) &&
|
|
|
+ extremeLow.Any(p => !usedIndices.Contains(availableProducts.IndexOf(p))) &&
|
|
|
+ _random.NextDouble() < 0.3)
|
|
|
+ {
|
|
|
+ // 添加一对极值
|
|
|
+ var high = extremeHigh.FirstOrDefault(p => !usedIndices.Contains(availableProducts.IndexOf(p)));
|
|
|
+ var low = extremeLow.FirstOrDefault(p => !usedIndices.Contains(availableProducts.IndexOf(p)));
|
|
|
+
|
|
|
+ if (high != null && low != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ usedIndices.Add(availableProducts.IndexOf(high));
|
|
|
+ usedIndices.Add(availableProducts.IndexOf(low));
|
|
|
+ currentSum += high.TorsChkValue + low.TorsChkValue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充剩余位置
|
|
|
+ for (int i = layer.Products.Count; i < PRODUCTS_PER_LAYER; i++)
|
|
|
+ {
|
|
|
+ decimal remaining = targetSum - currentSum;
|
|
|
+ decimal slotsLeft = PRODUCTS_PER_LAYER - i;
|
|
|
+ decimal idealValue = remaining / slotsLeft;
|
|
|
+
|
|
|
+ int bestIndex = -1;
|
|
|
+ decimal bestDiff = decimal.MaxValue;
|
|
|
+
|
|
|
+ for (int j = 0; j < shuffled.Count; j++)
|
|
|
+ {
|
|
|
+ int actualIndex = availableProducts.IndexOf(shuffled[j]);
|
|
|
+ if (usedIndices.Contains(actualIndex)) continue;
|
|
|
+
|
|
|
+ decimal diff = Math.Abs(shuffled[j].TorsChkValue - idealValue);
|
|
|
+ if (diff < bestDiff)
|
|
|
+ {
|
|
|
+ bestDiff = diff;
|
|
|
+ bestIndex = j;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (bestIndex == -1) return null;
|
|
|
+
|
|
|
+ layer.Products.Add(shuffled[bestIndex]);
|
|
|
+ usedIndices.Add(availableProducts.IndexOf(shuffled[bestIndex]));
|
|
|
+ currentSum += shuffled[bestIndex].TorsChkValue;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!layer.IsValid(_constraints))
|
|
|
+ {
|
|
|
+ if (!QuickAdjustLayer(layer, shuffled, usedIndices)) return null;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 验证层的极值成对约束
|
|
|
+ if (!layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold)) return null;
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!CheckSolderConstraints(box)) return null;
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 快速调整层
|
|
|
+ /// </summary>
|
|
|
+ private bool QuickAdjustLayer(LayerPackingLayerInfo layer, List<LayerPackingProduct> allProducts, HashSet<int> usedIndices)
|
|
|
+ {
|
|
|
+ decimal targetSum = _constraints.C;
|
|
|
+ decimal currentSum = layer.TotalTorsion;
|
|
|
+ decimal diff = currentSum - targetSum;
|
|
|
+
|
|
|
+ if (Math.Abs(diff) <= _constraints.C1)
|
|
|
+ return true;
|
|
|
+
|
|
|
+ // 只调整非极值产品
|
|
|
+ var nonExtremeInLayer = layer.Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (!nonExtremeInLayer.Any())
|
|
|
+ return false;
|
|
|
+
|
|
|
+ var worstProduct = diff > 0
|
|
|
+ ? nonExtremeInLayer.OrderByDescending(p => p.TorsChkValue).First()
|
|
|
+ : nonExtremeInLayer.OrderBy(p => p.TorsChkValue).First();
|
|
|
+
|
|
|
+ decimal idealReplacement = targetSum - (currentSum - worstProduct.TorsChkValue);
|
|
|
+
|
|
|
+ for (int i = 0; i < allProducts.Count; i++)
|
|
|
+ {
|
|
|
+ if (usedIndices.Contains(i)) continue;
|
|
|
+
|
|
|
+ var candidate = allProducts[i];
|
|
|
+
|
|
|
+ // 确保替换品也是非极值产品
|
|
|
+ if (candidate.TorsChkValue <= _dynamicLowThreshold || candidate.TorsChkValue >= _dynamicHighThreshold)
|
|
|
+ continue;
|
|
|
+
|
|
|
+ decimal newSum = currentSum - worstProduct.TorsChkValue + candidate.TorsChkValue;
|
|
|
+
|
|
|
+ if (Math.Abs(newSum - targetSum) < Math.Abs(diff))
|
|
|
+ {
|
|
|
+ int layerIndex = layer.Products.IndexOf(worstProduct);
|
|
|
+ int oldIndex = allProducts.IndexOf(worstProduct);
|
|
|
+
|
|
|
+ layer.Products[layerIndex] = candidate;
|
|
|
+ usedIndices.Remove(oldIndex);
|
|
|
+ usedIndices.Add(i);
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 检查焊点约束
|
|
|
+ /// </summary>
|
|
|
+ private bool CheckSolderConstraints(LayerPackingBoxInfo box)
|
|
|
+ {
|
|
|
+ var constraintType = _constraints.GetSolderConstraintType();
|
|
|
+
|
|
|
+ switch (constraintType)
|
|
|
+ {
|
|
|
+ case SolderConstraintType.NoConstraint:
|
|
|
+ return true;
|
|
|
+
|
|
|
+ case SolderConstraintType.OnlyProductCount:
|
|
|
+ return box.SolderProductCount <= _constraints.SolderMaxCount;
|
|
|
+
|
|
|
+ case SolderConstraintType.BothConstraints:
|
|
|
+ return box.SolderProductCount <= _constraints.SolderMaxCount &&
|
|
|
+ box.TotalSolderJoints <= _constraints.PerSolderMaxCount;
|
|
|
+
|
|
|
+ default:
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 检查并修复焊点约束
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱信息</param>
|
|
|
+ /// <param name="allProducts">所有可用产品</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private bool CheckAndFixSolderConstraints(LayerPackingBoxInfo box, List<LayerPackingProduct> allProducts)
|
|
|
+ {
|
|
|
+ var constraintType = _constraints.GetSolderConstraintType();
|
|
|
+
|
|
|
+ if (constraintType == SolderConstraintType.NoConstraint) return true;
|
|
|
+
|
|
|
+ if (box.SolderProductCount > _constraints.SolderMaxCount)
|
|
|
+ {
|
|
|
+ return FixExcessSolderProducts(box, allProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (constraintType == SolderConstraintType.BothConstraints && box.TotalSolderJoints > _constraints.PerSolderMaxCount)
|
|
|
+ {
|
|
|
+ return FixExcessSolderJoints(box, allProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 修复过多的焊点盘
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱信息</param>
|
|
|
+ /// <param name="allProducts">所有可用产品</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private bool FixExcessSolderProducts(LayerPackingBoxInfo box, List<LayerPackingProduct> allProducts)
|
|
|
+ {
|
|
|
+ var usedIds = box.Layers.SelectMany(l => l.Products.Select(p => p.Id)).ToHashSet();
|
|
|
+ int excess = box.SolderProductCount - _constraints.SolderMaxCount;
|
|
|
+
|
|
|
+ // 获取所有焊点盘,但排除极值产品
|
|
|
+ var solderProducts = box.Layers
|
|
|
+ .SelectMany((layer, layerIndex) => layer.Products
|
|
|
+ .Select((product, productIndex) => new { Layer = layer, Product = product, LayerIndex = layerIndex, ProductIndex = productIndex }))
|
|
|
+ .Where(x => x.Product.IsSolderProduct)
|
|
|
+ .Where(x => x.Product.TorsChkValue > _dynamicLowThreshold && x.Product.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ foreach (var item in solderProducts)
|
|
|
+ {
|
|
|
+ if (excess <= 0) break;
|
|
|
+
|
|
|
+ var replacement = allProducts
|
|
|
+ .Where(p => !p.IsSolderProduct && !usedIds.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .Where(p =>
|
|
|
+ {
|
|
|
+ var newSum = item.Layer.TotalTorsion - item.Product.TorsChkValue + p.TorsChkValue;
|
|
|
+ return newSum >= _constraints.MinLayerTotal && newSum <= _constraints.MaxLayerTotal;
|
|
|
+ })
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - item.Product.TorsChkValue))
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ if (replacement != null)
|
|
|
+ {
|
|
|
+ item.Layer.Products[item.ProductIndex] = replacement;
|
|
|
+ usedIds.Remove(item.Product.Id);
|
|
|
+ usedIds.Add(replacement.Id);
|
|
|
+ excess--;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return excess <= 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 修复过多的焊点总数
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱信息</param>
|
|
|
+ /// <param name="allProducts">所有可用产品</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private bool FixExcessSolderJoints(LayerPackingBoxInfo box, List<LayerPackingProduct> allProducts)
|
|
|
+ {
|
|
|
+ var usedIds = box.Layers.SelectMany(l => l.Products.Select(p => p.Id)).ToHashSet();
|
|
|
+ decimal excessJoints = box.TotalSolderJoints - _constraints.PerSolderMaxCount;
|
|
|
+
|
|
|
+ // 获取所有2焊点产品,但排除极值产品
|
|
|
+ var twoJointProducts = box.Layers
|
|
|
+ .SelectMany((layer, layerIndex) => layer.Products
|
|
|
+ .Select((product, productIndex) => new { Layer = layer, Product = product, LayerIndex = layerIndex, ProductIndex = productIndex }))
|
|
|
+ .Where(x => x.Product.SolderCount == 2)
|
|
|
+ .Where(x => x.Product.TorsChkValue > _dynamicLowThreshold && x.Product.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ foreach (var item in twoJointProducts)
|
|
|
+ {
|
|
|
+ if (excessJoints <= 0) break;
|
|
|
+
|
|
|
+ var replacement = allProducts
|
|
|
+ .Where(p => p.SolderCount < 2 && !usedIds.Contains(p.Id))
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .Where(p =>
|
|
|
+ {
|
|
|
+ var newSum = item.Layer.TotalTorsion - item.Product.TorsChkValue + p.TorsChkValue;
|
|
|
+ return newSum >= _constraints.MinLayerTotal && newSum <= _constraints.MaxLayerTotal;
|
|
|
+ })
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - item.Product.TorsChkValue))
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ if (replacement != null)
|
|
|
+ {
|
|
|
+ item.Layer.Products[item.ProductIndex] = replacement;
|
|
|
+ usedIds.Remove(item.Product.Id);
|
|
|
+ usedIds.Add(replacement.Id);
|
|
|
+ excessJoints -= (item.Product.SolderCount - replacement.SolderCount);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return excessJoints <= 0;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 激进搜索策略
|
|
|
+ /// </summary>
|
|
|
+ private LayerPackingBoxInfo AggressiveSearch()
|
|
|
+ {
|
|
|
+ var availableProducts = _productPool.Where(p => !p.IsUsed).ToList();
|
|
|
+ if (availableProducts.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 放宽约束进行搜索
|
|
|
+ var relaxedConstraints = new LayerPackingConstraints
|
|
|
+ {
|
|
|
+ P = _constraints.P,
|
|
|
+ P1 = _constraints.P1,
|
|
|
+ X = _constraints.X,
|
|
|
+ X1 = _constraints.X1 * 1.2m, // 放宽20%
|
|
|
+ C = _constraints.C,
|
|
|
+ C1 = _constraints.C1 * 1.2m, // 放宽20%
|
|
|
+ SolderMaxCount = _constraints.SolderMaxCount,
|
|
|
+ PerSolderMaxCount = _constraints.PerSolderMaxCount,
|
|
|
+ ProductsPerBox = _constraints.ProductsPerBox
|
|
|
+ };
|
|
|
+
|
|
|
+ // 尝试多种组合
|
|
|
+ for (int attempt = 0; attempt < 10; attempt++)
|
|
|
+ {
|
|
|
+ var candidates = availableProducts.OrderBy(x => _random.Next()).Take(Math.Min(150, availableProducts.Count)).ToList();
|
|
|
+
|
|
|
+ var box = TryBuildBox(candidates, relaxedConstraints);
|
|
|
+
|
|
|
+ if (box != null && box.IsValid(_constraints) && box.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold)) return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 最后尝试:使用模拟退火
|
|
|
+ return LastResortSimulatedAnnealing(availableProducts);
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 尝试构建箱子
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="candidates">当前可用产品</param>
|
|
|
+ /// <param name="constraints">约束规则</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingBoxInfo TryBuildBox(List<LayerPackingProduct> candidates, LayerPackingConstraints constraints)
|
|
|
+ {
|
|
|
+ if (candidates.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var used = new HashSet<long>();
|
|
|
+
|
|
|
+ // 分类产品
|
|
|
+ var extremeHigh = candidates.Where(p => p.TorsChkValue >= _dynamicHighThreshold).ToList();
|
|
|
+ var extremeLow = candidates.Where(p => p.TorsChkValue <= _dynamicLowThreshold).ToList();
|
|
|
+ var normal = candidates.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold).ToList();
|
|
|
+
|
|
|
+ for (int i = 0; i < LAYERS_PER_BOX; i++)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+
|
|
|
+ // 随机决定是否使用极值对
|
|
|
+ if (extremeHigh.Any(p => !used.Contains(p.Id)) && extremeLow.Any(p => !used.Contains(p.Id)) && _random.NextDouble() < 0.5)
|
|
|
+ {
|
|
|
+ var high = extremeHigh.FirstOrDefault(p => !used.Contains(p.Id));
|
|
|
+ var low = extremeLow.FirstOrDefault(p => !used.Contains(p.Id));
|
|
|
+
|
|
|
+ if (high != null && low != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ used.Add(high.Id);
|
|
|
+ used.Add(low.Id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 填充剩余位置
|
|
|
+ for (int j = layer.Products.Count; j < PRODUCTS_PER_LAYER; j++)
|
|
|
+ {
|
|
|
+ var available = normal.Where(c => !used.Contains(c.Id)).ToList();
|
|
|
+ if (!available.Any()) return null;
|
|
|
+
|
|
|
+ decimal currentSum = layer.TotalTorsion;
|
|
|
+ decimal targetValue = (constraints.C - currentSum) / (PRODUCTS_PER_LAYER - j);
|
|
|
+
|
|
|
+ var selected = available.OrderBy(p => Math.Abs(p.TorsChkValue - targetValue)).First();
|
|
|
+
|
|
|
+ layer.Products.Add(selected);
|
|
|
+ used.Add(selected.Id);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!layer.IsValid(constraints)) return null;
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ }
|
|
|
+
|
|
|
+ return box.IsValid(constraints) ? box : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 最后手段:模拟退火
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="candidates">有效产品</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingBoxInfo LastResortSimulatedAnnealing(List<LayerPackingProduct> candidates)
|
|
|
+ {
|
|
|
+ if (candidates.Count < PRODUCTS_PER_BOX) return null;
|
|
|
+
|
|
|
+ // 生成初始解
|
|
|
+ var currentBox = GenerateInitialSolution(candidates);
|
|
|
+ if (currentBox == null) return null;
|
|
|
+
|
|
|
+ var bestBox = currentBox;
|
|
|
+ var bestScore = CalculateBoxScore(currentBox);
|
|
|
+
|
|
|
+ double temperature = 200.0; // 提高初始温度
|
|
|
+ double coolingRate = 0.98; // 减缓冷却速度
|
|
|
+ int iterations = 2000; // 增加迭代次数
|
|
|
+
|
|
|
+ for (int i = 0; i < iterations; i++)
|
|
|
+ {
|
|
|
+ var neighborBox = GenerateNeighbor(currentBox, candidates);
|
|
|
+ if (neighborBox == null) continue;
|
|
|
+
|
|
|
+ // 验证极值成对约束
|
|
|
+ if (!neighborBox.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold)) continue;
|
|
|
+
|
|
|
+ var neighborScore = CalculateBoxScore(neighborBox);
|
|
|
+
|
|
|
+ if (neighborScore < bestScore || AcceptanceProbability(bestScore, neighborScore, temperature) > _random.NextDouble())
|
|
|
+ {
|
|
|
+ currentBox = neighborBox;
|
|
|
+
|
|
|
+ if (neighborScore < bestScore && neighborBox.IsValid(_constraints))
|
|
|
+ {
|
|
|
+ bestScore = neighborScore;
|
|
|
+ bestBox = neighborBox;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ temperature *= coolingRate;
|
|
|
+ }
|
|
|
+
|
|
|
+ return bestBox.IsValid(_constraints) && bestBox.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold) ? bestBox : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成初始解
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="candidates">有效产品</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingBoxInfo GenerateInitialSolution(List<LayerPackingProduct> candidates)
|
|
|
+ {
|
|
|
+ var box = new LayerPackingBoxInfo();
|
|
|
+ var used = new HashSet<long>();
|
|
|
+
|
|
|
+ // 分类产品
|
|
|
+ var extremeHigh = candidates.Where(p => p.TorsChkValue >= _dynamicHighThreshold).ToList();
|
|
|
+ var extremeLow = candidates.Where(p => p.TorsChkValue <= _dynamicLowThreshold).ToList();
|
|
|
+ var normal = candidates.Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .OrderBy(p => Math.Abs(p.TorsChkValue - _constraints.C / PRODUCTS_PER_LAYER))
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ for (int i = 0; i < LAYERS_PER_BOX; i++)
|
|
|
+ {
|
|
|
+ var layer = new LayerPackingLayerInfo();
|
|
|
+
|
|
|
+ // 每隔一层使用极值对
|
|
|
+ if (i % 2 == 0 && extremeHigh.Any(p => !used.Contains(p.Id)) && extremeLow.Any(p => !used.Contains(p.Id)))
|
|
|
+ {
|
|
|
+ var high = extremeHigh.FirstOrDefault(p => !used.Contains(p.Id));
|
|
|
+ var low = extremeLow.FirstOrDefault(p => !used.Contains(p.Id));
|
|
|
+
|
|
|
+ if (high != null && low != null)
|
|
|
+ {
|
|
|
+ layer.Products.Add(high);
|
|
|
+ layer.Products.Add(low);
|
|
|
+ used.Add(high.Id);
|
|
|
+ used.Add(low.Id);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 选择普通产品
|
|
|
+ var selected = normal
|
|
|
+ .Where(p => !used.Contains(p.Id))
|
|
|
+ .Take(PRODUCTS_PER_LAYER - layer.Products.Count)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (selected.Count < PRODUCTS_PER_LAYER - layer.Products.Count) return null;
|
|
|
+
|
|
|
+ foreach (var product in selected)
|
|
|
+ {
|
|
|
+ layer.Products.Add(product);
|
|
|
+ used.Add(product.Id);
|
|
|
+ }
|
|
|
+
|
|
|
+ box.Layers.Add(layer);
|
|
|
+ }
|
|
|
+
|
|
|
+ return box;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 生成邻域解
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="currentBox">当前箱信息</param>
|
|
|
+ /// <param name="allCandidates">所有产品</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private LayerPackingBoxInfo GenerateNeighbor(LayerPackingBoxInfo currentBox, List<LayerPackingProduct> allCandidates)
|
|
|
+ {
|
|
|
+ var newBox = new LayerPackingBoxInfo();
|
|
|
+
|
|
|
+ // 深拷贝
|
|
|
+ foreach (var layer in currentBox.Layers)
|
|
|
+ {
|
|
|
+ var newLayer = new LayerPackingLayerInfo();
|
|
|
+ newLayer.Products.AddRange(layer.Products);
|
|
|
+ newBox.Layers.Add(newLayer);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 执行随机操作
|
|
|
+ int operation = _random.Next(4);
|
|
|
+
|
|
|
+ switch (operation)
|
|
|
+ {
|
|
|
+ case 0: // 层内交换(保持极值成对)
|
|
|
+ SwapWithinLayerSafe(newBox);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 1: // 层间交换(保持极值成对)
|
|
|
+ SwapBetweenLayersSafe(newBox);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 2: // 替换产品(保持极值成对)
|
|
|
+ ReplaceProductSafe(newBox, allCandidates);
|
|
|
+ break;
|
|
|
+
|
|
|
+ case 3: // 批量调整
|
|
|
+ BatchAdjustSafe(newBox, allCandidates);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ return newBox;
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 安全的层内交换(保持极值成对) --已无实际意义
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱信息</param>
|
|
|
+ [Obsolete]
|
|
|
+ private void SwapWithinLayerSafe(LayerPackingBoxInfo box)
|
|
|
+ {
|
|
|
+ int layerIndex = _random.Next(LAYERS_PER_BOX);
|
|
|
+ var layer = box.Layers[layerIndex];
|
|
|
+
|
|
|
+ if (layer.Products.Count >= 2)
|
|
|
+ {
|
|
|
+ // 只交换非极值产品
|
|
|
+ var nonExtremeProducts = layer.Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (nonExtremeProducts.Count >= 2)
|
|
|
+ {
|
|
|
+ var indices = nonExtremeProducts.Select(p => layer.Products.IndexOf(p)).ToList();
|
|
|
+ int i = indices[_random.Next(indices.Count)];
|
|
|
+ int j = indices[_random.Next(indices.Count)];
|
|
|
+
|
|
|
+ if (i != j)
|
|
|
+ {
|
|
|
+ var temp = layer.Products[i];
|
|
|
+ layer.Products[i] = layer.Products[j];
|
|
|
+ layer.Products[j] = temp;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 安全的层间交换(保持极值成对)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱信息</param>
|
|
|
+ private void SwapBetweenLayersSafe(LayerPackingBoxInfo box)
|
|
|
+ {
|
|
|
+ int layer1 = _random.Next(LAYERS_PER_BOX);
|
|
|
+ int layer2 = _random.Next(LAYERS_PER_BOX);
|
|
|
+
|
|
|
+ if (layer1 != layer2)
|
|
|
+ {
|
|
|
+ // 获取两层的非极值产品
|
|
|
+ var nonExtreme1 = box.Layers[layer1].Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+ var nonExtreme2 = box.Layers[layer2].Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (nonExtreme1.Any() && nonExtreme2.Any())
|
|
|
+ {
|
|
|
+ var product1 = nonExtreme1[_random.Next(nonExtreme1.Count)];
|
|
|
+ var product2 = nonExtreme2[_random.Next(nonExtreme2.Count)];
|
|
|
+
|
|
|
+ int pos1 = box.Layers[layer1].Products.IndexOf(product1);
|
|
|
+ int pos2 = box.Layers[layer2].Products.IndexOf(product2);
|
|
|
+
|
|
|
+ box.Layers[layer1].Products[pos1] = product2;
|
|
|
+ box.Layers[layer2].Products[pos2] = product1;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 安全的产品替换(保持极值成对)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱信息 </param>
|
|
|
+ /// <param name="allCandidates">所有产品</param>
|
|
|
+ private void ReplaceProductSafe(LayerPackingBoxInfo box, List<LayerPackingProduct> allCandidates)
|
|
|
+ {
|
|
|
+ var usedIds = box.Layers.SelectMany(l => l.Products.Select(p => p.Id)).ToHashSet();
|
|
|
+ var unused = allCandidates
|
|
|
+ .Where(c => !usedIds.Contains(c.Id))
|
|
|
+ .Where(c => c.TorsChkValue > _dynamicLowThreshold && c.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (unused.Any())
|
|
|
+ {
|
|
|
+ int layerIndex = _random.Next(LAYERS_PER_BOX);
|
|
|
+ var nonExtremeInLayer = box.Layers[layerIndex].Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ if (nonExtremeInLayer.Any())
|
|
|
+ {
|
|
|
+ var oldProduct = nonExtremeInLayer[_random.Next(nonExtremeInLayer.Count)];
|
|
|
+ var newProduct = unused[_random.Next(unused.Count)];
|
|
|
+ int productIndex = box.Layers[layerIndex].Products.IndexOf(oldProduct);
|
|
|
+
|
|
|
+ box.Layers[layerIndex].Products[productIndex] = newProduct;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 安全的批量调整(保持极值成对)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">箱</param>
|
|
|
+ /// <param name="allCandidates"></param>
|
|
|
+ private void BatchAdjustSafe(LayerPackingBoxInfo box, List<LayerPackingProduct> allCandidates)
|
|
|
+ {
|
|
|
+ // 找出偏差最大的层
|
|
|
+ LayerPackingLayerInfo worstLayer = null;
|
|
|
+ decimal maxDeviation = 0;
|
|
|
+
|
|
|
+ foreach (var layer in box.Layers)
|
|
|
+ {
|
|
|
+ var deviation = layer.GetDeviationFromTarget(_constraints.C);
|
|
|
+ if (deviation > maxDeviation)
|
|
|
+ {
|
|
|
+ maxDeviation = deviation;
|
|
|
+ worstLayer = layer;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ if (worstLayer != null && maxDeviation > _constraints.C1 * 0.5m)
|
|
|
+ {
|
|
|
+ // 尝试改善这一层,只替换非极值产品
|
|
|
+ var usedIds = box.Layers.SelectMany(l => l.Products.Select(p => p.Id)).ToHashSet();
|
|
|
+ var unused = allCandidates
|
|
|
+ .Where(c => !usedIds.Contains(c.Id))
|
|
|
+ .Where(c => c.TorsChkValue > _dynamicLowThreshold && c.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ var nonExtremeInLayer = worstLayer.Products
|
|
|
+ .Where(p => p.TorsChkValue > _dynamicLowThreshold && p.TorsChkValue < _dynamicHighThreshold)
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ // 替换2-3个非极值产品
|
|
|
+ int replacements = Math.Min(3, Math.Min(unused.Count, nonExtremeInLayer.Count));
|
|
|
+ for (int i = 0; i < replacements; i++)
|
|
|
+ {
|
|
|
+ if (unused.Any() && nonExtremeInLayer.Any())
|
|
|
+ {
|
|
|
+ var oldProduct = nonExtremeInLayer[_random.Next(nonExtremeInLayer.Count)];
|
|
|
+ int index = worstLayer.Products.IndexOf(oldProduct);
|
|
|
+
|
|
|
+ // 选择能改善偏差的产品
|
|
|
+ var currentSum = worstLayer.TotalTorsion;
|
|
|
+ var targetSum = _constraints.C;
|
|
|
+
|
|
|
+ var replacement = unused
|
|
|
+ .Where(p => Math.Abs(currentSum - oldProduct.TorsChkValue + p.TorsChkValue - targetSum) < maxDeviation)
|
|
|
+ .OrderBy(x => _random.Next())
|
|
|
+ .FirstOrDefault();
|
|
|
+
|
|
|
+ if (replacement != null)
|
|
|
+ {
|
|
|
+ worstLayer.Products[index] = replacement;
|
|
|
+ unused.Remove(replacement);
|
|
|
+ unused.Add(oldProduct);
|
|
|
+ nonExtremeInLayer.Remove(oldProduct);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 接受概率计算
|
|
|
+ /// </summary>
|
|
|
+ private double AcceptanceProbability(decimal currentScore, decimal newScore, double temperature)
|
|
|
+ {
|
|
|
+ if (newScore < currentScore) return 1.0;
|
|
|
+
|
|
|
+ return Math.Exp(-((double)(newScore - currentScore) / temperature));
|
|
|
+ }
|
|
|
+
|
|
|
+ /// <summary>
|
|
|
+ /// 计算箱子的评分(强调接近目标值)
|
|
|
+ /// </summary>
|
|
|
+ /// <param name="box">需计算的箱信息</param>
|
|
|
+ /// <returns></returns>
|
|
|
+ private decimal CalculateBoxScore(LayerPackingBoxInfo box)
|
|
|
+ {
|
|
|
+ decimal score = 0;
|
|
|
+
|
|
|
+ // 基础分:箱子是否满足约束
|
|
|
+ if (!box.IsValid(_constraints))
|
|
|
+ {
|
|
|
+ score += 10000; // 不满足约束的严重惩罚
|
|
|
+ }
|
|
|
+
|
|
|
+ // 极值成对约束检查
|
|
|
+ if (!box.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ score += 5000; // 不满足极值成对约束的惩罚
|
|
|
+ }
|
|
|
+
|
|
|
+ // 整箱偏差(提高权重,强调接近目标值)
|
|
|
+ var boxDiff = box.GetDeviationFromTarget(_constraints.X);
|
|
|
+ score += boxDiff * 5; // 提高权重
|
|
|
+
|
|
|
+ // 整箱偏差百分比惩罚
|
|
|
+ decimal boxDeviationPercent = boxDiff / _constraints.X * 100;
|
|
|
+ if (boxDeviationPercent > 5) // 偏差超过5%
|
|
|
+ {
|
|
|
+ score += boxDeviationPercent * 10;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 每层偏差(提高权重)
|
|
|
+ foreach (var layer in box.Layers)
|
|
|
+ {
|
|
|
+ var layerDiff = layer.GetDeviationFromTarget(_constraints.C);
|
|
|
+ score += layerDiff * 2; // 提高权重
|
|
|
+
|
|
|
+ // 层偏差百分比惩罚
|
|
|
+ decimal layerDeviationPercent = layerDiff / _constraints.C * 100;
|
|
|
+ if (layerDeviationPercent > 5) // 偏差超过5%
|
|
|
+ {
|
|
|
+ score += layerDeviationPercent * 5;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 层约束违反惩罚
|
|
|
+ if (!layer.IsValid(_constraints))
|
|
|
+ {
|
|
|
+ score += 50;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 层的极值成对约束违反惩罚
|
|
|
+ if (!layer.HasValidExtremePair(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ score += 100;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 焊点约束检查
|
|
|
+ var constraintType = _constraints.GetSolderConstraintType();
|
|
|
+ switch (constraintType)
|
|
|
+ {
|
|
|
+ case SolderConstraintType.OnlyProductCount:
|
|
|
+ if (box.SolderProductCount > _constraints.SolderMaxCount)
|
|
|
+ {
|
|
|
+ score += 100 * (box.SolderProductCount - _constraints.SolderMaxCount);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+
|
|
|
+ case SolderConstraintType.BothConstraints:
|
|
|
+ if (box.SolderProductCount > _constraints.SolderMaxCount)
|
|
|
+ {
|
|
|
+ score += 100 * (box.SolderProductCount - _constraints.SolderMaxCount);
|
|
|
+ }
|
|
|
+ if (box.TotalSolderJoints > _constraints.PerSolderMaxCount)
|
|
|
+ {
|
|
|
+ score += 50 * (box.TotalSolderJoints - _constraints.PerSolderMaxCount);
|
|
|
+ }
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 层间均衡性(降低权重,因为现在更关注接近目标值)
|
|
|
+ var layerTotals = box.Layers.Select(l => l.TotalTorsion).ToList();
|
|
|
+ var avgLayer = layerTotals.Average();
|
|
|
+ var variance = layerTotals.Sum(t => Math.Pow((double)(t - avgLayer), 2)) / LAYERS_PER_BOX;
|
|
|
+ score += (decimal)variance * 0.05m; // 降低权重
|
|
|
+
|
|
|
+ // 极值产品使用奖励(成对使用)
|
|
|
+ var extremePairCount = box.Layers.Count(l =>
|
|
|
+ {
|
|
|
+ var highCount = l.Products.Count(p => p.TorsChkValue >= _dynamicHighThreshold);
|
|
|
+ var lowCount = l.Products.Count(p => p.TorsChkValue <= _dynamicLowThreshold);
|
|
|
+ return highCount == 1 && lowCount == 1;
|
|
|
+ });
|
|
|
+
|
|
|
+ // 根据极值对数量给予奖励
|
|
|
+ if (extremePairCount > 0)
|
|
|
+ {
|
|
|
+ score -= extremePairCount * 20; // 每对极值给予奖励
|
|
|
+ }
|
|
|
+
|
|
|
+ // 成箱奖励:完全满足约束且接近目标值给予巨大奖励
|
|
|
+ if (box.IsValid(_constraints) && box.HasValidExtremePairs(_dynamicHighThreshold, _dynamicLowThreshold))
|
|
|
+ {
|
|
|
+ score *= 0.1m; // 满足所有约束的箱子评分大幅降低
|
|
|
+
|
|
|
+ // 额外奖励:非常接近目标值
|
|
|
+ if (boxDiff < _constraints.X1 * 0.1m) // 在10%偏差范围内
|
|
|
+ {
|
|
|
+ score *= 0.5m; // 大幅奖励
|
|
|
+ }
|
|
|
+ else if (boxDiff < _constraints.X1 * 0.3m) // 在30%偏差范围内
|
|
|
+ {
|
|
|
+ score *= 0.7m;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 所有层都接近目标值的额外奖励
|
|
|
+ bool allLayersClose = box.Layers.All(l => l.GetDeviationFromTarget(_constraints.C) < _constraints.C1 * 0.3m);
|
|
|
+ if (allLayersClose)
|
|
|
+ {
|
|
|
+ score *= 0.8m;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 极值对使用率额外奖励
|
|
|
+ if (extremePairCount >= 3) // 至少3层使用了极值对
|
|
|
+ {
|
|
|
+ score *= 0.95m;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return score;
|
|
|
+ }
|
|
|
+ }
|
|
|
+}
|