python的智能制造导论工业场景模拟第七十一篇:模拟物料配送任务,优化AGV任务调度顺序,降低AGV空驶里程,减少物料能耗。

给AGV装上“最优路径大脑”:模拟物料配送任务,优化调度顺序降低空驶里程

 

周三上午,总装车间的物料呼叫大屏上,红点密密麻麻——12个工位同时亮起缺料警报。我站在AGV调度室门口,看着3号AGV在车间里来回穿梭,它的状态指示灯从绿色(载货)变成黄色(空驶),又变回绿色,再变黄色。调度屏上显示:3号AGV过去两小时跑了18.6公里,其中空驶11.2公里,空驶率60.2%。

“王工,你快看看这个。”AGV调度老孙指着监控屏幕,“今天上午任务密集,我手动派了8台AGV出去,结果它们像没头苍蝇一样乱窜。你看3号车,刚从东边仓库送料到西边工位,卸完货又空车跑回东边取料。这来回一趟,空跑了一半路。”

“我也在看。”我拉过椅子坐下,调出任务日志,“问题出在‘先来先服务’。系统接到呼叫就派最近的空闲AGV去取料,不管取完料送到哪、送完之后下一个任务在哪。就像出租车司机,送完一单乘客就原地等下一单,不知道去哪个区域更容易接到单。”

“那怎么办?”老孙问,“总不能让AGV送完货就在工位旁边等着吧?下一个呼叫可能在任何地方。”

“以前我们靠调度员的‘经验预判’。”我回忆起刚入行时跟着老师傅学的招,“老调度员看一眼呼叫屏,就知道‘东边这几个工位快用完了,先把料备过去’。但现在车间大了,工位从20个增加到60个,呼叫频率从每小时5次变成30次。人脑算不过来了。”

“而且还有能耗问题。”设备工程师小赵插话,“AGV用的是锂电池,满电跑8小时。空驶里程多,电量消耗快,中午就得换电池或者充电,影响下午生产。昨天3号车下午两点就没电了,紧急叫了备用车才没停线。”

“我们需要一个‘智能调度器’。”我打开笔记本,“它不只是给AGV派任务,而是像滴滴的‘全局调度’——考虑所有待配送任务、所有AGV的位置和电量、车间地图的路网距离,一次性规划最优的‘任务-车辆’匹配和顺序。”

“全局调度?”老孙皱眉,“那不是得算很久?产线等着用料呢。”

“用贪心算法 + 最近邻搜索,毫秒级就能出结果。”我解释,“它不追求数学上的绝对最优——那得算到天荒地老。它追求‘足够好’:每次给AGV分配下一个任务时,选那个距离最近、且方向顺路的。就像快递员送快递,不会走冤枉路回起点取件,而是规划一条不重复跑的路线。”

我们花了三天,基于车间CAD布局图,提取了60个工位和2个仓库的坐标,构建了路网距离矩阵。然后用模拟数据跑了500次配送任务,对比三种策略:

策略A(先来先服务):平均空驶率58.3%,单台AGV日均里程21.4公里。

策略B(最近邻贪心):平均空驶率31.7%,单台AGV日均里程15.2公里。

策略C(带方向约束的贪心):平均空驶率22.4%,单台AGV日均里程13.1公里。

“这差距太大了。”老孙看着数据,“策略C比A少了35%的空驶里程。8台AGV一天省下来的电,够多跑200多公里。”

“而且策略C还有个好处。”小赵指着电量曲线,“因为空驶少了,AGV的电量消耗更均匀,不用中午集中充电,可以撑到换班。”

上线第一周,车间AGV空驶率从平均56%降到了25%。老孙在周会上说:“以前我调度AGV靠喊、靠记、靠经验。现在系统自动排,我只需要盯着屏幕看有没有异常。这哪是调度,这是给AGV装了个‘老司机的脑子’。”

“准确说,是给整个配送系统装了个‘全局导航’。”我笑着说,“以后新车间扩建,加个地图就能用,不用再培养一个‘老调度员’了。”

 

一、实际应用场景(真实痛点)

 

场景设定:某电子制造总装车间,拥有8台AGV(自动导引车)负责仓库到工位的物料配送。现有调度系统采用“先来先服务”策略,AGV完成当前任务后,系统从任务队列头部取下一个任务分配给它,不考虑任务地理位置的分布和AGV当前方向。导致AGV频繁空驶折返,空驶率高达55%-65%,电池消耗快,配送效率低。

 

现场原话(叙事化):

 

“我每天上班第一件事,就是看AGV电量。”AGV调度员老孙说,“8台车,每天至少3台中午要换电池。不是车不行,是跑的冤枉路太多。你看这台——”他指着屏幕上的轨迹回放,“从仓库取料送到A12工位,卸完货,系统派它去B3工位取料。A12在东边,B3在西边,它空车横跨整个车间。送到B3后,又派去C7。这路线,画出来像蜘蛛网。”

“我也想优化啊。”MES工程师小刘摊手,“但系统就是按工单顺序派任务。工单先到先派,不管AGV在哪。我要改派单逻辑,得改底层代码,风险太大。”

“而且任务有优先级。”生产主管老陈补充,“有些工位是‘停线风险’工位,缺料5分钟就得停线。这些任务必须优先配送。但优先配送和最短路径经常冲突——最近的任务不一定是最紧急的。”

“还有AGV状态不一样。”设备工程师小赵说,“3号车电池老化,满电只能跑6小时;7号车是新车,能跑10小时。你不能把远程任务都派给3号车,它半路没电更麻烦。”

“最头疼的是‘潮汐现象’。”老孙说,“上午10点和下午2点,呼叫量暴增,20多个任务排队。AGV全出去了,新任务来了没车可用。但有些AGV其实快送完了,如果提前规划,可以让它送完直接去下一个呼叫点,而不是回仓库待命。”

“我们需要一个‘动态调度优化器’。”我总结,“输入:所有待配送任务(起点、终点、优先级、截止时间)、所有AGV的实时位置和电量。输出:每台AGV的最优任务序列。目标:最小化总空驶里程,同时满足配送时限。”

“这听起来像运筹学。”小刘说。

“对,就是车辆路径问题(VRP)的简化版。”我说,“但车间场景有特殊约束:单向通道、充电站位置、任务优先级。我们先用贪心算法做静态优化,再逐步加动态重规划。”

核心矛盾:“先来先服务”与“全局路径优化”之间的冲突。现有调度系统只做“任务-车辆”的即时匹配,缺乏“多任务-多车辆”的全局规划能力。我们有所有任务的位置信息和AGV的实时状态,但缺乏“路径优化”算法;能执行“单任务派单”,但无法“批量优化序列”。我们需要一个“AGV任务调度优化系统”。

 

二、痛点分析(映射到长安大学《智能制造导论》课程模型)

 

《智能制造导论》模块 本篇痛点对应

概述:高效率、低成本、资源优化 高效率:减少空驶等待时间;低成本:降低电池损耗和能耗;资源优化:提升AGV利用率。

智能制造技术基础:物流装备、传感技术 物流装备:AGV小车、充电桩、呼叫终端;传感技术:AGV定位(激光/二维码)、电量检测。

新一代支撑技术:工业大数据、人工智能(路径优化/贪心算法) 工业大数据:任务位置、AGV轨迹、能耗数据;人工智能:贪心算法、最近邻搜索实现任务序列优化。

智能工厂与智能生产:物流自动化、动态调度 物流自动化:AGV自主导航;动态调度:根据实时任务状态重规划路径。

演进范式:人工调度 → 固定路线 → 随机派单 → 全局优化 从“调度员喊车”到“算法自动规划最优序列”,实现从“被动响应”到“主动规划”的跨越。

 

一句话总结:我们需要构建一个“AGV配送任务调度优化引擎”,通过构建车间路网模型,采用贪心算法为每台AGV规划最优任务序列,最小化空驶里程和能耗,实现物料配送的“全局最优”。

 

三、核心逻辑讲解(大白话)

 

3.1 问题本质:把AGV调度看成“快递员送快递”,规划一条不跑冤枉路的路线

 

把AGV任务调度和路径优化的关系,想象成“快递员的一天”:

 

* AGV = 快递员:有几辆快递车(AGV),每辆车有当前位置和剩余电量。

* 配送任务 = 快递包裹:每个包裹有取件点(仓库)和送件点(工位),有些包裹加急(高优先级)。

* 车间路网 = 城市道路:有些路是单行道(单向通道),有些路口会堵车(AGV交汇)。

* 空驶里程 = 空车跑的路:快递员送完包裹,车里没货,空着车去下一个取件点。这段路不产生价值,但耗油(耗电)。

* 调度策略 = 派单逻辑:

   * 先来先服务:谁先打电话就先送谁的包裹,不管快递员在哪。结果快递员可能跨半个城市送一个包裹,然后空车回来。

   * 最近邻贪心:快递员送完一个包裹后,看周围最近的待送包裹,优先送它。这样空车距离最短。

   * 带方向约束的贪心:不仅看距离,还看方向。如果快递员在城东,下一个包裹也在城东,就顺路送;如果城西有包裹但方向完全相反,就暂时跳过,先送城东的。

 

工业应用:

 

* 输入:任务列表(起点坐标、终点坐标、优先级)、AGV列表(当前坐标、电量、状态)。

* 路网模型:用 networkx 构建车间有向图,节点是工位/仓库,边是通道,权重是距离。

* 距离计算:用最短路径算法(Dijkstra)计算任意两点间的实际行驶距离(不是直线距离,因为要绕通道)。

* 贪心分配:每台AGV完成当前任务后,从待分配任务中选一个:距离最近 + 方向一致 + 优先级高。

* 输出:每台AGV的任务序列和预计完成时间。

 

3.2 业务逻辑 → 代码映射

 

车间地图与任务数据

   │

   ▼ MapBuilder.build_graph()

构建路网图:

  1. 加载工位坐标CSV:node_id, x, y, type (warehouse/workstation/charging)

  2. 构建networkx有向图(考虑单向通道)

  3. 添加边权重(欧氏距离或曼哈顿距离)

  4. 计算所有节点对之间的最短路径距离矩阵

   │

   ▼ TaskGenerator.generate_tasks()

生成配送任务:

  1. 随机生成N个任务:task_id, start_node, end_node, priority, deadline

  2. 优先级映射:停线风险工位 → high, 普通工位 → medium/low

   │

   ▼ AGVFleet.init_fleet()

初始化AGV车队:

  1. 创建M台AGV:agv_id, current_node, battery_level, status

  2. 初始位置:仓库或随机工位

   │

   ▼ Scheduler.optimize()

贪心调度优化:

  1. 对每个空闲AGV:

     a. 从待分配任务中筛选:deadline未过期的任务

     b. 计算每个任务的综合得分:

        score = w1 * (1 / distance_from_agv) # 距离近优先

              + w2 * priority_score # 优先级高优先

              + w3 * direction_alignment # 方向一致优先

     c. 选得分最高的任务分配给该AGV

     d. 更新AGV位置、电量、任务序列

  2. 重复直到所有任务分配完毕或所有AGV忙碌

   │

   ▼ Simulator.run_simulation()

模拟执行:

  1. 按时间步推进仿真

  2. 每台AGV按任务序列移动:取料 → 送货 → 更新状态

  3. 记录:行驶距离、空驶距离、耗电量、任务完成时间

   │

   ▼ Visualizer.visualize()

可视化分析:

  1. AGV轨迹图(车间地图 + 路径)

  2. 空驶率对比柱状图(不同策略)

  3. 电量消耗曲线

  4. 任务完成时间甘特图

 

3.3 为什么用贪心算法而不是遗传算法/蚁群优化/整数规划?

 

* 遗传算法(GA)/蚁群优化(ACO):能找到更好的解,但计算时间长(几十秒到几分钟)。在动态调度场景下,任务不断到来,需要秒级响应。而且这类算法是随机的,每次结果不同,不利于现场稳定。

* 整数规划(MIP):能找到数学最优解,但计算复杂度随任务数指数增长。超过50个任务就需要专业求解器(如CPLEX),且求解时间不可控。

* 贪心算法(Greedy):实时调度的“实用派”。

   * 速度快:每次分配只需计算距离和得分,O(n)复杂度,毫秒级完成。

   * 可解释:每个决策都有明确依据(距离最近、优先级最高),调度员能理解。

   * 易于扩展:加入新约束(如电量、载重)只需修改得分函数。

   * 效果够好:虽然不是数学最优,但比先来先服务好30%-40%,对现场来说已经足够。

* 工业意义:“够用就好,快比完美重要”。在AGV调度中,任务每30秒产生一个新的,算法必须在1秒内给出结果。贪心算法虽然不是最优,但足够好且足够快。等以后算力够了,可以升级到更优算法,但贪心是性价比最高的起点。

* 类比:遗传算法是“请一个专家团队花一周时间规划全国物流网络”;整数规划是“用超级计算机算最优解”;贪心算法是“老快递站长凭经验,看一眼单子就派车”。在战场上,老站长的决策比等专家报告更管用。

 

3.4 如何处理“动态任务插入”和“AGV电量约束”?

 

* 动态任务插入:新任务到来时,不重新计算所有AGV的完整序列(那样太慢),而是:

   * 如果某台AGV当前空闲,立即用贪心策略分配。

   * 如果所有AGV忙碌,将任务加入优先级队列,等某台AGV完成当前任务时再分配。

   * 对于高优先级任务,可以中断低优先级任务的执行(需现场确认)。

* 电量约束:在得分函数中加入电量因子:

   * 如果AGV电量低于20%,只分配近距离任务或引导去充电站。

   * 计算任务往返耗电,如果电量不足以完成,直接排除。

* 代码实现:本例在 

"Scheduler" 的 

"calculate_score" 方法中加入电量和方向因子;在 

"Simulator" 中实时监控电量,低于阈值时插入充电任务。

 

类比:快递员快没油了,系统不会派他去远的地方,而是让他先去最近的加油站。同时,如果有个加急包裹,系统会看哪辆车离得近、油够不够,再决定派谁去。

 

四、OOP 代码实现

 

4.1 项目结构

 

agv_scheduling/

├── data/

│ ├── nodes.csv # 工位/仓库坐标

│ └── tasks.csv # 配送任务(可选)

├── models/ # 保存调度结果

│ └── schedule_result.pth # 调度结果(pickle)

├── results/ # 可视化结果

│ ├── agv_trajectories.png

│ ├── empty_rate_comparison.png

│ ├── battery_consumption.png

│ └── gantt_chart.png

├── scheduler.py # 核心代码

├── test_scheduler.py # 单元测试

├── README.md

└── requirements.txt

 

4.2 核心源码

 

<details>

 

<summary></summary>

 

"""

AGV任务调度优化系统 - 降低空驶里程

=================================================================

课程映射(长安大学《智能制造导论》):

  概述:高效率(减少空驶)、低成本(降低能耗)、资源优化(AGV利用率)

  技术基础:物流装备(AGV、充电桩)、传感技术(定位、电量检测)

  支撑技术:工业大数据(任务位置、轨迹数据)、人工智能(贪心算法/最近邻搜索)

  智能工厂:物流自动化(AGV自主导航)、动态调度(实时任务分配)

  演进范式:人工调度 → 固定路线 → 随机派单 → 全局优化

 

技术栈(严格):

  pandas # 数据加载、任务管理

  numpy # 数值计算:距离、得分

  matplotlib # 可视化:轨迹、柱状图、甘特图

  networkx # 路网图构建、最短路径

  scikit-learn # 无(预留聚类分析接口)

  scipy # 无(预留优化算法接口)

"""

 

from __future__ import annotations

 

import os

import math

import pickle

from dataclasses import dataclass, field

from typing import List, Dict, Tuple, Optional, Set

from enum import Enum

from pathlib import Path

 

import numpy as np

import pandas as pd

import matplotlib.pyplot as plt

import networkx as nx

from matplotlib.patches import FancyArrowPatch

 

plt.rcParams["font.sans-serif"] = ["SimHei", "DejaVu Sans"]

plt.rcParams["axes.unicode_minus"] = False

 

 

# ----------------------------------------------------------------------

# 1. 枚举与配置

# ----------------------------------------------------------------------

class NodeType(Enum):

    """节点类型"""

    WAREHOUSE = "仓库"

    WORKSTATION = "工位"

    CHARGING = "充电站"

 

 

class TaskPriority(Enum):

    """任务优先级"""

    HIGH = 3 # 停线风险

    MEDIUM = 2 # 紧急

    LOW = 1 # 普通

 

 

class AGVStatus(Enum):

    """AGV状态"""

    IDLE = "空闲"

    LOADING = "装载中"

    DELIVERING = "配送中"

    RETURNING = "返回中"

    CHARGING = "充电中"

    ERROR = "故障"

 

 

@dataclass

class SchedulingConfig:

    """调度配置"""

    # 地图参数

    map_width: float = 100.0 # 车间宽度(米)

    map_height: float = 60.0 # 车间高度(米)

    n_warehouses: int = 2

    n_workstations: int = 20

    n_charging: int = 2

    

    # AGV参数

    n_agvs: int = 5

    agv_speed: float = 1.2 # m/s

    agv_empty_consumption: float = 0.15 # %/km (空驶)

    agv_loaded_consumption: float = 0.25 # %/km (载货)

    agv_battery_capacity: float = 100.0 # %

    agv_low_battery_threshold: float = 20.0

    

    # 调度参数

    distance_weight: float = 0.5

    priority_weight: float = 0.3

    direction_weight: float = 0.2

    

    # 仿真参数

    simulation_time: float = 3600.0 # 仿真时长(秒)

    time_step: float = 10.0 # 时间步长(秒)

    

    # 路径

    data_dir: str = "data"

    results_dir: str = "results"

    model_path: str = "models/schedule_result.pth"

    

    random_seed: int = 42

 

 

# ----------------------------------------------------------------------

# 2. 数据结构

# ----------------------------------------------------------------------

@dataclass

class Node:

    """地图节点"""

    node_id: str

    x: float

    y: float

    node_type: NodeType

 

 

@dataclass

class DeliveryTask:

    """配送任务"""

    task_id: str

    start_node: str # 取料点(仓库)

    end_node: str # 送料点(工位)

    priority: TaskPriority

    deadline: float # 截止时间(仿真时间)

    created_at: float = 0.0

    assigned_agv: Optional[str] = None

    status: str = "pending" # pending, assigned, completed, expired

    distance: float = 0.0

    completed_at: float = 0.0

 

 

@dataclass

class AGV:

    """AGV小车"""

    agv_id: str

    current_node: str

    battery_level: float = 100.0

    status: AGVStatus = AGVStatus.IDLE

    current_task: Optional[DeliveryTask] = None

    task_sequence: List[DeliveryTask] = field(default_factory=list)

    trajectory: List[Tuple[float, float, float]] = field(default_factory=list) # (time, x, y)

    total_distance: float = 0.0

    empty_distance: float = 0.0

    loaded_distance: float = 0.0

 

 

# ----------------------------------------------------------------------

# 3. 地图构建器

# ----------------------------------------------------------------------

class MapBuilder:

    """车间地图构建器"""

    

    def __init__(self, config: SchedulingConfig):

        self.config = config

        self.nodes: Dict[str, Node] = {}

        self.graph = nx.DiGraph()

        self.distance_matrix: Dict[Tuple[str, str], float] = {}

    

    def generate_map(self):

        """生成车间地图"""

        print(f"[INFO] 生成车间地图...")

        np.random.seed(self.config.random_seed)

        

        # 1. 仓库(左上角、右下角)

        for i in range(self.config.n_warehouses):

            x = 5.0 if i == 0 else self.config.map_width - 5.0

            y = 5.0 if i == 0 else self.config.map_height - 5.0

            node_id = f"W{i+1}"

            self.nodes[node_id] = Node(node_id, x, y, NodeType.WAREHOUSE)

        

        # 2. 工位(网格分布)

        ws_per_row = 5

        ws_rows = self.config.n_workstations // ws_per_row

        for i in range(self.config.n_workstations):

            row = i // ws_per_row

            col = i % ws_per_row

            x = 20.0 + col * (self.config.map_width - 40) / (ws_per_row - 1)

            y = 15.0 + row * (self.config.map_height - 30) / max(ws_rows - 1, 1)

            node_id = f"S{i+1:02d}"

            self.nodes[node_id] = Node(node_id, x, y, NodeType.WORKSTATION)

        

        # 3. 充电站(中间区域)

        for i in range(self.config.n_charging):

            x = self.config.map_width / 2 + (i - 0.5) * 10

            y = self.config.map_height / 2

            node_id = f"C{i+1}"

            self.nodes[node_id] = Node(node_id, x, y, NodeType.CHARGING)

        

        # 4. 构建networkx有向图

        for node in self.nodes.values():

            self.graph.add_node(node.node_id, x=node.x, y=node.y, type=node.node_type.value)

        

        # 5. 添加边(双向通道,部分单向)

        node_list = list(self.nodes.keys())

        for i, n1 in enumerate(node_list):

            for j, n2 in enumerate(node_list):

                if i != j:

                    # 距离阈值:超过50米不直接连接

                    dist = self._euclidean_distance(self.nodes[n1], self.nodes[n2])

                    if dist < 50.0:

                        # 80%概率双向,20%概率单向

                        if np.random.rand() < 0.8:

                            self.graph.add_edge(n1, n2, weight=dist)

                            self.graph.add_edge(n2, n1, weight=dist)

                        else:

                            self.graph.add_edge(n1, n2, weight=dist)

        

        print(f" 节点数: {len(self.nodes)}")

        print(f" 边数: {self.graph.number_of_edges()}")

        

        # 6. 计算所有节点对最短路径距离

        self._compute_distance_matrix()

        

        return self.nodes, self.graph

    

    def _euclidean_distance(self, n1: Node, n2: Node) -> float:

        """欧氏距离"""

        return math.sqrt((n1.x - n2.x)**2 + (n1.y - n2.y)**2)

    

    def _compute_distance_matrix(self):

        """计算所有节点对最短路径距离"""

        print(f" 计算最短路径距离矩阵...")

        for n1 in self.nodes:

            for n2 in self.nodes:

                if n1 != n2:

                    try:

                        dist = nx.shortest_path_length(self.graph, n1, n2, weight="weight")

                    except nx.NetworkXNoPath:

                        dist = self._euclidean_distance(self.nodes[n1], self.nodes[n2])

                    self.distance_matrix[(n1, n2)] = dist

    

    def get_distance(self, n1: str, n2: str) -> float:

        """获取两节点间最短距离"""

        if n1 == n2:

            return 0.0

        return self.distance_matrix.get((n1, n2), float('inf'))

 

 

# ----------------------------------------------------------------------

# 4. 任务生成器

# ----------------------------------------------------------------------

class TaskGenerator:

    """配送任务生成器"""

    

    def __init__(self, config: SchedulingConfig, nodes: Dict[str, Node]):

        self.config = config

        self.nodes = nodes

        self.warehouses = [n for n in nodes.values() if n.node_type == NodeType.WAREHOUSE]

        self.workstations = [n for n in nodes.values() if n.node_type == NodeType.WORKSTATION]

    

    def generate_tasks(self, n_tasks: int = 30, start_time: float = 0.0) -> List[DeliveryTask]:

        """生成配送任务"""

        print(f"[INFO] 生成 {n_tasks} 个配送任务...")

        np.random.seed(self.config.random_seed)

        

        tasks = []

        for i in range(n_tasks):

            # 随机起点(仓库)

            start_node = np.random.choice(self.warehouses).node_id

            

            # 随机终点(工位)

            end_node = np.random.choice(self.workstations).node_id

            

            # 优先级(泊松分布:大部分普通,少量紧急)

            priority_rand = np.random.rand()

            if priority_rand < 0.1:

                priority = TaskPriority.HIGH

                deadline = start_time + 300 # 5分钟内

            elif priority_rand < 0.3:

                priority = TaskPriority.MEDIUM

                deadline = start_time + 600 # 10分钟内

            else:

                priority = TaskPriority.LOW

                deadline = start_time + 1200 # 20分钟内

            

            task = DeliveryTask(

                task_id=f"T{i+1:03d}",

                start_node=start_node,

                end_node=end_node,

                priority=priority,

                deadline=deadline,

                created_at=start_time + np.random.exponential(30) # 任务到达间隔

            )

            tasks.append(task)

        

        # 按创建时间排序

        tasks.sort(key=lambda t: t.created_at)

        

        print(f" 任务生成完成:")

        print(f" HIGH: {sum(1 for t in tasks if t.priority == TaskPriority.HIGH)}")

        print(f" MEDIUM: {sum(1 for t in tasks if t.priority == TaskPriority.MEDIUM)}")

        print(f" LOW: {sum(1 for t in tasks if t.priority == TaskPriority.LOW)}")

        

        return tasks

 

 

# ----------------------------------------------------------------------

# 5. AGV车队

# ----------------------------------------------------------------------

class AGVFleet:

    """AGV车队管理"""

    

    def __init__(self, config: SchedulingConfig, nodes: Dict[str, Node], map_builder: MapBuilder):

        self.config = config

        self.nodes = nodes

        self.map_builder = map_builder

        self.agvs: Dict[str, AGV] = {}

        self._init_fleet()

    

    def _init_fleet(self):

        """初始化AGV"""

        print(f"[INFO] 初始化 {self.config.n_agvs} 台AGV...")

        

        warehouses = [n for n in self.nodes.values() if n.node_type == NodeType.WAREHOUSE]

        charging = [n for n in self.nodes.values() if n.node_type == NodeType.CHARGING]

        

        for i in range(self.config.n_agvs):

            # 初始位置:仓库或充电站

            if i < len(warehouses):

                start_node = warehouses[i % len(warehouses)].node_id

            else:

                start_node = charging[(i - len(warehouses)) % len(charging)].node_id

            

            agv = AGV(

                agv_id=f"A{i+1}",

                current_node=start_node,

                battery_level=self.config.agv_battery_capacity - np.random.uniform(0, 20)

            )

            # 初始轨迹点

            node = self.nodes[start_node]

            agv.trajectory.append((0.0, node.x, node.y))

            self.agvs[agv.agv_id] = agv

        

        print(f" AGV初始化完成")

    

    def get_idle_agvs(self) -> List[AGV]:

        """获取空闲AGV"""

        return [agv for agv in self.agvs.values() if agv.status == AGVStatus.IDLE]

    

    def get_available_agvs(self) -> List[AGV]:

        """获取可用AGV(空闲或即将空闲)"""

        return [agv for agv in self.agvs.values() 

                if agv.status in [AGVStatus.IDLE, AGVStatus.RETURNING]]

 

 

# ----------------------------------------------------------------------

# 6. 调度器

# ----------------------------------------------------------------------

class Scheduler:

    """AGV任务调度器(贪心算法)"""

    

    def __init__(self, config: SchedulingConfig, map_builder: MapBuilder):

        self.config = config

        self.map_builder = map_builder

        self.strategy = "greedy" # greedy, fifo

    

    def calculate_score(self, agv: AGV, task: DeliveryTask, current_time: float) -> float:

        """计算任务分配得分(越高越优先)"""

        # 1. 距离得分(越近得分越高)

        dist_to_start = self.map_builder.get_distance(agv.current_node, task.start_node)

        dist_start_to_end = self.map_builder.get_distance(task.start_node, task.end_node)

        total_dist = dist_to_start + dist_start_to_end

        

        if total_dist == float('inf'):

            return -float('inf')

        

        distance_score = 1.0 / (1.0 + total_dist / 100.0)

        

        # 2. 优先级得分

        priority_score = {

            TaskPriority.HIGH: 1.0,

            TaskPriority.MEDIUM: 0.6,

            TaskPriority.LOW: 0.3

        }[task.priority]

        

        # 3. 方向得分(终点方向和下一个任务方向一致)

        direction_score = 0.5 # 默认中等

        if agv.current_task:

            # 计算当前任务终点方向

            current_end = self.map_builder.nodes[agv.current_task.end_node]

            task_start = self.map_builder.nodes[task.start_node]

            # 简化:如果任务起点在当前终点附近,得分高

            dist = math.sqrt((current_end.x - task_start.x)**2 + (current_end.y - task_start.y)**2)

            direction_score = 1.0 / (1.0 + dist / 50.0)

        

        # 4. 电量因子

        battery_factor = 1.0

        if agv.battery_level < self.config.agv_low_battery_threshold:

            # 电量低时,只分配近距离任务

            if total_dist > 30:

                battery_factor = 0.1

        

        # 综合得分

        score = (

            self.config.distance_weight * distance_score +

            self.config.priority_weight * priority_score +

            self.config.direction_weight * direction_score

        ) * battery_factor

        

        return score

    

    def assign_tasks_greedy(self, agvs: List[AGV], tasks: List[DeliveryTask], 

                           current_time: float) -> Dict[str, List[DeliveryTask]]:

        """贪心分配任务"""

        assignments: Dict[str, List[DeliveryTask]] = {agv.agv_id: [] for agv in agvs}

        

        # 待分配任务(未过期、未分配)

        pending_tasks = [t for t in tasks 

                       if t.status == "pending" and t.deadline > current_time]

        

        # 按优先级排序

        pending_tasks.sort(key=lambda t: (t.pr

利用AI解决实际问题,如果你觉得这个工具好用,欢迎关注长安牧笛!

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值