paiban/apply_fairness_final.ps1
Developer 7c80f73079 1111
2025-09-23 08:33:50 +08:00

369 lines
18 KiB
PowerShell
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

$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)