369 lines
18 KiB
PowerShell
369 lines
18 KiB
PowerShell
$path = "NPP.SmartSchedue.Api/Services/Integration/Algorithms/LinearProgrammingEngine.cs"
|
||
$lines = [System.Collections.Generic.List[string]]::new()
|
||
$lines.AddRange([System.IO.File]::ReadAllLines($path))
|
||
|
||
function FindContainsIndex([string[]]$array, [string]$value, [int]$start = 0) {
|
||
for ($i = $start; $i -lt $array.Length; $i++) {
|
||
if ($array[$i].Contains($value)) { return $i }
|
||
}
|
||
return -1
|
||
}
|
||
|
||
function FindBlockIndices([string[]]$array, [int]$start) {
|
||
$brace = 0
|
||
for ($i = $start; $i -lt $array.Length; $i++) {
|
||
if ($array[$i].Contains('{')) { $brace++ }
|
||
if ($array[$i].Contains('}')) { $brace-- }
|
||
if ($brace -eq 0) { return $i }
|
||
}
|
||
return -1
|
||
}
|
||
|
||
# BuildAuxiliaryVariables adjustments
|
||
$index = FindContainsIndex $lines 'var auxiliaryVars = new Dictionary<string, Variable>();'
|
||
if ($index -ge 0) {
|
||
$lines.InsertRange($index + 1, [string[]]@(
|
||
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out var totalHistoricalTasks);',
|
||
' var avgWorkload = context.AvailablePersonnel.Count > 0',
|
||
' ? (double)(context.Tasks.Count + totalHistoricalTasks) / context.AvailablePersonnel.Count',
|
||
' : 0.0;'
|
||
))
|
||
}
|
||
$index = FindContainsIndex $lines 'var avgWorkload = (double)context.Tasks.Count / context.AvailablePersonnel.Count;'
|
||
if ($index -ge 0) { $lines.RemoveAt($index) }
|
||
$index = FindContainsIndex $lines 'var negDeviationVar = solver.MakeNumVar(0, avgWorkload, $"neg_dev_'
|
||
if ($index -ge 0) { $lines[$index] = ' var negDeviationVar = solver.MakeNumVar(0, Math.Max(0.0, avgWorkload), $"neg_dev_{personnel.Id}");' }
|
||
|
||
# Replace fairness method
|
||
$regionIndex = FindContainsIndex $lines '#region 公平性约束 - 对应遗传算法负载均衡机制'
|
||
if ($regionIndex -ge 0) {
|
||
$start = $regionIndex + 2
|
||
$end = FindContainsIndex $lines '#endregion' ($start)
|
||
if ($end -gt $start) {
|
||
$lines.RemoveRange($start, $end - $start)
|
||
$newFairness = [string[]]@(
|
||
' private void AddFairnessConstraints(',
|
||
' Solver solver,',
|
||
' Dictionary<(long TaskId, long PersonnelId), Variable> decisionVars,',
|
||
' Dictionary<string, Variable> auxiliaryVars,',
|
||
' GlobalAllocationContext context)',
|
||
' {',
|
||
' var constraintCount = 0;',
|
||
'',
|
||
' _logger.LogInformation("⚖️ 开始添加公平性约束...");',
|
||
'',
|
||
' var totalTasks = context.Tasks.Count;',
|
||
' var totalPersonnel = context.AvailablePersonnel.Count;',
|
||
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out var totalHistoricalWorkload);',
|
||
' var totalWorkload = totalTasks + totalHistoricalWorkload;',
|
||
' var avgWorkload = totalPersonnel > 0 ? (double)totalWorkload / totalPersonnel : 0.0;',
|
||
'',
|
||
' // 公平性约束1:负载均衡偏差计算',
|
||
' foreach (var personnel in context.AvailablePersonnel)',
|
||
' {',
|
||
' var workloadVarKey = $"workload_{personnel.Id}";',
|
||
' var posDevVarKey = $"pos_dev_{personnel.Id}";',
|
||
' var negDevVarKey = $"neg_dev_{personnel.Id}";',
|
||
' var historicalWorkload = historicalWorkloads.GetValueOrDefault(personnel.Id);',
|
||
'',
|
||
' if (auxiliaryVars.ContainsKey(workloadVarKey) &&',
|
||
' auxiliaryVars.ContainsKey(posDevVarKey) &&',
|
||
' auxiliaryVars.ContainsKey(negDevVarKey))',
|
||
' {',
|
||
' // 约束:currentWorkload + 历史工作量偏移 = avgWorkload + pos_deviation - neg_deviation',
|
||
' var targetWorkload = avgWorkload - historicalWorkload;',
|
||
' var balanceConstraint = solver.MakeConstraint(targetWorkload, targetWorkload,',
|
||
' $"load_balance_{personnel.Id}");',
|
||
'',
|
||
' balanceConstraint.SetCoefficient(auxiliaryVars[workloadVarKey], 1);',
|
||
' balanceConstraint.SetCoefficient(auxiliaryVars[posDevVarKey], -1);',
|
||
' balanceConstraint.SetCoefficient(auxiliaryVars[negDevVarKey], 1);',
|
||
' constraintCount++;',
|
||
' }',
|
||
' }',
|
||
'',
|
||
' // 公平性约束2:最大负载限制 - 防止极端不均衡',
|
||
' var maxTotalWorkload = Math.Ceiling(avgWorkload * 1.5);',
|
||
' foreach (var personnel in context.AvailablePersonnel)',
|
||
' {',
|
||
' var workloadVarKey = $"workload_{personnel.Id}";',
|
||
' var historicalWorkload = historicalWorkloads.GetValueOrDefault(personnel.Id);',
|
||
' if (auxiliaryVars.ContainsKey(workloadVarKey))',
|
||
' {',
|
||
' var constraint = solver.MakeConstraint(0, Math.Max(0.0, maxTotalWorkload - historicalWorkload),',
|
||
' $"max_workload_{personnel.Id}");',
|
||
' constraint.SetCoefficient(auxiliaryVars[workloadVarKey], 1);',
|
||
' constraintCount++;',
|
||
' }',
|
||
' }',
|
||
'',
|
||
' // 公平性约束3:最小人员利用率约束 - 确保不会过度集中',
|
||
' var minPersonnelUtilization = totalPersonnel == 0',
|
||
' ? 0',
|
||
' : (totalTasks > 10 ? Math.Max(3, Math.Ceiling(totalPersonnel * 0.4)) : Math.Min((double)totalPersonnel, 2));',
|
||
'',
|
||
' // 创建人员参与指示变量',
|
||
' var participationVars = new List<Variable>();',
|
||
' foreach (var personnel in context.AvailablePersonnel)',
|
||
' {',
|
||
' var participationVar = solver.MakeBoolVar($"participation_{personnel.Id}");',
|
||
' participationVars.Add(participationVar);',
|
||
'',
|
||
' // 如果人员参与,则至少分配1个任务',
|
||
' var constraint = solver.MakeConstraint(0, double.PositiveInfinity,',
|
||
' $"participation_min_{personnel.Id}");',
|
||
'',
|
||
' constraint.SetCoefficient(participationVar, -1);',
|
||
'',
|
||
' var personnelTasks = decisionVars.Where(v => v.Key.PersonnelId == personnel.Id);',
|
||
' foreach (var kvp in personnelTasks)',
|
||
' {',
|
||
' constraint.SetCoefficient(kvp.Value, 1);',
|
||
' }',
|
||
' constraintCount++;',
|
||
' }',
|
||
'',
|
||
' // 最小参与人数约束',
|
||
' var minParticipationConstraint = solver.MakeConstraint(minPersonnelUtilization, double.PositiveInfinity,',
|
||
' "min_personnel_utilization");',
|
||
'',
|
||
' foreach (var participationVar in participationVars)',
|
||
' {',
|
||
' minParticipationConstraint.SetCoefficient(participationVar, 1);',
|
||
' }',
|
||
' constraintCount++;',
|
||
'',
|
||
' _logger.LogDebug("✅ 公平性约束添加完成 - 约束数量:{ConstraintCount},平均负载:{AvgWorkload:F2},最大负载:{MaxWorkload:F2},最小参与人数:{MinParticipation}",',
|
||
' constraintCount, avgWorkload, maxTotalWorkload, minPersonnelUtilization);',
|
||
' }'
|
||
)
|
||
$lines.InsertRange($start, $newFairness)
|
||
}
|
||
}
|
||
|
||
# Update LogWorkloadAnalysis call
|
||
$index = FindContainsIndex $lines 'LogWorkloadAnalysis(workloadDistribution, context.Tasks.Count, context.AvailablePersonnel.Count);'
|
||
if ($index -ge 0) { $lines[$index] = ' LogWorkloadAnalysis(workloadDistribution, context.Tasks.Count, context.AvailablePersonnel.Count, context);' }
|
||
|
||
# ValidateSolutionQuality adjustments
|
||
$index = FindContainsIndex $lines 'var totalChecks = 0;'
|
||
if ($index -ge 0 -and -not $lines[$index + 1].Contains('BuildHistoricalWorkloadMap')) {
|
||
$lines.Insert($index + 1, ' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out _);')
|
||
}
|
||
$start = FindContainsIndex $lines '// 检查3:负载均衡评估'
|
||
if ($start -ge 0) {
|
||
$lines.RemoveRange($start, 8)
|
||
$lines.InsertRange($start, [string[]]@(
|
||
' // 检查3:负载均衡评估(纳入历史任务)',
|
||
' var currentWorkloadMap = assignments',
|
||
' .GroupBy(a => a.Value)',
|
||
' .ToDictionary(g => g.Key, g => g.Count());',
|
||
'',
|
||
' var combinedWorkloads = context.AvailablePersonnel.ToDictionary(',
|
||
' p => p.Id,',
|
||
' p => historicalWorkloads.GetValueOrDefault(p.Id) + currentWorkloadMap.GetValueOrDefault(p.Id));',
|
||
'',
|
||
' if (combinedWorkloads.Any())',
|
||
' {',
|
||
' var workloadValues = combinedWorkloads.Values.Select(v => (decimal)v).ToList();',
|
||
' var giniCoefficient = CalculateGiniCoefficient(workloadValues);',
|
||
' metrics.LoadBalanceScore = Math.Max(0.0, 1.0 - giniCoefficient);',
|
||
'',
|
||
' var utilizedCount = combinedWorkloads.Count(kv => kv.Value > 0);',
|
||
' metrics.PersonnelUtilizationRate = context.AvailablePersonnel.Count > 0',
|
||
' ? (double)utilizedCount / context.AvailablePersonnel.Count',
|
||
' : 0.0;',
|
||
' }'
|
||
))
|
||
}
|
||
|
||
# Replace LogWorkloadAnalysis method
|
||
$logStart = FindContainsIndex $lines 'private void LogWorkloadAnalysis('
|
||
if ($logStart -ge 0) {
|
||
$logEnd = FindBlockIndices $lines $logStart
|
||
if ($logEnd -ge $logStart) {
|
||
$lines.RemoveRange($logStart, $logEnd - $logStart + 1)
|
||
$newLog = [string[]]@(
|
||
' private void LogWorkloadAnalysis(',
|
||
' Dictionary<long, decimal> workloadDistribution,',
|
||
' int totalTasks,',
|
||
' int totalPersonnel,',
|
||
' GlobalAllocationContext context)',
|
||
' {',
|
||
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out _);',
|
||
'',
|
||
' if (!workloadDistribution.Any() && historicalWorkloads.Values.All(value => value == 0))',
|
||
' {',
|
||
' return;',
|
||
' }',
|
||
'',
|
||
' var combinedWorkloads = context.AvailablePersonnel.ToDictionary(',
|
||
' p => p.Id,',
|
||
' p => workloadDistribution.GetValueOrDefault(p.Id) + historicalWorkloads.GetValueOrDefault(p.Id));',
|
||
'',
|
||
' if (!combinedWorkloads.Any())',
|
||
' {',
|
||
' return;',
|
||
' }',
|
||
'',
|
||
' var combinedValues = combinedWorkloads.Values.Select(value => (decimal)value).ToList();',
|
||
' var avgWorkload = combinedValues.Average(value => (double)value);',
|
||
' var maxWorkload = combinedValues.Max();',
|
||
' var minWorkload = combinedValues.Min();',
|
||
' var utilizedCount = combinedWorkloads.Count(kv => kv.Value > 0);',
|
||
' var utilizationRate = totalPersonnel > 0 ? (double)utilizedCount / totalPersonnel : 0.0;',
|
||
'',
|
||
' _logger.LogInformation("📈 工作负载分析(含历史) - 平均:{Avg:F2},最大:{Max},最小:{Min},当前批次任务数:{CurrentTasks},人员利用率:{Util:P2},基尼系数:{Gini:F4}",',
|
||
' avgWorkload, maxWorkload, minWorkload, totalTasks, utilizationRate, CalculateGiniCoefficient(combinedValues));',
|
||
'',
|
||
' var currentDistributionLog = string.Join(", ",',
|
||
' workloadDistribution.OrderBy(kv => kv.Key)',
|
||
' .Select(kv => $"人员{kv.Key}:{kv.Value}任务"));',
|
||
' if (!string.IsNullOrWhiteSpace(currentDistributionLog))',
|
||
' {',
|
||
' _logger.LogDebug("📊 本次任务负载:{Distribution}", currentDistributionLog);',
|
||
' }',
|
||
'',
|
||
' var historicalDistributionLog = string.Join(", ",',
|
||
' historicalWorkloads.OrderBy(kv => kv.Key)',
|
||
' .Select(kv => $"人员{kv.Key}:{kv.Value}历史任务"));',
|
||
' if (!string.IsNullOrWhiteSpace(historicalDistributionLog))',
|
||
' {',
|
||
' _logger.LogDebug("🕰️ 历史任务负载:{Distribution}", historicalDistributionLog);',
|
||
' }',
|
||
'',
|
||
' var cumulativeDistributionLog = string.Join(", ",',
|
||
' combinedWorkloads.OrderBy(kv => kv.Key)',
|
||
' .Select(kv => $"人员{kv.Key}:{kv.Value}累计任务"));',
|
||
' _logger.LogDebug("⚖️ 累计任务负载:{Distribution}", cumulativeDistributionLog);',
|
||
' }'
|
||
)
|
||
$lines.InsertRange($logStart, $newLog)
|
||
}
|
||
}
|
||
|
||
# Insert helper method after 辅助方法 region line
|
||
$regionIndex = FindContainsIndex $lines '#region 辅助方法'
|
||
if ($regionIndex -ge 0) {
|
||
$lines.InsertRange($regionIndex + 1, [string[]]@(
|
||
'',
|
||
' private Dictionary<long, int> BuildHistoricalWorkloadMap(GlobalAllocationContext context, out int totalHistoricalTasks)',
|
||
' {',
|
||
' var historicalWorkloads = new Dictionary<long, int>();',
|
||
' totalHistoricalTasks = 0;',
|
||
'',
|
||
' foreach (var personnel in context.AvailablePersonnel)',
|
||
' {',
|
||
' var historyCount = 0;',
|
||
' if (context.PersonnelHistoryTasks.TryGetValue(personnel.Id, out var historyTasks) && historyTasks != null)',
|
||
' {',
|
||
' historyCount = historyTasks.Count;',
|
||
' }',
|
||
'',
|
||
' historicalWorkloads[personnel.Id] = historyCount;',
|
||
' totalHistoricalTasks += historyCount;',
|
||
' }',
|
||
'',
|
||
' return historicalWorkloads;',
|
||
' }',
|
||
''
|
||
))
|
||
}
|
||
|
||
# Update CreateBatchIntegrityConstraints block
|
||
$blockStart = FindContainsIndex $lines 'private int CreateBatchIntegrityConstraints('
|
||
if ($blockStart -ge 0) {
|
||
$blockEnd = FindBlockIndices $lines $blockStart
|
||
$lines.RemoveRange($blockStart, $blockEnd - $blockStart + 1)
|
||
$newBlock = [string[]]@(
|
||
' private int CreateBatchIntegrityConstraints(',
|
||
' Solver solver,',
|
||
' Dictionary<(long TaskId, long PersonnelId), Variable> decisionVars,',
|
||
' Dictionary<string, Variable> auxiliaryVars,',
|
||
' GlobalAllocationContext context)',
|
||
' {',
|
||
' var constraintCount = 0;',
|
||
'',
|
||
' // 1. 项目连续性约束增强',
|
||
' var projectGroups = context.Tasks',
|
||
' .GroupBy(t => ExtractProjectFromWorkOrderCode(t.WorkOrderCode))',
|
||
' .Where(g => g.Count() > 1)',
|
||
' .ToList();',
|
||
'',
|
||
' foreach (var projectGroup in projectGroups)',
|
||
' {',
|
||
' var projectTasks = projectGroup.ToList();',
|
||
' var projectCode = projectGroup.Key;',
|
||
'',
|
||
' // 创建项目一致性指示变量',
|
||
' foreach (var personnel in context.AvailablePersonnel)',
|
||
' {',
|
||
' var projectParticipationVars = projectTasks',
|
||
' .Where(task => decisionVars.ContainsKey((task.Id, personnel.Id)))',
|
||
' .Select(task => decisionVars[(task.Id, personnel.Id)])',
|
||
' .ToList();',
|
||
'',
|
||
' if (projectParticipationVars.Count > 1)',
|
||
' {',
|
||
' var participationVar = solver.MakeBoolVar(',
|
||
' $"project_participation_{projectCode}_{personnel.Id}");',
|
||
'',
|
||
' // 如果参与项目,则至少分配一个任务',
|
||
' var constraint = solver.MakeConstraint(0, double.PositiveInfinity,',
|
||
' $"project_integrity_{projectCode}_{personnel.Id}");',
|
||
'',
|
||
' constraint.SetCoefficient(participationVar, -1);',
|
||
' foreach (var taskVar in projectParticipationVars)',
|
||
' {',
|
||
' constraint.SetCoefficient(taskVar, 1);',
|
||
' }',
|
||
' constraintCount++;',
|
||
'',
|
||
' // 限制项目参与人员数量',
|
||
' auxiliaryVars[$"project_participation_{projectCode}_{personnel.Id}"] = participationVar;',
|
||
' }',
|
||
' }',
|
||
' }',
|
||
'',
|
||
' // 2. 工作负载平衡约束',
|
||
' var totalTasks = context.Tasks.Count;',
|
||
' var totalPersonnel = context.AvailablePersonnel.Count;',
|
||
' var historicalWorkloads = BuildHistoricalWorkloadMap(context, out var totalHistoricalWorkload);',
|
||
' var totalWorkload = totalTasks + totalHistoricalWorkload;',
|
||
' var avgWorkload = totalPersonnel > 0 ? (double)totalWorkload / totalPersonnel : 0.0;',
|
||
' var maxDeviation = Math.Ceiling(avgWorkload * 0.3); // 允许30%偏差',
|
||
'',
|
||
' foreach (var personnel in context.AvailablePersonnel)',
|
||
' {',
|
||
' var personnelTasks = decisionVars',
|
||
' .Where(kvp => kvp.Key.PersonnelId == personnel.Id)',
|
||
' .Select(kvp => kvp.Value)',
|
||
' .ToList();',
|
||
' var historicalWorkload = historicalWorkloads.GetValueOrDefault(personnel.Id);',
|
||
'',
|
||
' if (personnelTasks.Any())',
|
||
' {',
|
||
' // 工作负载总量(含历史)不应过度偏离平均值',
|
||
' var lowerBound = Math.Max(0.0, (avgWorkload - maxDeviation) - historicalWorkload);',
|
||
' var upperBound = Math.Max(lowerBound, (avgWorkload + maxDeviation) - historicalWorkload);',
|
||
' var constraint = solver.MakeConstraint(',
|
||
' lowerBound,',
|
||
' upperBound,',
|
||
' $"workload_balance_{personnel.Id}");',
|
||
'',
|
||
' foreach (var taskVar in personnelTasks)',
|
||
' {',
|
||
' constraint.SetCoefficient(taskVar, 1);',
|
||
' }',
|
||
' constraintCount++;',
|
||
' }',
|
||
' }',
|
||
'',
|
||
' return constraintCount;',
|
||
' }'
|
||
)
|
||
$lines.InsertRange($blockStart, $newBlock)
|
||
}
|
||
|
||
[System.IO.File]::WriteAllLines($path, $lines)
|