206 lines
10 KiB
Python
206 lines
10 KiB
Python
import random
|
||
import numpy as np
|
||
from data_structures import OrderData, RiskEnterpriseData, SupplierData
|
||
|
||
|
||
class ChromosomeUtils:
|
||
"""染色体编码、解码、修复工具类"""
|
||
def __init__(self, order_data: OrderData, risk_data: RiskEnterpriseData, supplier_data: SupplierData):
|
||
self.order = order_data
|
||
self.risk = risk_data
|
||
self.supplier = supplier_data
|
||
self.I = order_data.I
|
||
self.material_optional_enterprises = self._get_material_optional_enterprises()
|
||
self.material_enterprise_count = [len(ents) for ents in self.material_optional_enterprises]
|
||
self.chromosome_length = 3 * sum(self.material_enterprise_count)
|
||
|
||
def _get_material_optional_enterprises(self) -> list[list[int]]:
|
||
"""获取每个物料的可选企业列表:0=风险企业,1~supplier_count=供应商"""
|
||
optional = []
|
||
for i in range(self.I):
|
||
ents = [0] # 先加入风险企业
|
||
for j in range(self.supplier.supplier_count):
|
||
if self.supplier.can_produce[j][i] == 1:
|
||
ents.append(j + 1)
|
||
optional.append(ents)
|
||
return optional
|
||
|
||
def _split_chromosome(self, chromosome: np.ndarray) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
||
"""拆分染色体为三层:企业编码层、能力编码层、数量编码层"""
|
||
total_ent_count = sum(self.material_enterprise_count)
|
||
enterprise_layer = chromosome[:total_ent_count]
|
||
capacity_layer = chromosome[total_ent_count:2*total_ent_count]
|
||
quantity_layer = chromosome[2*total_ent_count:]
|
||
return enterprise_layer, capacity_layer, quantity_layer
|
||
|
||
def _merge_chromosome(self, enterprise_layer: np.ndarray, capacity_layer: np.ndarray,
|
||
quantity_layer: np.ndarray) -> np.ndarray:
|
||
"""合并三层为完整染色体"""
|
||
return np.hstack([enterprise_layer, capacity_layer, quantity_layer])
|
||
|
||
def repair_enterprise_layer(self, enterprise_layer: np.ndarray) -> np.ndarray:
|
||
"""修复企业编码层:确保每种物料至少有一个企业生产"""
|
||
repaired = enterprise_layer.copy()
|
||
split_points = np.cumsum(self.material_enterprise_count)
|
||
start = 0
|
||
for i in range(self.I):
|
||
end = split_points[i]
|
||
segment = repaired[start:end]
|
||
if np.sum(segment) == 0:
|
||
select_idx = random.randint(0, len(segment)-1)
|
||
segment[select_idx] = 1
|
||
repaired[start:end] = segment
|
||
start = end
|
||
return repaired
|
||
|
||
def repair_capacity_layer(self, enterprise_layer: np.ndarray, capacity_layer: np.ndarray) -> np.ndarray:
|
||
"""修复能力编码层:满足单物料产能和总产能约束"""
|
||
repaired = capacity_layer.copy()
|
||
split_points = np.cumsum(self.material_enterprise_count)
|
||
start = 0
|
||
# 单物料产能约束
|
||
for i in range(self.I):
|
||
end = split_points[i]
|
||
ents = self.material_optional_enterprises[i]
|
||
segment = repaired[start:end]
|
||
enterprise_segment = enterprise_layer[start:end]
|
||
for idx, ent in enumerate(ents):
|
||
if enterprise_segment[idx] == 1:
|
||
if ent == 0:
|
||
max_cap = self.risk.C0_i_std[i]
|
||
else:
|
||
supplier_id = ent - 1
|
||
max_cap = self.supplier.Cj_i_std[supplier_id][i]
|
||
if segment[idx] <= 0 or segment[idx] > max_cap:
|
||
segment[idx] = random.uniform(1, max_cap)
|
||
repaired[start:end] = segment
|
||
start = end
|
||
# 总产能约束
|
||
enterprise_total_capacity = {}
|
||
start = 0
|
||
for i in range(self.I):
|
||
end = split_points[i]
|
||
ents = self.material_optional_enterprises[i]
|
||
enterprise_segment = enterprise_layer[start:end]
|
||
capacity_segment = repaired[start:end]
|
||
for idx, ent in enumerate(ents):
|
||
if enterprise_segment[idx] == 1:
|
||
if ent not in enterprise_total_capacity:
|
||
enterprise_total_capacity[ent] = 0
|
||
enterprise_total_capacity[ent] += capacity_segment[idx]
|
||
start = end
|
||
# 调整超出总产能的企业
|
||
for ent, total_cap in enterprise_total_capacity.items():
|
||
if ent == 0:
|
||
max_total_cap = self.risk.C0_total_max
|
||
else:
|
||
supplier_id = ent - 1
|
||
max_total_cap = self.supplier.Cj_total_max[supplier_id]
|
||
if total_cap > max_total_cap:
|
||
scale = max_total_cap / total_cap
|
||
start = 0
|
||
for i in range(self.I):
|
||
end = split_points[i]
|
||
ents = self.material_optional_enterprises[i]
|
||
enterprise_segment = enterprise_layer[start:end]
|
||
capacity_segment = repaired[start:end]
|
||
for idx, e in enumerate(ents):
|
||
if e == ent and enterprise_segment[idx] == 1:
|
||
capacity_segment[idx] *= scale
|
||
repaired[start:end] = capacity_segment
|
||
start = end
|
||
return repaired
|
||
|
||
def repair_quantity_layer(self, enterprise_layer: np.ndarray, quantity_layer: np.ndarray) -> np.ndarray:
|
||
"""修复数量编码层:满足总量需求、起订量和最大供应量约束"""
|
||
repaired = quantity_layer.copy()
|
||
split_points = np.cumsum(self.material_enterprise_count)
|
||
start = 0
|
||
for i in range(self.I):
|
||
qi = self.order.Q[i]
|
||
end = split_points[i]
|
||
ents = self.material_optional_enterprises[i]
|
||
segment = repaired[start:end]
|
||
enterprise_segment = enterprise_layer[start:end]
|
||
selected_indices = np.where(enterprise_segment == 1)[0]
|
||
if len(selected_indices) == 0:
|
||
start = end
|
||
continue
|
||
selected_ents = [ents[idx] for idx in selected_indices]
|
||
# 步骤1:初始化合法范围(起订量~最大供应量)
|
||
for idx, ent in zip(selected_indices, selected_ents):
|
||
if ent == 0:
|
||
min_q = 0
|
||
max_q = qi
|
||
else:
|
||
supplier_id = ent - 1
|
||
min_q = self.supplier.MinOrder[supplier_id][i]
|
||
max_q = self.supplier.MaxOrder[supplier_id][i]
|
||
if segment[idx] < min_q:
|
||
segment[idx] = min_q
|
||
elif segment[idx] > max_q:
|
||
segment[idx] = max_q
|
||
# 步骤2:强制总量等于需求数量(核心修复)
|
||
current_total = np.sum(segment[selected_indices])
|
||
if current_total <= 0:
|
||
weights = np.random.rand(len(selected_indices))
|
||
weights /= np.sum(weights)
|
||
segment[selected_indices] = qi * weights
|
||
else:
|
||
scale = qi / current_total
|
||
segment[selected_indices] *= scale
|
||
# 步骤3:处理超出最大供应量的情况(修复中文变量名+逻辑)
|
||
need_adjust = True
|
||
while need_adjust:
|
||
need_adjust = False
|
||
total_excess = 0
|
||
for idx, ent in zip(selected_indices, selected_ents):
|
||
if ent == 0:
|
||
max_q = qi
|
||
else:
|
||
supplier_id = ent - 1
|
||
max_q = self.supplier.MaxOrder[supplier_id][i]
|
||
if segment[idx] > max_q:
|
||
excess = segment[idx] - max_q
|
||
total_excess += excess
|
||
segment[idx] = max_q
|
||
need_adjust = True
|
||
if total_excess > 0:
|
||
available_indices = []
|
||
available_max = []
|
||
for idx, ent in zip(selected_indices, selected_ents):
|
||
if ent == 0:
|
||
max_q = qi
|
||
else:
|
||
supplier_id = ent - 1
|
||
max_q = self.supplier.MaxOrder[supplier_id][i]
|
||
remaining = max_q - segment[idx]
|
||
if remaining > 0:
|
||
available_indices.append(idx)
|
||
available_max.append(remaining)
|
||
if len(available_indices) > 0:
|
||
available_total = sum(available_max)
|
||
if available_total > 0:
|
||
alloc_ratio = np.array(available_max) / available_total
|
||
alloc_excess = total_excess * alloc_ratio
|
||
for idx, alloc in zip(available_indices, alloc_excess):
|
||
segment[idx] += alloc
|
||
else:
|
||
segment[selected_indices] = np.minimum(segment[selected_indices],
|
||
[self.supplier.MaxOrder[ent-1][i] if ent !=0 else qi for ent in selected_ents])
|
||
current_total = np.sum(segment[selected_indices])
|
||
if abs(current_total - qi) > 1e-6:
|
||
scale = qi / current_total
|
||
segment[selected_indices] *= scale
|
||
need_adjust = True
|
||
repaired[start:end] = segment
|
||
start = end
|
||
return repaired
|
||
|
||
def repair_chromosome(self, chromosome: np.ndarray) -> np.ndarray:
|
||
"""完整修复染色体:企业层→能力层→数量层"""
|
||
enterprise_layer, capacity_layer, quantity_layer = self._split_chromosome(chromosome)
|
||
enterprise_layer = self.repair_enterprise_layer(enterprise_layer)
|
||
capacity_layer = self.repair_capacity_layer(enterprise_layer, capacity_layer)
|
||
quantity_layer = self.repair_quantity_layer(enterprise_layer, quantity_layer)
|
||
return self._merge_chromosome(enterprise_layer, capacity_layer, quantity_layer) |