Single Source Capacity Facility Location Problem

本篇博客涉及贪心算法以及模拟退火算法解决单源设施选址问题


问题描述

Suppose there are n facilities and m customers. We wish to choose:
(1) which of the n facilities to open
(2) the assignment of customers to facilities
The objective is to minimize the sum of the opening cost and the assignment cost.
The total demand assigned to a facility must not exceed its capacity.

数据集格式

数据集

此处AssignmentCost 有No.Customer行,No.Facility列。即每个用户对于不同的设备有不同的Assignment cost

思路分析

思路分析

目标

贪心算法

贪心算法是比较简单但不能够获得最优解的方法。使用贪心算法可以考虑较少的情况。我在贪心算法的编写中,忽略了对于用户分配顺序的考虑,直接从前到后进行分配。对于每个用户,首先通过用户的需求DEMAND来筛选出可用的设备,接着通过比较这些设备的opencost以及用户在这些设备上的assignment cost,选出其中的最小值。
显然,贪心算法得到的解并不是最好,并且可能导致后续用户没有可用分配

模拟退火算法

模拟退火
在本题中,我选择将所有用户的设备分配情况作为一个design,而总的opencost + assignment cost则作为目标方程。在生成新的Design时,一般情况下是选择当前解的领域解,但需要考虑这样的情况
新解的产生

因此,一直从后往前选择的话可能会变成下山法,所以我采用直接从前往后进行选择。即产生的解为随机解

代码实现

  1. 初始化
import random
import math
import matplotlib.pyplot as plt

CAPACITY = []
OPENCOST = []
ASSIGNCOST = []
OPENSTATUS = []
DEMAND = []
FACNUM = 0
CUSNUM = 0
Num = 0

#读取文件,获取各项变量
def init(filepath):
    with open(filepath, 'r') as f:
        global FACNUM
        global CUSNUM
        global CAPACITY
        global OPENCOST
        global ASSIGNCOST
        global OPENSTATUS
        FACNUM, CUSNUM = f.readline().strip("\n").split()
        FACNUM = int(FACNUM)
        CUSNUM = int(CUSNUM)
        OPENSTATUS = [0] * FACNUM
        for i in range(FACNUM):
            line = f.readline().strip("\n").split()
            CAPACITY.append(int(line[0]))
            OPENCOST.append(int(line[1]))

        for i in range(int(CUSNUM / 10)):
            line = f.readline().strip("\n").replace(' ', '').split(".")
            for j in range(10):
                DEMAND.append(int(line[j]))

        for i in range(CUSNUM):
            linetoread = int(FACNUM / 10)
            temp = []
            for j in range(linetoread):
                line = f.readline().strip("\n").replace(' ', '').split(".")
                for k in range(10):
                    temp.append(int(line[k]))

            ASSIGNCOST.append(temp)

    print(ASSIGNCOST)


  1. 贪心算法
def greedy():
    global CAPACITY

    capacity = CAPACITY.copy()
    openstatus = [0] * FACNUM
    totalcost = 0
    assign = [-1] * CUSNUM
    assigncost = 0
    opencost = 0

    for cus in range(CUSNUM):
    
        facIndex = []       # the facilities may be chosen
        for i in range(FACNUM):
            if capacity[i] >= DEMAND[cus]:
                facIndex.append(i)

        # ASSIGNCOST for customer i
        asforeach = ASSIGNCOST[cus]

        # ASSIGNCOST + OPENCOST for customer i
        temp = [sum(x) for x in zip(asforeach, OPENCOST)]

        # select the min cost index
        sindex = facIndex[0]
        for i in facIndex:
            if temp[i] < temp[sindex]:
                sindex = i

        openstatus[sindex] = 1
        capacity[sindex] = capacity[sindex] - DEMAND[cus]
        assign[cus] = sindex

        assigncost += asforeach[sindex]

    for i in range(FACNUM):
        opencost += openstatus[i] * OPENCOST[i]

    totalcost = assigncost + opencost

    print(assign)
    print("assignment cost: %d"%assigncost)
    print("opencost: %d"%opencost)
    print(totalcost)
    print("openstatus: {}".format(openstatus))

    return assign

  1. 模拟退火
class Anneal:
    assign = []  # list of Customer's choice , len = NumofCus
    capacity = []
    openstatus = []
    totalcost = 0
    assigncost = 0
    opencost = 0

    def __init__(self):
        self.openstatus = [0] * FACNUM

        self.assign = [-1] * CUSNUM
        # 注意!!list的赋值是传引用,不是真正的copy。应该换为list.copy
        self.capacity = CAPACITY.copy()

        for i in range(CUSNUM):
            available = []
            for j in range(FACNUM):
                if self.capacity[j] >= DEMAND[i]:
                    available.append(j)


            choose = random.choice(available)
            self.capacity[choose] = self.capacity[choose] - DEMAND[i]
            self.assign[i] = choose

            self.assigncost += ASSIGNCOST[i][self.assign[i]]
            self.openstatus[self.assign[i]] = 1

        for j in range(FACNUM):
            self.opencost += self.openstatus[j] * OPENCOST[j]

        self.totalcost = self.assigncost + self.opencost



    def f(self):
        return self.totalcost


def simulated_Annealing():
    # Number of cycles
    n = 50
    # Number of trials per cycle
    m = 50
    # Number of accepted solutions
    na = 0.0
    # Probability of accepting worse solution at the start
    p1 = 0.7
    # Probability of accepting worse solution at the end
    p50 = 0.001
    # Initial temperature
    t1 = -1.0 / math.log(p1)
    # Final temperature
    t50 = -1.0 / math.log(p50)
    # Fractional reduction every cycle
    frac = (t50 / t1) ** (1.0 / (n - 1.0))

    # Initialize x
    x = []
    newassign = Anneal()
    x.append(newassign)

    xi = x[0]
    na = na + 1.0

    # Current best results so far
    xc = x[0]
    fc = xc.f()
    
    fs = []
    fs.append(fc)

    # Current temperature
    t = t1

    # delta Average
    delta_avg = 0.0

    for i in range(n):
        print('Cycle: ' + str(i) + ' with Temperature: ' + str(t))
        for j in range(m):
            # Generate new trial points
            xi = Anneal()
            delta = abs(xi.f() - fc)

            if xi.f() > fc:
                # Initialize delta_avg if a worse solution was found
                #   on the first iteration
                if i == 0 and j == 0:
                    delta_avg = delta

                # objective function is worse
                # generate probability of acceptance
                p = math.exp(-delta / (delta_avg * t))

                # determine whether to accept worse point
                if random.random() < p:
                    # accept the worse solution
                    accept = True
                else:
                    # don't accept the worse solution
                    accept = False
            else:
                accept = True

            if accept is True:
                xc = xi
                fc = xi.f()
                # increment number of accepted solutions
                na = na + 1.0
                # update delta_avg
                delta_avg = (delta_avg * (na - 1.0) + delta) / na

        # Record the best x values at the end of every cycle
        x.append(xc)
        fs.append(fc)

        # Lower the temperature for next cycle
        t = frac * t

    # print solution
    print('Best solution: ' + str(xc.assign))
    print('Best objective: ' + str(fc))
    print('assign cost: %d' % xc.assigncost)
    print('opencost: %d' % xc.opencost)
    print('openstatus:{}'.format(xc.openstatus))

    fig = plt.figure()
    ax1 = fig.add_subplot(211)
    ax1.plot(fs, 'r.-')
    ax1.legend(['Objective'])
    ax2 = fig.add_subplot(212)
    ax2.plot(xc.assign, 'b.')
    ax2.legend(['assignment'])

    # Save the figure as a PNG
    plt.savefig('iterations.png')

    plt.show()

运行结果

  1. 贪心
    greedy结果

  2. 模拟退火
    退火结果
    模拟退火结果

  3. 所有测例结果

测例 result time
贪心 模拟退火 贪心 模拟退火
p1 8498 19645 0.00076 2.209
p2 9041 19691 0.00059 1.232673
p3 11041 20656 0.00059 1.141639
p4 13041 23497 0.000398 1.189788
p5 13183 21249 0.00036 1.217148
p6 9513 18960 0.00036 1.195
p7 11513 22057 0.00036 1.179
p8 11432 21032 0.00045 1.167
p9 11245 21321 0.00045 1.232
p10 12113 21002 0.0033 1.214
p11 14097 17937 0.0002388887793277094 2.1204225189034434
p12 9718 21400 0.0008501598521910548 1.7513507015347138
p13 8267 18636 0.0007761457421708519 1.3833779247258877
p14 10779 19245 0.00025261508807137206 1.8370976323881316
p15 12259 22203 0.0003596301842028789 1.4753091504308617
p16 8337 23187 0.00024378627460451438 1.522405570563798
p17 8293 22673 0.0003703139043843042 1.683500707666125
p18 13726 21101 0.0008365639087288284 2.37975234090938
p19 8435 17830 0.0007232209484137225 1.7592657987186129
p20 11040 23192 0.0002757067721610368 2.246349242969258
p21 12700 19958 0.0005068108918336679 1.7403384749015314
p22 9979 21992 0.0001109954649953437 1.3276989768748344
p23 9647 18544 0.0005626644716672123 1.6609951905584563
p24 10639 17472 0.0007133010391008938 1.1590103877223343
p25 12707 22540 0.0005347218749303618 1.3111166520920643
p26 12423 19113 0.0005425276308653035 1.6612034356782832
p27 12590 22931 0.0004119945511482627 2.044352333196343
p28 11963 20755 0.00010553246875135694 1.793188627276194
p29 9789 23063 0.000728669534581603 1.4637117678613822
p30 10331 18546 0.0003385151801413741 1.9078472374750857
p31 9418 18394 0.00014250504393344672 2.067755491745352
p32 9167 22364 0.00032594163151644445 1.2634427849114496
p33 13952 17737 0.00047667496710894144 2.3784752175025874
p34 12102 19043 0.0002664715858721881 1.5072313865400668
p35 11184 19593 0.0004445802079924833 1.4873528144020982
p36 12351 16494 0.00024331209416919293 2.2408305779842292
p37 14014 20967 0.0007356799447518672 1.531007004413978
p38 13083 18789 0.0016665679956422218 2.7363278891088365
p39 12245 22108 0.0025178354490367613 2.22419974572203
p40 14020 22036 0.00287006026475386 2.570584215852868
p41 12913 20110 0.0022252554407345315 3.328531688044958
p42 12429 21013 0.002877983931398792 2.7221039224684214
p43 10553 21954 0.0020935759633520486 2.3940863372930363
p44 11393 18229 0.0018149032112489746 3.29131842441069
p45 12520 19987 0.001411162815252279 2.7629983863571765
p46 14666 18857 0.0020177059505604887 2.35614329460681
p47 11311 22473 0.0019542696251172538 2.54853823974901
p48 11847 21092 0.001281688229963093 2.9401093364080664
p49 12483 21570 0.0016020646271611451 2.469400858094551
p50 12041 20566 0.0013875142367434335 2.545814887398914
p51 14868 19644 0.001984131684255792 3.031923160336729
p52 11250 22197 0.002190513911085248 2.784567423266155
p53 19266 40815 0.0037283588827462053 4.734696141082202
p54 16538 37977 0.005091494117570511 4.962815956683496
p55 17090 30191 0.0049373375322601075 4.622363943876621
p56 20189 22027 0.004862308752880511 6.13632113300098
p57 16250 32534 0.004371748178831857 4.574235790074207
p58 19499 36915 0.0057753951282135925 4.8478838019477
p59 20624 33722 0.0036656781000750528 4.117314251202088
p60 19843 29409 0.0037484529098043584 5.5945888299197355
p61 16596 41807 0.005518804656960051 4.659352047542419
p62 18967 26603 0.004645578163212637 5.324560150699286
p63 17402 32011 0.004319263096421779 6.074201180149529
p64 20762 37708 0.003347568556529948 6.364104417141454
p65 17117 36609 0.0041139002438534905 4.248342713149869
p66 17755 24607 0.003946885862667265 5.359500050019501
p67 17999 36491 0.005787445328384771 5.970078728708109
p68 18943 26211 0.003371421843038767 6.058090122619244
p69 20417 25419 0.00577118309130166 6.400724363260939
p70 16798 38623 0.0044011698986977325 6.133120854312724
p71 16968 34113 0.004525640939452812 5.199443490503002

结果分析

从结果看到,模拟退火过程最后得到的结果并不是很好,原因可能在于模拟退火选择的初始态以及在寻找新解的过程都是随机的。可能的改进为:

  1. 使用贪心算法的结果作为初始值
  2. 产生新解的过程可以通过:
    • 从中间往前改变
    • 仅仅进行点交换
  3. 对于产生的较差的结果,可以通过爬山法的结果进行替换

猜你喜欢

转载自blog.csdn.net/huangbx_tx/article/details/85032230