|  | @@ -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;
 | 
	
		
			
				|  |  | +        }
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +}
 |