import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
from scipy.spatial.distance import euclidean
from shapely.geometry import Polygon, Point, LineString
import random
from datetime import datetime
import os
import math
# -------------------------- 从Excel读取任务参数(新增ABCD类任务筛选) --------------------------
EXCEL_PATH = "附件2:问题二任务清单.xlsx"
REQUIRED_CYCLE_TYPES = {'A', 'B', 'C', 'D'} # 核心约束:ABCD类任务
try:
task_df = pd.read_excel(EXCEL_PATH)
required_columns = [
"任务类别", "任务持续时间", "跟踪距离",
"移动任务", "航速", "航向",
"点位X轴坐标", "点位Y轴坐标"
]
missing_cols = [col for col in required_columns if col not in task_df.columns]
if missing_cols:
raise ValueError(f"Excel文件缺少必需列:{', '.join(missing_cols)},请检查列名是否匹配模板")
# 读取所有任务参数
TASK_TYPES = task_df["任务类别"].tolist()
TASK_DURATIONS = task_df["任务持续时间"].astype(float).tolist()
TASK_TRACKING_DIST = task_df["跟踪距离"].astype(float).tolist()
MOVING_TASKS = task_df["移动任务"].astype(bool).tolist()
MOVING_SPEEDS = task_df["航速"].astype(float).tolist()
MOVING_COURSES = task_df["航向"].astype(float).tolist()
TASK_LOCATIONS = list(zip(
task_df["点位X轴坐标"].astype(float).tolist(),
task_df["点位Y轴坐标"].astype(float).tolist()
))
TASK_LOCATIONS = [(round(x, 2), round(y, 2)) for x, y in TASK_LOCATIONS]
TOTAL_TASKS_FROM_EXCEL = len(TASK_TYPES)
# 筛选ABCD类任务的ID
REQUIRED_CYCLE_TASK_IDS = [
idx for idx, task_type in enumerate(TASK_TYPES)
if task_type in REQUIRED_CYCLE_TYPES
]
print(f"成功从Excel读取{TOTAL_TASKS_FROM_EXCEL}个任务的参数")
print(f"需要12小时周期约束的ABCD类任务ID:{REQUIRED_CYCLE_TASK_IDS}")
if not REQUIRED_CYCLE_TASK_IDS:
print("警告:未检测到ABCD类任务,周期约束自动失效")
except FileNotFoundError:
raise FileNotFoundError(f"未找到Excel文件:{EXCEL_PATH},请检查文件路径是否正确")
except Exception as e:
raise Exception(f"读取Excel失败:{str(e)}")
# -------------------------- 基础参数设置(新增周期参数) --------------------------
plt.rcParams['font.sans-serif'] = ['SimHei']
plt.rcParams['axes.unicode_minus'] = False # 解决负号显示问题
SHIP_SPEEDS = [20, 24, 26, 30] # 节
SHIP_START_POINTS = [(187, 175), (169, 112), (50, 88), (73, 214)] # 4艘舰艇初始位置
EXIT_POLYGON = Polygon([(194.0, 177.6), (173.4, 106.9), (42.5, 81.2),
(68.1, 220.7), (144.6, 211.5)])
Cruise_line = [(187.05, 175.1), (168.65, 112.1), (50, 88.75), (73, 214.05), (142.45, 205.75)]
cruise_polygon = Polygon(Cruise_line)
cruise_boundary = LineString(cruise_polygon.exterior.coords)
SHIP_Handling_radius=[5,3,1,1] #处置半径
SHIP_Handling_amount=[6,5,3,2] #处置数量
# 遗传算法参数 + 周期约束参数
POP_SIZE = 8
MAX_GEN = 300
CX_PROB = 0.85
MUT_PROB = 0.15
ELITE_RATIO = 0.1
KNOTS_TO_KMH = 1.852
NAUTICAL_TO_KM = 1.852
CYCLE_DURATION = 12 # 约束周期:12小时
PENALTY_FACTOR = 0.5 # 约束不满足时的适应度惩罚系数
# 新增:舰艇航迹配置(颜色、线型、标记,4艘舰艇各不同)
SHIP_TRAJECTORY_CONFIG = [
{"color": "#1f77b4", "line_style": "-", "init_marker": "D", "task_marker": "o", "label": "舰艇1"},
{"color": "#ff7f0e", "line_style": "--", "init_marker": "D", "task_marker": "s", "label": "舰艇2"},
{"color": "#2ca02c", "line_style": "-.", "init_marker": "D", "task_marker": "^", "label": "舰艇3"},
{"color": "#d62728", "line_style": ":", "init_marker": "D", "task_marker": "p", "label": "舰艇4"}
]
# == == == == == 新增:周期约束辅助函数 == == == == ==
def get_time_cycles(total_time):
if total_time <= 0:
return []
cycle_count = int(np.ceil(total_time / CYCLE_DURATION))
return [[cycle * CYCLE_DURATION, (cycle + 1) * CYCLE_DURATION]
for cycle in range(cycle_count)]
def is_task_executed_in_cycle(task_start, task_end, cycle_start, cycle_end):
return (task_start >= cycle_start and task_start < cycle_end) or \
(task_end > cycle_start and task_end <= cycle_end) or \
(task_start <= cycle_start and task_end >= cycle_end)
def check_cycle_constraint(ship_tasks, total_time):
if not REQUIRED_CYCLE_TASK_IDS:
return {"约束状态": "无ABCD类任务,约束失效"}, 0
cycles = get_time_cycles(total_time)
constraint_result = {
"总周期数": len(cycles),
"周期详情": [f"{cycle[0]}-{cycle[1]}h" for cycle in cycles],
"任务约束检查": {}
}
unmet_count = 0
for task_id in REQUIRED_CYCLE_TASK_IDS:
task_type = TASK_TYPES[task_id]
task_executions = []
for ship_idx in range(len(ship_tasks)):
for task in ship_tasks[ship_idx]:
if task["任务编号"] == task_id:
task_executions.append({
"舰艇编号": task["舰艇编号"],
"开始时间": task["处置任务开始时刻(小时)"],
"结束时间": task["处置任务结束时刻(小时)"]
})
cycle_check = []
for cycle_idx, (cycle_start, cycle_end) in enumerate(cycles):
executed = any(
is_task_executed_in_cycle(
exec["开始时间"], exec["结束时间"], cycle_start, cycle_end
) for exec in task_executions
)
if not executed:
unmet_count += 1
cycle_check.append(f"周期{cycle_idx + 1}({cycle_start}-{cycle_end}h):未执行❌")
else:
cycle_check.append(f"周期{cycle_idx + 1}({cycle_start}-{cycle_end}h):已执行✅")
constraint_result["任务约束检查"][f"任务{task_id}({task_type}类)"] = cycle_check
constraint_result["约束状态"] = f"共{unmet_count}个(任务-周期)对未满足约束"
return constraint_result, unmet_count
# == == == == == 原有辅助函数(保持不变) == == == == ==
def calc_course(from_pos, to_pos):
dx = to_pos[0] - from_pos[0]
dy = to_pos[1] - from_pos[1]
if dx == 0 and dy == 0:
return 0.0
math_angle = np.arctan2(dy, dx)
course = (90 - np.degrees(math_angle)) % 360
return round(course, 2)
def get_closest_cruise_point(end_pos):
end_point = Point(end_pos)
proj_dist = cruise_boundary.project(end_point)
closest_point = cruise_boundary.interpolate(proj_dist)
closest_pos = (round(closest_point.x, 2), round(closest_point.y, 2))
boundary_vertices = list(cruise_boundary.coords)[:-1]
min_x = min(v[0] for v in boundary_vertices)
max_x = max(v[0] for v in boundary_vertices)
min_y = min(v[1] for v in boundary_vertices)
max_y = max(v[1] for v in boundary_vertices)
cx, cy = closest_pos
if not (min_x - 1e-6 <= cx <= max_x + 1e-6 and min_y - 1e-6 <= cy <= max_y + 1e-6):
vertex_dists = [euclidean(end_pos, v) for v in boundary_vertices]
closest_idx = np.argmin(vertex_dists)
closest_pos = boundary_vertices[closest_idx]
return closest_pos
def calc_end_course_to_cruise(end_pos):
closest_pos = get_closest_cruise_point(end_pos)
return calc_course(end_pos, closest_pos)
def get_target_position(task_id, t):
if not MOVING_TASKS[task_id]:
return TASK_LOCATIONS[task_id]
x0, y0 = TASK_LOCATIONS[task_id]
speed_kmh = MOVING_SPEEDS[task_id] * KNOTS_TO_KMH
course_rad = np.deg2rad(MOVING_COURSES[task_id])
dx = speed_kmh * t * np.sin(course_rad)
dy = speed_kmh * t * np.cos(course_rad)
return (round(x0 + dx, 2), round(y0 + dy, 2))
def is_target_exited(task_id, t):
if not MOVING_TASKS[task_id]:
return False
pos = get_target_position(task_id, t)
return not EXIT_POLYGON.contains(Point(pos))
def calc_distance(pos1, pos2):
return round(euclidean(pos1, pos2), 2)
def calc_travel_time(ship_speed, distance):
if distance == 0:
return 0.0
speed_kmh = ship_speed * KNOTS_TO_KMH
return round(distance / speed_kmh, 2)
def calc_time_in_polygon(init_point, speed, direction_angle, polygon_vertices):
"""
计算移动点从多边形外进入、再离开的总时间(简化验证版)
:param init_point: 初始点坐标(元组):(x0, y0)
:param speed: 移动速度(正数)
:param direction_angle: 移动方向角(度,与x轴正方向夹角)
:param polygon_vertices: 多边形顶点列表:[(x1,y1), ..., (xn,yn)]
:return: 总时间,无有效交点时返回0
"""
# 提取初始坐标
init_x, init_y = init_point
# 1. 初始化多边形和轨迹
polygon = Polygon(polygon_vertices)
theta = math.radians(450-direction_angle)
vx = speed * math.cos(theta)
vy = speed * math.sin(theta)
# 生成足够长的轨迹线段
end_x = init_x + vx * 1e6
end_y = init_y + vy * 1e6
trajectory = LineString([(init_x, init_y), (end_x, end_y)])
# 2. 计算轨迹与多边形边界的交点
intersection = trajectory.intersection(polygon.boundary)
if intersection.is_empty:
return 0.0 # 无交点
# 3. 提取并排序交点(按到初始点的距离)
if intersection.geom_type == "Point":
intersections = [intersection]
elif intersection.geom_type == "MultiPoint":
intersections = list(intersection.geoms)
else:
return 0.0 # 非点类型交点视为无效
print(intersection)
# 按轨迹顺序排序(确保进入点在前,离开点在后)
intersections.sort(key=lambda p: math.hypot(p.x - init_x, p.y - init_y))
# 4. 取前两个交点计算距离(假设存在两个有效交点)
if len(intersections) < 2:
return 0.0 # 交点不足
enter_point, leave_point = intersections[0], intersections[1]
distance = math.hypot(leave_point.x - enter_point.x, leave_point.y - enter_point.y)
return round(distance / speed, 6)
# == == == == == 遗传算法核心(保持不变) == == == == ==
class GAOptimizer:
def __init__(self, num_ships=4, forced_task_count=3):
self.num_ships = num_ships
self.total_tasks = TOTAL_TASKS_FROM_EXCEL
self.forced_task_count = min(forced_task_count, self.total_tasks)
self.forced_tasks = list(range(self.forced_task_count))
self.normal_tasks = list(range(self.forced_task_count, self.total_tasks))
print(f"任务划分:强制任务{self.forced_tasks},普通任务{self.normal_tasks}")
def init_population(self):
population = []
for _ in range(POP_SIZE):
forced_shuffled = random.sample(self.forced_tasks, len(self.forced_tasks))
normal_shuffled = random.sample(self.normal_tasks, len(self.normal_tasks)) if self.normal_tasks else []
chrom = forced_shuffled + normal_shuffled
population.append(chrom)
return population
def evaluate_individual(self, individual):
ship_times = [0.0] * self.num_ships
ship_positions = [pos for pos in SHIP_START_POINTS]
used_ships_for_forced = set()
task_execution_records = [[] for _ in range(self.num_ships)]
for task_id in individual:
if task_id in self.forced_tasks:
for ship_idx in range(self.num_ships):
if ship_idx not in used_ships_for_forced:
target_ship_idx = ship_idx
used_ships_for_forced.add(ship_idx)
break
current_time = 0.0
else:
target_ship_idx = np.argmin(ship_times)
current_time = ship_times[target_ship_idx]
ship_speed = SHIP_SPEEDS[target_ship_idx]
current_pos = ship_positions[target_ship_idx]
target_pos = get_target_position(task_id, current_time) if MOVING_TASKS[task_id] else TASK_LOCATIONS[
task_id]
distance = calc_distance(current_pos, target_pos)
travel_time = calc_travel_time(ship_speed, distance)
if TASK_TYPES[task_id] in ['S2', 'S3']:
# 修正:S2/S3任务需要跟踪目标移动一段距离
# 计算舰艇到达目标位置的时间(考虑目标移动)
travel_time = calc_travel_time(
ship_speed,
calc_distance(current_pos, TASK_LOCATIONS[task_id])
)
# 修正:计算追踪时间(目标移动指定距离所需时间)
tracking_time = TASK_TRACKING_DIST[task_id] / MOVING_SPEEDS[task_id]
exit_time = calc_time_in_polygon(target_pos, MOVING_SPEEDS[task_id], MOVING_COURSES[task_id],
EXIT_POLYGON)
# 修正:总任务时间 = 航行时间 + 追踪时间
task_time = travel_time + tracking_time + exit_time
# 修正:任务结束位置应为目标移动后的位置
end_pos = get_target_position(task_id, current_time + travel_time + tracking_time + exit_time)
ship_times[target_ship_idx] = current_time + task_time
ship_positions[target_ship_idx] = end_pos
else:
task_time = travel_time + TASK_DURATIONS[task_id]
ship_times[target_ship_idx] = current_time + task_time
ship_positions[target_ship_idx] = get_target_position(task_id, ship_times[target_ship_idx])
total_time = max(ship_times)
base_fitness = 1 / (total_time + 1e-6)
if REQUIRED_CYCLE_TASK_IDS:
_, unmet_count = check_cycle_constraint(task_execution_records, total_time)
constrained_fitness = base_fitness * (PENALTY_FACTOR ** unmet_count)
if unmet_count > 0:
print(
f" 警告:该个体有{unmet_count}个(任务-周期)对未满足12小时约束,适应度惩罚后:{constrained_fitness:.4f}")
else:
constrained_fitness = base_fitness
return constrained_fitness, total_time
def evaluate_population(self, population):
fitness_list = []
total_time_list = []
for ind in population:
fit, total_time = self.evaluate_individual(ind)
fitness_list.append(fit)
total_time_list.append(total_time)
return fitness_list, total_time_list
def select_parents(self, population, fitness):
parents = []
for _ in range(POP_SIZE):
candidates = random.sample(range(POP_SIZE), 3)
winner = max(candidates, key=lambda i: fitness[i])
parents.append(population[winner])
return parents
def crossover(self, parent1, parent2):
size = len(parent1)
forced_size = len(self.forced_tasks)
start_forced, end_forced = sorted(random.sample(range(forced_size), 2)) if forced_size >= 2 else (0, 0)
start_normal = forced_size
end_normal = size - 1
start_normal_rand, end_normal_rand = sorted(random.sample(range(start_normal, end_normal + 1), 2)) if (
end_normal - start_normal + 1) >= 2 else (
start_normal, start_normal)
child1 = [-1] * size
child1[start_forced:end_forced + 1] = parent1[start_forced:end_forced + 1]
child1[start_normal_rand:end_normal_rand + 1] = parent1[start_normal_rand:end_normal_rand + 1]
idx = 0
for gene in parent2[:forced_size]:
if gene not in child1[:forced_size] and idx < forced_size:
while child1[idx] != -1:
idx += 1
child1[idx] = gene
idx = start_normal
for gene in parent2[start_normal:]:
if gene not in child1[start_normal:] and idx <= end_normal:
while child1[idx] != -1:
idx += 1
child1[idx] = gene
child2 = [-1] * size
child2[start_forced:end_forced + 1] = parent2[start_forced:end_forced + 1]
child2[start_normal_rand:end_normal_rand + 1] = parent2[start_normal_rand:end_normal_rand + 1]
idx = 0
for gene in parent1[:forced_size]:
if gene not in child2[:forced_size] and idx < forced_size:
while child2[idx] != -1:
idx += 1
child2[idx] = gene
idx = start_normal
for gene in parent1[start_normal:]:
if gene not in child2[start_normal:] and idx <= end_normal:
while child2[idx] != -1:
idx += 1
child2[idx] = gene
return child1, child2
def mutate(self, individual):
forced_size = len(self.forced_tasks)
if len(individual) < 2:
return individual
if random.random() < 0.5 and forced_size >= 2:
idx1, idx2 = random.sample(range(forced_size), 2)
else:
idx_range = range(forced_size, len(individual)) if (len(individual) - forced_size) >= 2 else range(
len(individual))
idx1, idx2 = random.sample(idx_range, 2)
individual[idx1], individual[idx2] = individual[idx2], individual[idx1]
return individual
def optimize(self):
population = self.init_population()
best_fitness_history = []
avg_fitness_history = []
current_gen_best_time_history = []
current_gen_avg_time_history = []
best_solution = None
best_fitness = 0
best_total_time = float('inf')
best_constraint_result = None
for gen in range(MAX_GEN):
fitness_list, total_time_list = self.evaluate_population(population)
current_best_fit = max(fitness_list)
current_avg_fit = sum(fitness_list) / POP_SIZE
current_best_time = min(total_time_list)
current_avg_time = sum(total_time_list) / POP_SIZE
if current_best_fit > best_fitness:
best_fitness = current_best_fit
best_total_time = current_best_time
best_solution = population[np.argmax(fitness_list)].copy()
best_ship_tasks = self.decode_solution(best_solution)
best_constraint_result, _ = check_cycle_constraint(best_ship_tasks, best_total_time)
best_fitness_history.append(current_best_fit)
avg_fitness_history.append(current_avg_fit)
current_gen_best_time_history.append(current_best_time)
current_gen_avg_time_history.append(current_avg_time)
parents = self.select_parents(population, fitness_list)
offspring = []
for i in range(0, POP_SIZE, 2):
if i + 1 < POP_SIZE:
p1, p2 = parents[i], parents[i + 1]
if random.random() < CX_PROB:
c1, c2 = self.crossover(p1, p2)
else:
c1, c2 = p1.copy(), p2.copy()
offspring.append(c1)
offspring.append(c2)
offspring = [self.mutate(ind) for ind in offspring]
elite_size = max(int(ELITE_RATIO * POP_SIZE), 1)
elite_indices = np.argsort(fitness_list)[-elite_size:]
for i in range(elite_size):
offspring[i] = population[elite_indices[i]].copy()
population = offspring
if gen % 10 == 0:
print(f"\nGeneration {gen}: "
f"Best Time = {current_best_time:.2f}h, "
f"Avg Time = {current_avg_time:.2f}h, "
f"Best Fitness = {current_best_fit:.4f}")
self.best_constraint_result = best_constraint_result
return (best_solution, best_fitness, best_total_time,
best_fitness_history, avg_fitness_history,
current_gen_best_time_history, current_gen_avg_time_history)
def decode_solution(self, solution):
ship_times = [0.0] * self.num_ships
ship_positions = [pos for pos in SHIP_START_POINTS]
ship_tasks = [[] for _ in range(self.num_ships)]
used_ships_for_forced = set()
for task_id in solution:
if task_id in self.forced_tasks:
for ship_idx in range(self.num_ships):
if ship_idx not in used_ships_for_forced:
target_ship_idx = ship_idx
used_ships_for_forced.add(ship_idx)
break
depart_time = 0.0
else:
target_ship_idx = np.argmin(ship_times)
depart_time = ship_times[target_ship_idx]
ship_speed = SHIP_SPEEDS[target_ship_idx]
depart_pos = ship_positions[target_ship_idx]
task_start_pos = get_target_position(task_id, depart_time) if MOVING_TASKS[task_id] else TASK_LOCATIONS[
task_id]
travel_dist = calc_distance(depart_pos, task_start_pos)
travel_time = calc_travel_time(ship_speed, travel_dist)
task_start_time = depart_time + travel_time
depart_course = calc_course(depart_pos, task_start_pos)
if TASK_TYPES[task_id] in ['S2', 'S3']:
# 修正:S2/S3任务需要追踪目标移动
# 计算航行时间
travel_dist = calc_distance(depart_pos, TASK_LOCATIONS[task_id])
travel_time = calc_travel_time(ship_speed, travel_dist)
# 修正:计算追踪时间(目标移动指定距离所需时间)
tracking_time = TASK_TRACKING_DIST[task_id] / MOVING_SPEEDS[task_id]
# 修正:任务开始时间 = 出发时间 + 航行时间
task_start_time = depart_time + travel_time
exit_time = calc_time_in_polygon(task_start_pos,MOVING_SPEEDS[task_id],MOVING_COURSES[task_id],EXIT_POLYGON)
# 修正:任务结束时间 = 开始时间 + 追踪时间
task_end_time = task_start_time + tracking_time + exit_time
# 到达任务点位置
task_start_pos = get_target_position(task_id, task_start_time)
# 修正:任务结束位置 = 目标在结束时刻的位置
task_end_pos = get_target_position(task_id, task_end_time)
# 修正:任务总耗时 = 航行时间 + 追踪时间
task_duration = travel_time + tracking_time + exit_time
else:
task_duration = TASK_DURATIONS[task_id]
task_end_time = task_start_time + task_duration
task_end_pos = get_target_position(task_id, task_end_time)
is_required_cycle = task_id in REQUIRED_CYCLE_TASK_IDS
task_detail = {
"舰艇编号": target_ship_idx + 1,
"任务编号": task_id,
"任务类型": TASK_TYPES[task_id],
"是否ABCD类任务": "是" if is_required_cycle else "否",
"舰艇速度(节)": ship_speed,
"舰艇出发时刻(小时)": round(depart_time, 2),
"舰艇出发坐标(x,y)": depart_pos, # 航迹关键坐标1:舰艇出发位置
"舰艇出发航向(°)": depart_course,
"处置任务开始时刻(小时)": round(task_start_time, 2),
"处置任务开始坐标(x,y)": task_start_pos, # 航迹关键坐标2:任务开始位置
"处置任务结束时刻(小时)": round(task_end_time, 2),
"处置任务结束坐标(x,y)": task_end_pos, # 航迹关键坐标3:任务结束位置
"任务结束后航向(°)": None,
"任务耗时(小时)": round(task_duration, 2)
}
ship_tasks[target_ship_idx].append(task_detail)
ship_times[target_ship_idx] = task_end_time
ship_positions[target_ship_idx] = task_end_pos
for ship_idx in range(self.num_ships):
tasks = ship_tasks[ship_idx]
num_tasks = len(tasks)
for i in range(num_tasks):
current_task = tasks[i]
current_end_pos = current_task["处置任务结束坐标(x,y)"]
if i == num_tasks - 1:
end_course = calc_end_course_to_cruise(current_end_pos)
else:
next_task = tasks[i + 1]
next_task_id = next_task["任务编号"]
next_depart_time = current_task["处置任务结束时刻(小时)"]
next_task_start_pos = get_target_position(next_task_id, next_depart_time) if MOVING_TASKS[
next_task_id] else TASK_LOCATIONS[next_task_id]
end_course = calc_course(current_end_pos, next_task_start_pos)
current_task["任务结束后航向(°)"] = end_course
return ship_tasks
# == == == == == 主程序(核心新增:舰艇航迹绘制) == == == == ==
if __name__ == "__main__":
optimizer = GAOptimizer(num_ships=4)
(best_sol, best_fit, best_total_time,
best_fit_hist, avg_fit_hist,
gen_best_time_hist, gen_avg_time_hist) = optimizer.optimize()
# 输出结果(含约束检查结果)
print("\n" + "=" * 80)
print("优化完成!基础结果如下:")
print(f"Excel总任务数: {TOTAL_TASKS_FROM_EXCEL}")
print(f"需要12小时周期约束的ABCD类任务ID:{REQUIRED_CYCLE_TASK_IDS}")
print(f"最佳任务序列: {best_sol}")
print(f"历史最佳总完成时间: {best_total_time:.2f} 小时")
print(f"历史最佳适应度: {best_fit:.4f}")
print(f"巡航线闭合线圈顶点: {Cruise_line}")
print("\n【ABCD类任务12小时周期约束检查结果】")
if optimizer.best_constraint_result:
for key, value in optimizer.best_constraint_result.items():
if isinstance(value, dict):
print(f" {key}:")
for task_key, task_value in value.items():
print(f" {task_key}:")
for item in task_value:
print(f" - {item}")
else:
print(f" {key}: {value}")
print("=" * 80)
# 导出Excel(含ABCD类任务标记)
ship_tasks = optimizer.decode_solution(best_sol)
all_tasks = []
for ship in ship_tasks:
all_tasks.extend(ship)
df = pd.DataFrame(all_tasks)
column_order = [
"舰艇编号", "任务编号", "任务类型", "是否ABCD类任务",
"舰艇速度(节)", "舰艇出发时刻(小时)", "舰艇出发坐标(x,y)", "舰艇出发航向(°)",
"处置任务开始时刻(小时)", "处置任务开始坐标(x,y)",
"处置任务结束时刻(小时)", "处置任务结束坐标(x,y)",
"任务结束后航向(°)", "任务耗时(小时)"
]
df = df[column_order]
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
excel_filename = f"第二问舰艇任务分配详情_含ABCD约束_{timestamp}.xlsx"
try:
with pd.ExcelWriter(excel_filename, engine="openpyxl") as writer:
df.to_excel(writer, sheet_name="任务分配详情", index=False)
if optimizer.best_constraint_result:
constraint_data = []
for key, value in optimizer.best_constraint_result.items():
if isinstance(value, list) and key == "周期详情":
for i, cycle in enumerate(value):
constraint_data.append(
{"约束项": "周期详情", "周期序号": i + 1, "周期范围": cycle, "备注": ""})
elif isinstance(value, dict) and key == "任务约束检查":
for task_key, task_cycles in value.items():
task_id = task_key.split("(")[0]
task_type = task_key.split("(")[1].replace("类)", "")
for cycle_idx, cycle_check in enumerate(task_cycles):
cycle_range = optimizer.best_constraint_result["周期详情"][cycle_idx]
status = "已执行" if "✅" in cycle_check else "未执行"
constraint_data.append({
"约束项": task_key,
"周期序号": cycle_idx + 1,
"周期范围": cycle_range,
"执行状态": status,
"备注": cycle_check.split(":")[1]
})
else:
constraint_data.append(
{"约束项": key, "周期序号": "", "周期范围": "", "执行状态": "", "备注": value})
constraint_df = pd.DataFrame(constraint_data)
constraint_df.to_excel(writer, sheet_name="ABCD类任务约束结果", index=False)
print(f"\nExcel表格已生成:{excel_filename}")
print(f"文件路径:{os.path.abspath(excel_filename)}")
print(f"表格包含2个工作表:")
print(f" 1. 任务分配详情:所有任务的执行信息(含是否ABCD类任务标记)")
print(f" 2. ABCD类任务约束结果:12小时周期约束的详细检查结果")
except Exception as e:
print(f"\n导出Excel失败:{str(e)}")
print("请确保已安装依赖库:pip install pandas openpyxl shapely")
# -------------------------- 1. 绘制适应度图(保持) --------------------------
plt.figure(figsize=(12, 8))
plt.plot(best_fit_hist, label="最佳适应度(含约束惩罚)", color="#1f77b4", linewidth=2.5)
plt.plot(avg_fit_hist, label="平均适应度(含约束惩罚)", color="#ff7f0e", linewidth=2.5)
plt.xlabel("迭代次数", fontsize=20)
plt.ylabel("适应度(总完成时间倒数+约束惩罚)", fontsize=20)
plt.title("遗传算法迭代过程中适应度变化(含ABCD类任务12小时约束)", fontsize=24, pad=20)
plt.legend(fontsize=18, loc="upper right")
plt.grid(alpha=0.3, linestyle="--")
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
plt.tight_layout()
plt.show()
# -------------------------- 2. 绘制完成时间图(保持) --------------------------
plt.figure(figsize=(12, 8))
plt.plot(gen_best_time_hist, label="每代最佳总完成时间", color="#2ca02c", linewidth=2.5)
plt.plot(gen_avg_time_hist, label="每代平均总完成时间", color="#d62728", linewidth=2.5, linestyle="--")
plt.axhline(y=best_total_time, color="#2ca02c", linestyle=":", linewidth=2,
label=f"历史最佳总时间:{best_total_time:.2f}h")
plt.xlabel("迭代次数", fontsize=20)
plt.ylabel("总完成时间(小时)", fontsize=20)
plt.title("遗传算法每代任务总完成时间变化", fontsize=24, pad=20)
plt.legend(fontsize=18, loc="upper right")
plt.grid(alpha=0.3, linestyle="--")
plt.xticks(fontsize=16)
plt.yticks(fontsize=16)
last_gen = MAX_GEN - 1
plt.annotate(
f'历史最佳:{best_total_time:.2f}h',
xy=(last_gen, best_total_time),
xytext=(last_gen - 80, best_total_time + 2),
fontsize=16,
arrowprops=dict(arrowstyle="->", color="#2ca02c", lw=2)
)
plt.tight_layout()
plt.show()
# -------------------------- 3. 绘制任务结束航向可视化(保持) --------------------------
plt.figure(figsize=(12, 10))
cruise_x = [p[0] for p in Cruise_line] + [Cruise_line[0][0]]
cruise_y = [p[1] for p in Cruise_line] + [Cruise_line[0][1]]
plt.plot(cruise_x, cruise_y, color="#1f77b4", linewidth=3, label="巡航线闭合线圈")
plt.fill(cruise_x, cruise_y, color="#1f77b4", alpha=0.1)
for task in all_tasks:
end_x, end_y = task["处置任务结束坐标(x,y)"]
end_course = task["任务结束后航向(°)"]
ship_num = task["舰艇编号"]
task_id = task["任务编号"]
is_abcd = task["是否ABCD类任务"]
scatter_color = "#d62728" if is_abcd == "是" else "#ff7f0e"
plt.scatter(end_x, end_y, color=scatter_color, s=180, zorder=5,
label="ABCD类任务" if is_abcd == "是" and "ABCD类任务" not in plt.gca().get_legend_handles_labels()[
1] else "")
arrow_len = 15
angle_rad = np.deg2rad(90 - end_course)
dx = arrow_len * np.cos(angle_rad)
dy = arrow_len * np.sin(angle_rad)
plt.arrow(end_x, end_y, dx, dy, color=scatter_color, width=0.8,
head_width=3, head_length=3, zorder=6)
plt.text(end_x, end_y + 8, f"舰艇{ship_num}", color=scatter_color, fontsize=17, fontweight="bold",
ha="center", va="center", zorder=7)
plt.text(end_x, end_y - 8, f"任务{task_id}", color="black", fontsize=14,
ha="center", va="center", zorder=7)
plt.text(end_x + dx + 3, end_y + dy + 3, f"{end_course}°", color="#483D8B", fontsize=14,
ha="center", va="center", zorder=7)
plt.xlabel("X坐标", fontsize=20)
plt.ylabel("Y坐标", fontsize=20)
plt.title("任务结束后航向可视化(红色=ABCD类任务,橙色=其他任务)", fontsize=24, pad=20)
plt.legend(fontsize=16, loc="upper left")
plt.grid(alpha=0.3, linestyle="--")
plt.axis("equal")
plt.tight_layout()
plt.show()
# -------------------------- 4. 核心新增:绘制每艘舰艇的航迹图 --------------------------
plt.figure(figsize=(14, 12))
# 4.1 绘制背景元素(巡航线、任务区域边界)
# 巡航线
cruise_x = [p[0] for p in Cruise_line] + [Cruise_line[0][0]]
cruise_y = [p[1] for p in Cruise_line] + [Cruise_line[0][1]]
plt.plot(cruise_x, cruise_y, color="#808080", linewidth=2, linestyle="--", label="巡航线")
plt.fill(cruise_x, cruise_y, color="#808080", alpha=0.05)
# 任务区域边界(EXIT_POLYGON)
exit_x, exit_y = EXIT_POLYGON.exterior.xy
plt.plot(exit_x, exit_y, color="#000000", linewidth=2.5, label="任务区域边界")
# 4.2 为每艘舰艇提取航迹点并绘制
for ship_idx in range(4): # 遍历4艘舰艇
ship_task_list = ship_tasks[ship_idx] # 当前舰艇的所有任务
traj_config = SHIP_TRAJECTORY_CONFIG[ship_idx] # 当前舰艇的航迹样式配置
# 初始化航迹点列表(第一个点是舰艇初始位置)
traj_points = [SHIP_START_POINTS[ship_idx]]
# 遍历当前舰艇的每个任务,提取关键航迹点
for task in ship_task_list:
# 航迹点顺序:舰艇出发位置 → 任务开始位置 → 任务结束位置
traj_points.append(task["舰艇出发坐标(x,y)"])
traj_points.append(task["处置任务开始坐标(x,y)"])
traj_points.append(task["处置任务结束坐标(x,y)"])
# 4.3 绘制航迹线(连接所有航迹点)
traj_x = [p[0] for p in traj_points]
traj_y = [p[1] for p in traj_points]
plt.plot(traj_x, traj_y,
color=traj_config["color"],
linestyle=traj_config["line_style"],
linewidth=3,
label=traj_config["label"],
zorder=3)
# 4.4 标记关键节点(初始位置、任务开始/结束位置)
# 标记初始位置(菱形,更大尺寸)
init_x, init_y = traj_points[0]
plt.scatter(init_x, init_y,
color=traj_config["color"],
marker=traj_config["init_marker"],
s=300,
edgecolor="#000000",
linewidth=2,
zorder=5)
plt.text(init_x + 5, init_y + 5, f"{traj_config['label']}初始位置",
color=traj_config["color"], fontsize=14, fontweight="bold", zorder=6)
# 标记任务节点(任务开始+结束位置,用任务编号标注)
task_idx = 0
for i in range(1, len(traj_points), 3): # 每3个点对应一个任务(出发→开始→结束)
if task_idx >= len(ship_task_list):
break
current_task = ship_task_list[task_idx]
task_id = current_task["任务编号"]
is_abcd = current_task["是否ABCD类任务"]
# 任务开始位置(空心标记)
start_x, start_y = traj_points[i+1]
plt.scatter(start_x, start_y,
color=traj_config["color"],
marker=traj_config["task_marker"],
s=200,
facecolor="none",
edgecolor="#000000",
linewidth=2,
zorder=4)
# 任务结束位置(实心标记,ABCD类任务用红色边框突出)
end_x, end_y = traj_points[i+2]
edge_color = "#d62728" if is_abcd == "是" else traj_config["color"]
plt.scatter(end_x, end_y,
color=traj_config["color"],
marker=traj_config["task_marker"],
s=200,
edgecolor=edge_color,
linewidth=2.5,
zorder=4)
# 标注任务编号(ABCD类任务用红色)
text_color = "#d62728" if is_abcd == "是" else "#000000"
plt.text(end_x + 5, end_y - 5, f"任务{task_id}",
color=text_color, fontsize=12, fontweight="bold", zorder=6)
task_idx += 1
# 4.5 图表格式设置
plt.xlabel("X坐标", fontsize=22)
plt.ylabel("Y坐标", fontsize=22)
plt.title("每艘舰艇航迹可视化(含初始位置、任务节点)", fontsize=26, pad=20)
plt.legend(fontsize=16, loc="upper left")
plt.grid(alpha=0.3, linestyle="--")
plt.axis("equal") # 等比例坐标,确保航迹方向准确
plt.tight_layout()
plt.show()纠正上述代码航迹图的绘制
最新发布