Python遗传算法解决作业调度问题(JSP)

2020年12月9日更新

感谢谭同学发现的bug,由于后期绘制甘特图的时候是依靠索引来取工件数和机器数,所以会导致工件或者机器>9的时候绘图失败(进化算法本身没问题),针对于此,采取正则表达式匹配Ci_j中的i(工件)与j(机器)。
更新后两个版本的代码进化算法本身没问题可以放心用,但是旧版本的甘特图不完美,新版本在文末
以下为旧码,

import matplotlib.pyplot as plt
from collections import OrderedDict
import random
import copy
import datetime

jobs = 5  # 工件数
machines = 4  # 机器数
population_num = 10  # 种群规模
population = []  # 初始种群
variation_rate = 0.8  # 变异率
iters = 50  # 进化次数
target_points = [1, 2, 3]  # 变异靶点

# 初始化一个种群
for i in range(population_num):
    population.append(random.sample(range(1, jobs + 1), jobs))

time_table = [[31, 41, 25, 30],  # 加工时间表,工件为行,机器为列
              [19, 55, 3, 34],
              [23, 42, 27, 6],
              [13, 22, 14, 13],
              [33, 5, 57, 19]]

# 产生随机解
random.seed(0)


# 定义工作节点类 name为Cij:第i个工件在第j个机器上加工,StartTime为开始时间,LoadTime为加工时间,EndTime为加工结束时间
class Cij:
    def __init__(self, name, StartTime, LoadTime):
        self.name = name
        self.StartTime = StartTime
        self.LoadTime = LoadTime
        self.EndTime = StartTime + LoadTime



# 定义最大流程时间函数
def c_max(n):
    # 循环赋值函数,将工件数,机器数与加工时间进行绑定
    for job, i in enumerate(time_table):
        for machine, loadtime in enumerate(i):
            locals()['c{}_{}'.format(job + 1, machine + 1)] = Cij(name='c{}_{}'.format(job + 1, machine + 1),
                                                                  StartTime=0, LoadTime=loadtime, )
    # 加工流程记录表
    load_time_tables = []
    for num, job in enumerate(n):
        for machine in range(machines + 1):
            if num == 0 and machine == 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = 0
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])
            elif num == 0 and machine > 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = locals()['c{}_{}'.format(job, machine - 1)].EndTime
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])
            elif num > 0 and machine == 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = locals()[
                    'c{}_{}'.format(n[num - 1], machine)].EndTime
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])

            elif num > 0 and machine > 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = max(
                    locals()['c{}_{}'.format(n[num - 1], machine)].EndTime,
                    locals()['c{}_{}'.format(job, machine - 1)].EndTime)
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])
    return load_time_tables, load_time_tables[-1][-1][-1]


def fitness(n):
    return 1 / (c_max(n)[1])


# 定义节点类state为当前解向量,封装当前解的排列方式,加工时间状况,最大加工时间以及适应度
class node:
    def __init__(self, state):
        self.state = state
        self.load_table = c_max(state)[0]
        self.makespan = c_max(state)[1]
        self.fitness = fitness(state)


# PMX两点交叉函数
def two_points_cross(a1, a2):
    # 不改变原始数据进行操作
    a1_1 = copy.deepcopy(a1)
    a2_1 = copy.deepcopy(a2)
    # 交叉位置,point1<point2
    point1 = random.randint(0, len(a1_1))
    point2 = random.randint(0., len(a1_1))
    while point1 > point2 or point1 == point2:
        point1 = random.randint(0, len(a1_1))
        point2 = random.randint(0., len(a1_1))
    # 记录交叉项
    fragment1 = a1[point1:point2]
    fragment2 = a2[point1:point2]
    # 交叉
    a1_1[point1:point2], a2_1[point1:point2] = a2_1[point1:point2], a1_1[point1:point2]
    # 定义容器
    a1_2 = []  # 储存修正后的head
    a2_2 = []
    a1_3 = []  # 修正后的tail
    a2_3 = []
    # 子代1头部修正
    for i in a1_1[:point1]:
        while i in fragment2:
            i = fragment1[fragment2.index(i)]
        a1_2.append(i)
    # 子代2尾部修正
    for i in a1_1[point2:]:
        while i in fragment2:
            i = fragment1[fragment2.index(i)]
        a1_3.append(i)
    # 子代2头部修订
    for i in a2_1[:point1]:
        while i in fragment1:
            i = fragment2[fragment1.index(i)]
        a2_2.append(i)
    # 子代2尾部修订
    for i in a2_1[point2:]:
        while i in fragment1:
            i = fragment2[fragment1.index(i)]
        a2_3.append(i)

    child1 = a1_2 + fragment2 + a1_3
    child2 = a2_2 + fragment1 + a2_3
    # print('修正后的子代为:\n{}\n{}'.format(child1, child2))
    return child1, child2


######变异函数######

# 交换变异
def gene_exchange(n):
    point1 = random.randint(0, len(n) - 1)
    point2 = random.randint(0, len(n) - 1)
    while point1 == point2 or point1 > point2:
        point1 = random.randint(0, len(n) - 1)
        point2 = random.randint(0, len(n) - 1)
    n[point1], n[point2] = n[point2], n[point1]
    return n


# 插入变异
def gene_insertion(n):
    point1 = random.randint(0, len(n) - 1)
    point2 = random.randint(0, len(n) - 1)
    while point1 == point2:
        point1 = random.randint(0, len(n) - 1)
        point2 = random.randint(0, len(n) - 1)
    x = n.pop(point1)
    n.insert(point2, x)
    return n


# 局部逆序变异
def gene_reverse(n):
    point1 = random.randint(0, len(n) - 1)
    point2 = random.randint(0, len(n) - 1)
    while point1 == point2 or point1 > point2:
        point1 = random.randint(0, len(n) - 1)
        point2 = random.randint(0, len(n) - 1)
    ls_res = n[point1:point2]
    ls_res.reverse()
    l1 = n[:point1]
    l2 = n[point2:]
    n_res_end = l1 + ls_res + l2
    return n_res_end


solution_list = []  # 存放noda解结点类
for i in population:
    locals()['solution{}'.format(population.index(i))] = node(i)
    solution_list.append(locals()['solution{}'.format(population.index(i))])
# 循环开始
start = datetime.datetime.now()
for i in range(iters):
    if i % 10 == 0:
        print('第{}次进化后的最优加工时间为{}'.format(i, solution_list[0].makespan))
    pops = [i.state for i in solution_list]
    pop_children1 = pops[1::2]  # 偶数解
    pop_children2 = pops[::2]  # 奇数解
    # PMX两点交叉变异
    for i in range(len(pop_children1)):
        pop_children1[i], pop_children2[i] = two_points_cross(pop_children1[i], pop_children2[i])
    # 交叉后的子种群
    cross_population = pop_children1 + pop_children2
    # 变异
    for i in cross_population:
        mutation_rate = random.random()
        if mutation_rate > variation_rate:
            target = random.choice(target_points)
            if target == 1:
                cross_population[cross_population.index(i)] = gene_exchange(i)
            elif target == 2:
                cross_population[cross_population.index(i)] = gene_insertion(i)
            else:
                cross_population[cross_population.index(i)] = gene_reverse(i)
    # 选择
    cross_solution = [node(i) for i in cross_population]
    solution_list = solution_list + cross_solution
    solution_list.sort(key=lambda x: x.makespan)
    del solution_list[population_num:]
print('进化完成,最终最优加工时间为:', solution_list[0].makespan)
end = datetime.datetime.now()
print('耗时{}'.format(end - start))
# 绘制甘特图
colors = ['pink', 'cyan', 'violet', 'moccasin', 'lightgreen']  # 甘特图颜色
for i in solution_list[0].load_table:
    print(i)
    plt.barh(y=i[0][-1], left=i[1][0], width=i[1][-1] - i[1][0], height=0.5, color=colors[eval(i[0][1]) - 1],
             label='job{}'.format(i[0][1]))
plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文标签
plt.rcParams['font.serif'] = ['KaiTi']
plt.rcParams['axes.unicode_minus'] = False
plt.title('jsp最优调度甘特图')
plt.xlabel('加工时间')
plt.ylabel('加工机器')
handles, labels = plt.gca().get_legend_handles_labels()  # 标签去重
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys())
plt.show()


以下为新码:


import matplotlib.pyplot as plt
from collections import OrderedDict
import random
import copy
import datetime
import numpy as np
import re

jobs = 25  # 工件数
machines = 10  # 机器数
population_num = 10  # 种群规模
population = []  # 初始种群
variation_rate = 0.8  # 变异率
iters = 50  # 进化次数
target_points = [1, 2, 3]  # 变异靶点

# 初始化一个种群
for i in range(population_num):
    population.append(random.sample(range(1, jobs + 1), jobs))


'''time_table = [[31, 41, 25, 30],  # 加工时间表,工件为行,机器为列
              [19, 55, 3, 34],
              [23, 42, 27, 6],
              [13, 22, 14, 13],
              [33, 5, 57, 19]]'''
time_table = np.random.randint(1, 100, (jobs, machines))  # 加工时间表,10*5
# 产生随机解
random.seed(0)


# 定义工作节点类 name为Cij:第i个工件在第j个机器上加工,StartTime为开始时间,LoadTime为加工时间,EndTime为加工结束时间
class Cij:
    def __init__(self, name, StartTime, LoadTime):
        self.name = name
        self.StartTime = StartTime
        self.LoadTime = LoadTime
        self.EndTime = StartTime + LoadTime



# 定义最大流程时间函数
def c_max(n):
    # 循环赋值函数,将工件数,机器数与加工时间进行绑定
    for job, i in enumerate(time_table):
        for machine, loadtime in enumerate(i):
            locals()['c{}_{}'.format(job + 1, machine + 1)] = Cij(name='c{}_{}'.format(job + 1, machine + 1),
                                                                  StartTime=0, LoadTime=loadtime, )
    # 加工流程记录表
    load_time_tables = []
    for num, job in enumerate(n):
        for machine in range(machines + 1):
            if num == 0 and machine == 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = 0
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])
            elif num == 0 and machine > 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = locals()['c{}_{}'.format(job, machine - 1)].EndTime
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])
            elif num > 0 and machine == 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = locals()[
                    'c{}_{}'.format(n[num - 1], machine)].EndTime
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])

            elif num > 0 and machine > 1:
                locals()['c{}_{}'.format(job, machine)].StartTime = max(
                    locals()['c{}_{}'.format(n[num - 1], machine)].EndTime,
                    locals()['c{}_{}'.format(job, machine - 1)].EndTime)
                locals()['c{}_{}'.format(job, machine)].EndTime = locals()['c{}_{}'.format(job, machine)].StartTime + \
                                                                  locals()['c{}_{}'.format(job, machine)].LoadTime
                load_time_tables.append([locals()['c{}_{}'.format(job, machine)].name, [
                    locals()['c{}_{}'.format(job, machine)].StartTime,
                    locals()['c{}_{}'.format(job, machine)].EndTime]])
    return load_time_tables, load_time_tables[-1][-1][-1]


def fitness(n):
    return 1 / (c_max(n)[1])


# 定义节点类state为当前解向量,封装当前解的排列方式,加工时间状况,最大加工时间以及适应度
class node:
    def __init__(self, state):
        self.state = state
        self.load_table = c_max(state)[0]
        self.makespan = c_max(state)[1]
        self.fitness = fitness(state)


# PMX两点交叉函数
def two_points_cross(a1, a2):
    # 不改变原始数据进行操作
    a1_1 = copy.deepcopy(a1)
    a2_1 = copy.deepcopy(a2)
    # 交叉位置,point1<point2
    point1 = random.randint(0, len(a1_1))
    point2 = random.randint(0., len(a1_1))
    while point1 > point2 or point1 == point2:
        point1 = random.randint(0, len(a1_1))
        point2 = random.randint(0., len(a1_1))
    # 记录交叉项
    fragment1 = a1[point1:point2]
    fragment2 = a2[point1:point2]
    # 交叉
    a1_1[point1:point2], a2_1[point1:point2] = a2_1[point1:point2], a1_1[point1:point2]
    # 定义容器
    a1_2 = []  # 储存修正后的head
    a2_2 = []
    a1_3 = []  # 修正后的tail
    a2_3 = []
    # 子代1头部修正
    for i in a1_1[:point1]:
        while i in fragment2:
            i = fragment1[fragment2.index(i)]
        a1_2.append(i)
    # 子代2尾部修正
    for i in a1_1[point2:]:
        while i in fragment2:
            i = fragment1[fragment2.index(i)]
        a1_3.append(i)
    # 子代2头部修订
    for i in a2_1[:point1]:
        while i in fragment1:
            i = fragment2[fragment1.index(i)]
        a2_2.append(i)
    # 子代2尾部修订
    for i in a2_1[point2:]:
        while i in fragment1:
            i = fragment2[fragment1.index(i)]
        a2_3.append(i)

    child1 = a1_2 + fragment2 + a1_3
    child2 = a2_2 + fragment1 + a2_3
    # print('修正后的子代为:\n{}\n{}'.format(child1, child2))
    return child1, child2


######变异函数######

# 交换变异
def gene_exchange(n):
    point1 = random.randint(0, len(n) - 1)
    point2 = random.randint(0, len(n) - 1)
    while point1 == point2 or point1 > point2:
        point1 = random.randint(0, len(n) - 1)
        point2 = random.randint(0, len(n) - 1)
    n[point1], n[point2] = n[point2], n[point1]
    return n


# 插入变异
def gene_insertion(n):
    point1 = random.randint(0, len(n) - 1)
    point2 = random.randint(0, len(n) - 1)
    while point1 == point2:
        point1 = random.randint(0, len(n) - 1)
        point2 = random.randint(0, len(n) - 1)
    x = n.pop(point1)
    n.insert(point2, x)
    return n


# 局部逆序变异
def gene_reverse(n):
    point1 = random.randint(0, len(n) - 1)
    point2 = random.randint(0, len(n) - 1)
    while point1 == point2 or point1 > point2:
        point1 = random.randint(0, len(n) - 1)
        point2 = random.randint(0, len(n) - 1)
    ls_res = n[point1:point2]
    ls_res.reverse()
    l1 = n[:point1]
    l2 = n[point2:]
    n_res_end = l1 + ls_res + l2
    return n_res_end


solution_list = []  # 存放noda解结点类
for i in population:
    locals()['solution{}'.format(population.index(i))] = node(i)
    solution_list.append(locals()['solution{}'.format(population.index(i))])
# 循环开始
start = datetime.datetime.now()
for i in range(iters):
    if i % 10 == 0:
        print('第{}次进化后的最优加工时间为{}'.format(i, solution_list[0].makespan))
    pops = [i.state for i in solution_list]
    pop_children1 = pops[1::2]  # 偶数解
    pop_children2 = pops[::2]  # 奇数解
    # PMX两点交叉变异
    for i in range(len(pop_children1)):
        pop_children1[i], pop_children2[i] = two_points_cross(pop_children1[i], pop_children2[i])
    # 交叉后的子种群
    cross_population = pop_children1 + pop_children2
    # 变异
    for i in cross_population:
        mutation_rate = random.random()
        if mutation_rate > variation_rate:
            target = random.choice(target_points)
            if target == 1:
                cross_population[cross_population.index(i)] = gene_exchange(i)
            elif target == 2:
                cross_population[cross_population.index(i)] = gene_insertion(i)
            else:
                cross_population[cross_population.index(i)] = gene_reverse(i)
    # 选择
    cross_solution = [node(i) for i in cross_population]
    solution_list = solution_list + cross_solution
    solution_list.sort(key=lambda x: x.makespan)
    del solution_list[population_num:]
print('进化完成,最终最优加工时间为:', solution_list[0].makespan)
end = datetime.datetime.now()
print('耗时{}'.format(end - start))
# 绘制甘特图
def color():# 甘特图颜色生成函数
    color_ls=['0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F']
    col=''
    for i in range(6):
        col+=random.choice(color_ls)
    return '#'+col
colors = [color() for i in range(jobs)]# 甘特图颜色列表
for i in solution_list[0].load_table:
    print(i)
    y=eval(re.findall('_(\d+)',i[0])[0])#正则表达式匹配工件数
    label=re.findall(r'(\d*?)_',i[0])[0]#正则表达式匹配机器数
    plt.barh(y=y, left=i[1][0], width=i[1][-1] - i[1][0], height=0.5, color=colors[eval(label) - 1],
             label=f'job{label}')
plt.rcParams['font.sans-serif'] = ['SimHei']  # 显示中文标签
plt.rcParams['font.serif'] = ['KaiTi']
plt.rcParams['axes.unicode_minus'] = False
plt.title('jsp最优调度甘特图')
plt.xlabel('加工时间')
plt.ylabel('加工机器')
handles, labels = plt.gca().get_legend_handles_labels()  # 标签去重
by_label = OrderedDict(zip(labels, handles))
plt.legend(by_label.values(), by_label.keys())
plt.show()

再次感谢小谭同学!

  • 17
    点赞
  • 95
    收藏
    觉得还不错? 一键收藏
  • 10
    评论
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值