具有部分可用机器的排列作业调度

时间:2014-11-30 14:47:11

标签: algorithm search genetic-algorithm evolutionary-algorithm

我正在寻找合适的算法来解决时间安排问题。首先,我将概述问题本身,然后在第二部分,我将给出我正在考虑解决方案的方向。我试图解决这个问题,因为我对这些问题感兴趣,并且因为同样的问题可以在以后通过更多的变量和更大的设置来解决。

问题

我想对电池进行一些测试,以了解它们在连接到负载时的响应方式。并在尽可能短的时间内完成所有测试。这里的两个重要变量是:

  • 充电状态(SoC)电池剩余的能量从100%到0%。我们将测试99%,75%,50%和25%(4种变化)。 (后面解释为什么99%而不是100%)。当放松为0时,我们将假设SoC丢失。
  • 放松电池在几小时内放松的量。我们知道理论上24小时应该足够了,所以这是最大的。我们将测试不同的时间,如:5分钟,15分钟,30分钟,1小时,2小时,6小时,24小时(7种变化)。

总组合:一个电池4 x 7 = 28

测试应该进行的顺序如下: 充电至100%,放电至所需的SoC,放松,在测量时放电至新的SoC

示例:我们希望看到放电2小时后电池放电时的反应从75%降至50%

  1. 电池未知SoC(测量方法不够准确)
  2. 充值至100%
  3. 放电至75%
  4. 放松2小时
  5. 测量时放电,停止在50%
  6. 电池现在可以再次放松,并从50%到25%开始测量。它不必再次充电至100%。

    情况/状态

    现在我将概述一些可能发生的情况以及在这种情况下必须采取的措施。

    初始化

    可以使用已执行的测试来初始化问题(这很重要,因为我们可能希望在中途重新安排)。如果电池具有已知状态(SoC /松弛),我们可以使用它。如果SoC未知,那么电池必须充电。如果放松未知但SoC已知,那么电池必须放松至少24小时。

    再充电

    将电池放入充电器必须手动完成。将电池留在充电器中不是问题。充电大约需要2.5小时。每个电池都有自己的专用充电器,但将来我们可能会有更多的电池充电器,因此算法需要能够使用不同数量的充电器。

    放松(放松)

    放松可以简单地通过不将电池连接到任何东西来完成,因此它不需要任何特殊设备。在松弛时间段开始之前,必须对电池施加压力(=连接到放电器)。我们不确定压力期将持续多长时间,但我们假设将电池放电1%所需的时间就足够了。 99%将成为我们可以准确确定放松时间的第一个SoC。

    放电

    目前只有一个放电器,但算法应该能够采用不同数量的放电器。将电池放入放电器必须手动完成(也取出)。但是,如果将电池放入放电器,则无需立即将电池放电。可以将时间设置为在特定时间开始。当排出足够的能量时,放电器可以自动停止。可以从查找表估计放电时间的估计。这不是线性的,因此75%至50%不必花费相同的时间,从25%到0%。查找相当准确(2.5小时差异大约5分钟)。

    等待

    如果取出所有放电器,电池可以等待,但是等待放电器会增加放松时间。因此,如果放松时间高于必须进行的测量所需的弛豫时间,那么它必须放电到较低的充电水平并再次放松,或者必须再次充电。
    如果安全地使用所有充电器,电池可以等待,除此之外没有任何惩罚/劣势,否则需要等待一段时间。

    约束

    必须手动完成的事情只能在办公时间(星期一至星期五8:30-17:00)完成。因此,例如将电池放入放电器必须手动完成。然后在夜晚的设定时间(电池放松后),可以在计时器上启动放电器,然后第二天早上到达办公室时,电池可以放入充电器。

    解决方案的想法

    我不确定我是否在这里思考正确的方向,因为我还没有找到合适的解决方案。因此,该部分中的任何内容都可能是错误的。

    任务序列很重要,因为不同的序列可能会引入比另一个序列更多或更少的等待时间。因此,对于只有一个28次测试的电池,这将是28的排列!这是一个相当大的数字。因此,对问题空间的详尽搜索是不可行的。我所知道的唯一可以在这些问题上得到相当好结果的算法是遗传算法。虽然有了所有的限制和可能性,但我不能只是使用经典的遗传算法。我已经阅读了一些(研究)论文,最终对排列Flowshop调度问题(PFSP)的描述产生了最大的共鸣(各种来源)。虽然提到的扩展作业车间调度问题(EJSSP)here也很有趣。

    我看到的最大问题是办公时间限制。如果不是这样,则调度可能类似于仅将块拟合到时隙中(即使插槽具有动态大小)。我不确定处理这种约束的最佳方法是什么。要么我可以将机器(放电器)建模为两个独立的机器,每个机器在不同时刻都有效,或者我可以引入假工作,以便机器不能被正常工作带走。

    这只是在这一点上的猜测,因为我缺乏经验。我是一个实用的程序员而不是一个学者,我真的很难弄清楚哪些算法适合哪些,以及注意事项是什么。我很高兴能够实施,但现在我仍然坚持:

    1. 哪种算法适合此类问题?
    2. 如何设置算法的特殊条件?
    3. 我如何制作交叉/选择/突变功能?
    4. 我是否需要在子问题中解决这个问题并将其纳入更大的算法?首先要解决哪些子问题?
    5. 伪代码怎么样?

2 个答案:

答案 0 :(得分:10)

概述

虽然您已明确标记evolutionary-algorithm,但我建议您查看一下以近似动态规划(ADP)名称汇总的一组算法。在我看来,一本好的介绍性书籍是沃伦·鲍威尔(Warren B. Powell)。它包含了很多这样的资源分配问题,以及其他几个实际相关的东西,通常都是以最优控制的名义(例如:控制一家拥有数千辆卡车的货运公司)。

ADP在直接应用进化算法,模拟退火等方面的一个优点是它没有提供特定的算法,而是一个用于建模时间依赖的马尔可夫决策问题的框架。在这些框架内,人们可以自由地使用各种适当的算法。

为了应用ADP,一个关键是给定问题的正确数学建模。最重要的是系统的stateactions可以采用给定状态以及cost这些操作需要以及哪个想要最小化 - 这里的成本由测试持续时间给出。鉴于一个人已经构建了一个合适的模型,那么任务就是(近似)解决 Bellman方程,其中存在几种算法。

在下文中,我将举例说明如何进行此处。这自然不是一个随时可用的模型,因为这可能需要相当长的时间来构建 - 实际上我认为这对于大师甚至是博士论文来说都是一个很好的问题。但是,我会尝试在第一步中保持详尽无遗,以便稍后可以引入近似值。

建模单个电池的状态

首先,我们要为单个电池设置一个粗略的模型。在这里,您的问题与您所描述的一样具有确定性是有帮助的,即此处没有随机成分(例如,所有持续时间都是固定的,而不是从某些统计分布中随机抽取)。

  • 单一状态:正如您所写,一个电池由一个州提供

    S = {SoC, Relax}      where SoC   \in {UNKNOWN, 0%, 25%, 50%, 75%, 100%}
                          and   Relax \in {UNKNOWN, 0m, 5m, 15m, 30m, 1h, 2h, 6h, 24h}
    

    为方便起见,我添加了0%0m,但这里可能并不需要它们。

    请注意,我已在此处进行了大量简化,例如,充电状态也可以是79%。证明这一点的假设如下:一旦你第一次开始实验(或者很长一段时间后重新开始),所有电池都合理地处于状态{UNKNOWN,UNKNOWN}。然后,根据您的描述,第一个要做的是进行完全充值,这将全部设置为状态{100%,0m}(以及成本2,5h)。从这里开始,只有符合条件的状态更改 - 仅对特定SoC进行放电,而仅对100%进行放电(我假设基于此你的描述)。请注意,这在更自然的随机框架中变得更难,例如电池和电池。 SoC表现不佳。

  • 对于特定的单电池状态,
  • 操作和成本,一个已关联一组可行的操作(加上相应的成本)。让我们收集以下内容:

    State                  Possible Actions                       Cost
    -------------------------------------------------------------------------------
    {UNKNOWN, Relax}  ->   RECHARGE TO {100%,0m}                  2,5h
    
    {SoC, 0m}         ->   RELAX TO {SoC,5m},                     5m          
    
    {SoC, Relax}      ->   RECHARGE TO {100%,0m},                 2,5h           
                           DISCARGE TO {SoC-1},                   C(SoC, SoC-1)
                           DISCHARGE-MEASURE TO {SoC-1},          C(SoC, SoC-1)
                           RELAX TO {SoC, RELAX+1}                C(Relax, Relax+1)
    
    {SoC, UNKNOWN}    ->   RECHARGE TO {100%,0m},                 2,5h
                           DISCARGE TO {SOC-1,0m},                C(SoC,SoC-1)   
                           RELAX {SoC, 24h}                       24h
    

    SoC-1这里代表下一个可行状态,而C(SoC, SoC-1)意味着"时间从SoC到SoC-1,例如。从75%到50%。轮到您查看此表是否符合您的型号。如果不是,你必须纠正或扩展它。

    请注意,我通过仅允许转换到下一个可行状态SoC-1(例如,从75%到50%)再次进行了简化。这是合理的,因为假设成本是额外的。例如,当你从75%变为25%时,它需要与首先变为50%然后变为25%相同的时间。

    此外,所有RECHARGEDISCHARGE行动仅在办公时间内可行,上表中未对其进行说明(但后面需要将其纳入模型中)。 / p>

将上述内容与完整系统的模型相结合

现在让我们假设你有N电池,M充电器和K放电器(我们可以假设M<=NK<=N),所有这些都是是相同的。此外,我们的目标是只执行一次测试。

  • 测试状态:测试状态Test4*701的向量包含是否已执行特定测试的信息。请注意,这对应于2^28个可能的状态,因此必须在此处引入近似值。

  • 多电池状态:所有电池B的组合状态是单电池状态的笛卡尔积,即它位于空间{{1} }。这意味着只需要考虑{SoC,Relax}^N单电池状态。

    N

    同样,对于中等数字B={SoC_1, Relax_1}, ..., {SoC_N, Relax_N} ,此空间的大小将是一个非常大的数字。

  • 办公时间:此外,我们需要合并当天的时间N。完全这样做,最终会有一些T可能的五分钟时段。

  • 多电池操作:同样,多电池操作由一维操作的24*60m / 5m = 288维笛卡尔积给出。 NRECHARGE仅适用于DISCHARGE在offcie时段,并且如果有足够的Re-and Dischargers(所有T / RECHARGE的总和必须不超过DISCHARGE / M)。

总结一下,完整状态K由组合

给出
S

状态空间的维度约为S = {Test, T, B} ,很快变得巨大。

此外,对于每个州,都有一组相应的允许行动,现在应该清楚了。

现在已经或多或少地指定了系统的模型(如果需要,请更正它!),我们可以继续尝试解决它。​​

贝尔曼方程

Bellman方程是(近似)动态规划的中心方程。如需精彩的介绍,请查看网上免费提供的book of Sutton and Barto

它的想法很简单:对于每个可能的状态,引入一个值函数2^28 * (6*9)^N * 288,它告诉你在状态S中有多好。这个值函数原则上包含了所需的时间。一旦处于状态S,就完成测试。为了确定有限时间范围问题的这个值,从最终状态开始直到开始 - 也就是说,至少是问题的大小允许它。但是,让我们在下面的示意图中看到:

最终状态V(S)仅包含1个的状态,即已执行所有Test次测试。为所有这些状态设置28(无论V(S)=0和多电池状态),因为您不再需要时间。

后退一步:现在考虑T只有27个1和0的所有状态,即仍然需要执行一个测试。对于每个可能的多电池状态Test和时间点B,可以自动发现最快的替代方案。设置T等于此费用。

又退一步:接下来考虑V(S)只有26个和两个零的所有状态(还有两个测试)。现在,对于每个可能的多电池状态Test和时间点B,可以选择操作T,例如最小化操作的成本加上操作的状态值线索。就您而言,您必须选择a,例如最小化a,其中C(a) + V(S')aS。如果您找到此操作,请将状态设置为S'

等等。其中一个针对所有可能状态执行此操作并存储以此方式获得的每个最佳值V(S) = C(a) + V(S') - 对于少量电池V(S),这在实践中甚至可行。

一旦你准备好了,你就可以在每个州获得最佳的行动。为此,如果其中一个处于状态N,则其中一个遵循与上述相同的配方,并始终选择最小化S的操作a。当存储每个州的最佳行动时,这也只能进行一次。

然后你完成了 - 你完全解决了你的问题。或者说,至少在理论上,因为在实践中,问题规模需要太多的努力和存储才能进行上述后向递归并将其存储在表格中(对于上面指出的问题,我说这个制度开始于{ {1}}和更大)。因此,需要引入近似值。

逼近

在使用近似值时,通常会牺牲最佳解决方案&#34;为了一个良好的解决方案&#34;。因为这可以通过多种方式完成,这就是艺术开始的地方。或者,引用鲍威尔,第15.7章:&#34;对于一个重要的问题类,一个成功的ADP算法,我们相信, 可专利发明&#34;。因此,我将只列出一些可以在这里完成的步骤。

  • 一类称为聚合的近似方法使用状态变量的简化模型。例如,不是在状态变量(288个状态)中的C(a) + V(S')块中包含时间N~3,而是可以使用T块或者甚至是布尔值来指示它是否为&#39}。的办公时间。或者您只能在办公时间使用5m块加上一个州外的非办公时间。等等......很多可能性。这里总是获得

  • 的较小表格表示
  • 另一类近似方法使用值函数的参数化表示,比如线性回归模型或神经网络。在上面的迭代方案中,值函数不存储在表中,而是用作输入以适合参数。这个通过更少数量的参数替换了通常巨大的表格表示,但缺点是拟合过程通常更复杂。 (请注意,在此步骤中,可以自然地应用进化算法)。

  • 在另一种方法中,使用基函数来捕获系统的重要状态。例如,在井字游戏中,您不需要所有可能的游戏状态,但是指示谁占据中心和占用边数的基础状态就足够了。

  • 接下来,不是试图对所有状态执行完整迭代,而是可以使用蒙特卡罗方法随机探索许多但不是所有可能的状态。这是更有效的,更好的启发式存在,让算法探索有意义的状态。

有关其他想法及其实际应用,请参阅上述书籍。


好的,这变得冗长但我希望能帮助您了解一种可能的方法。我建议你只使用一个电池设计一个小型模型,然后尝试自己实现上面的反向迭代。或者,在所引用的两本书中,您会发现几个玩具问题,您可以熟悉这些问题。祝电池好运!

答案 1 :(得分:2)

因为这是StackOverflow,并且因为你将花费50英镑的难以获得的积分,而且我自己对这些东西非常感兴趣,我给你写了一些示例代码,它实现了我几天前提出的配方。虽然它是一个在那里解决的玩具模型,它包含了你原始问题的大多数问题,并且 - 如果有足够的计算机能力 - 可能很容易扩展到解决后者。

模型问题

让我们考虑以下模型问题:

  • N=2电池,M=2充电器,K=1放电器。

  • 时间 T以6小时的倍数进行:0:006:0012:0018:00。办公时间除了夜晚0:00以外都是。

  • 电池状态是:

      SoC
    • {0%,50%,100%}Relax
    • {0h,6h,12h,18h,24h}

    电池最初处于{0%,0h}状态,即在测试传导开始时未充电(放松是不重要的,因为无论如何需要首先充电)。

  • 可用的操作是:

    • RELAX:什么都不做。 Relax增加6小时。
    • RECHARGE:将SoC提升到下一个州,并将Relax设置为0h
    • MEASURE:将SoC缩减到之前的状态,并将Relax设置为0h

    每项行动&#34;费用&#34; 6个小时。

    请注意,MEASURE的行为几乎与您所谓的DISCHARGE相同。特别是,如果将MEASURE应用于不是测试用例的电池状态,则仅仅是放电。但是,这是唯一的区别,当MEASURE应用于其中一个测试用例时,Test状态也会更新。

  • 对于{SoC,Relax}中的每个州{{50%,6h},{100%,6h},{50%,24h},{100%,24h}},需要测量一次电池。这使得{4}的Test向量包含0或1。

熟悉下面的实现后,您可以轻松更改所有这些选择。

型号说明

我将使用我之前回答中提供的系统描述。特别是,系统的状态由多向量

给出
{Test, T, {SoC_1,Relax_1}, {SoC_2,Relax_2}}

这实际上不是最好的状态变量( - 状态变量应该始终尽可能紧凑)。例如,它没有考虑电池的对称性。在&#34;现实 - 艰难&#34;模型,尽可能避免这样的问题。

此外,我通过假设所有事情都发生在6小时的时间步骤来简化事情。通过这个可以简单地在每个动作之后将T变量增加6小时。如果有持续时间为12小时的动作,则需要在状态变量中添加更多变量,以指示电池是否被阻塞以及何时再次可用。例如,如果有一个持续5个小时的行动,就不说了。所以你看,时间在这里被认为是基本的。

实施

我已经使用C ++进行实施,希望您对此感到满意。代码本身已经相当冗长,但我试着至少对它进行评论。以下是实施要点:

  • 首先,关于基本要素:enum给出了更基本的要素,例如

    enum struct StateOfCharge
    {
        P0=0,
        P50,
        P100,
        END_OF_LIST
    };
    

    整数也会运行良好,但我想使用枚举更容易阅读。对于这些枚举,我还提供了operator<<用于屏幕输出以及operator++operator--用于增加/减少状态。后两个运算符是接受任何枚举的函数模板( - 至少如果它包含状态END_OF_LIST)。

  • 更高级的元素(如状态和操作)是类。特别是State类包含许多逻辑:它能够通过其成员判断是否允许给定的操作

    bool isActionAllowed(Action const&) const
    

    此外,它可以通过

    为您提供给定操作的下一个状态
    State nextState(Action const&) const
    

    这些函数是下一个描述的值迭代的核心。

  • 有一个类BatteryCharger,它执行实际的动态编程算法。它包含一个容器std::map<State,int>来存储给定状态的值(请记住,这里的值只是完成所需的时间,自然应该最小化)。为了使map起作用,还有一个operator<比较了两个State变量。

  • 最后,关于 Value Iteration 方案的几句话。在上面的答案中,我写了一个可以通过向后迭代来做到这一点。这是事实,但它可能会变得相当复杂,因为您需要找到一种方法,以便涵盖所有可能的状态(并且最多只能覆盖一次)。虽然这可以在这里实现,但也可以简单地以任意方式迭代所有状态。然而,这需要迭代完成,因为否则你会利用尚未优化的状态值。当所有值都收敛时,迭代结束。

    有关值迭代的更多信息,请再次查看book of Sutton and Barto

代码

这是代码。它编写得不是很好( - 全局变量等),但它可以工作并且可以很容易地扩展:

#include<iostream>
#include<array>
#include<map>
#include<type_traits>
#include<algorithm>

template<typename T, typename = typename std::enable_if<std::is_enum<T>::value >::type>
bool isLast(T const& t)
{
    return t == static_cast<T>(static_cast<int>(T::END_OF_LIST) - 1);
}

template<typename T, typename = typename std::enable_if<std::is_enum<T>::value >::type>
bool isFirst(T const& t)
{
    return t == static_cast<T>(0);
}



template<typename T, typename = typename std::enable_if<std::is_enum<T>::value >::type>
T& operator++(T& t)
{
    if (static_cast<int>(t) < static_cast<int>(T::END_OF_LIST) - 1)
    {
        t = static_cast<T>(static_cast<int>(t)+1);
    }
    return t;
}

template<typename T, typename = typename std::enable_if<std::is_enum<T>::value >::type>
T operator++(T& t, int)
{
    auto ret = t;
    ++t;
    return ret; 
}


template<typename T, typename = typename std::enable_if<std::is_enum<T>::value >::type>
T& operator--(T& t)
{
    if (static_cast<int>(t) > 0)
    {
        t = static_cast<T>(static_cast<int>(t)-1);
    }
    return t;
}


template<typename T, typename = typename std::enable_if<std::is_enum<T>::value >::type>
T operator--(T& t, int)
{
    auto ret = t;
    --t;
    return ret;
}



const int Nbattery = 2;
const int Nrecharger = 2;
const int Ndischarger = 1;
const int Ntest = 4;

enum struct StateOfCharge
{
    P0=0,
    P50,
    P100,
    END_OF_LIST
};

//screen output
std::ostream& operator<<(std::ostream& os, StateOfCharge const& s)
{
    if(s==StateOfCharge::P0) os << "P0";
    else if (s==StateOfCharge::P50) os << "P50";
    else if (s == StateOfCharge::P100) os << "P100";
    return os;
}


enum struct StateOfRelax
{
    H0=0,
    H6,
    H12,
    H18,
    H24,
    END_OF_LIST
};

//screen output
std::ostream& operator<<(std::ostream& os, StateOfRelax const& s)
{
    if(s==StateOfRelax::H0) os << "H0";
    else if (s == StateOfRelax::H6) os << "H6";
    else if (s == StateOfRelax::H12) os << "H12";
    else if (s == StateOfRelax::H18) os << "H18";
    else if (s == StateOfRelax::H24) os << "H24";
    return os;
}


struct SingleBatteryState
{
    //initialize battery as unfilled
    StateOfCharge SoC;
    StateOfRelax Relax;
    SingleBatteryState(StateOfCharge _SoC = static_cast<StateOfCharge>(0), StateOfRelax _Relax = static_cast<StateOfRelax>(0))
        : SoC(_SoC)
        , Relax(_Relax)
    {}


    //loop over state
    bool increase()
    {
        //try to increase Relax
        if (!isLast(Relax))
        {
            ++Relax;
            return true;
        }
        //if not possible, reset Relax
        else if (!isLast(SoC))
        {
            ++SoC;
            Relax = static_cast<StateOfRelax>(0);
            return true;
        }

        //no increment possible: reset and return false
        SoC = static_cast<StateOfCharge>(0);
        Relax = static_cast<StateOfRelax>(0);
        return false;
    }
};

std::ostream& operator<<(std::ostream& os, SingleBatteryState const& s)
{
    os << "("<<s.SoC << "," << s.Relax << ")";
    return os;
}


bool operator<(SingleBatteryState const& s1, SingleBatteryState const& s2)
{
    return std::tie(s1.SoC, s1.Relax) < std::tie(s2.SoC, s2.Relax);
}

bool operator==(SingleBatteryState const& s1, SingleBatteryState const& s2)
{
    return std::tie(s1.SoC, s1.Relax) == std::tie(s2.SoC, s2.Relax);
}

//here specify which cases you want to have tested
std::array<SingleBatteryState, Ntest> TestCases = { SingleBatteryState{ StateOfCharge::P50, StateOfRelax::H6 }
                                                  , SingleBatteryState{ StateOfCharge::P50, StateOfRelax::H24 }
                                                  , SingleBatteryState{ StateOfCharge::P100, StateOfRelax::H6 }
                                                  , SingleBatteryState{ StateOfCharge::P100, StateOfRelax::H24 } };


// for a state s (and action MEASURE), return the entry in the array Test
// which has to be set to true
int getTestState(SingleBatteryState const& s)
{
    auto it = std::find(std::begin(TestCases), std::end(TestCases), s);

    if(it==std::end(TestCases))
        return -1;
    else
        return it - std::begin(TestCases);
}


enum struct SingleAction
{
    RELAX = 0,
    RECHARGE,
    MEASURE,
    END_OF_LIST
};

std::ostream& operator<<(std::ostream& os, SingleAction const& a)
{
    if(a== SingleAction::RELAX) os << "RELAX";
    else if (a == SingleAction::RECHARGE) os << "RECHARGE";
    else if (a == SingleAction::MEASURE) os << "MEASURE";

    return os;
}


enum struct TimeOfDay
{
    H0 = 0,
    H6,
    H12,
    H18,
    END_OF_LIST
};


//here set office times
std::array<TimeOfDay,3> OfficeTime = {TimeOfDay::H6, TimeOfDay::H12, TimeOfDay::H18};

bool isOfficeTime(TimeOfDay t)
{
    return std::find(std::begin(OfficeTime), std::end(OfficeTime),t) != std::end(OfficeTime);
}

//screen output
std::ostream& operator<<(std::ostream& os, TimeOfDay const& s)
{

    if(s==TimeOfDay::H0) os << "0:00 h";
    else if (s == TimeOfDay::H6) os << "6:00 h";
    else if (s == TimeOfDay::H12) os << "12:00 h";
    else if (s == TimeOfDay::H18) os << "18:00 h";
    return os;
}


struct Action
{
    SingleAction& operator[](int i) { return A[i]; }
    SingleAction const& operator[](int i) const { return A[i]; }

    std::array<SingleAction, Nbattery> A{};

    bool increase()
    {
        for (int i = Nbattery - 1; i >= 0; --i)
        {
            if (!isLast(A[i]))
            {
                ++A[i];
                return true;
            }
            else
            {
                A[i] = static_cast<SingleAction>(0);
            }       
        }
        return false;
    }
};


//screen output
std::ostream& operator<<(std::ostream& os, Action const& A)
{
    os << "(";
    for (int i = 0; i < Nbattery-1 ; ++i)
    {
        os << A[i] << ",";
    }
    os << A[Nbattery-1] << ")";

    return os;
}



struct State
{
    std::array<bool, Ntest> Test{};
    TimeOfDay T = TimeOfDay::H0;
    std::array<SingleBatteryState, Nbattery> B{};

    State()
    {
        for (int i = 0; i < Ntest; ++i)
        {
            Test[i] = true;
        }
    }

    bool isSingleActionAllowed(SingleAction const& a) const
    {
        if ( !isOfficeTime(T) &&  a != SingleAction::RELAX)
        {
            return false;
        }

        return true;
    }

    bool isActionAllowed(Action const& A) const
    {
        //check whether single action is allowed
        for (int i = 0; i < Nbattery; ++i)
        {
            if (!isSingleActionAllowed(A[i]))
                return false;
        }

        //check whether enough Rechargers and Dischargers are available
        int re = 0;
        int dis = 0;

        for (int i = 0; i < Nbattery; ++i)
        {
            //increase re counter
            if (A[i] == SingleAction::RECHARGE)
            {
                ++re;
            }
            //increase dis counter
            else if (A[i] == SingleAction::MEASURE)
            {
                ++dis;
            }

            //check whether ressources are exceeded
            if (re>Nrecharger || dis > Ndischarger)
            {
                return false;
            }
        }

        return true;
    }

    //loop over state
    bool increase()
    {
        //increase time
        if (!isLast(T))
        {
            ++T;
            return true;
        }
        else
        {
            T = static_cast<TimeOfDay>(0);
        }

        //if not possible, try to increase single battery states
        for (int i = Nbattery-1; i >= 0; --i)
        {
            if (B[i].increase())
            {
                return true;
            }
        }

        //if also not possible, try to increase Test state
        for (int i = Ntest-1; i >=0; --i)
        {
            if (Test[i])
            {
                Test[i] = false;
                return true;
            }
            else
            {
                Test[i] = true;
            }
        }

        return false;
    }


    // given a single action and a single-battery state, calculate the new single-battery state.
    // it is assumed the action is allowed
    SingleBatteryState newState(SingleBatteryState s, SingleAction const& a) const
    {
        if (a == SingleAction::RELAX)
        {
            ++s.Relax;
        }
        else if (a == SingleAction::RECHARGE)
        {
            ++s.SoC;
            s.Relax = static_cast<StateOfRelax>(0);
        }
        else if (a == SingleAction::MEASURE)
        {
            --s.SoC;
            s.Relax = static_cast<StateOfRelax>(0);
        }
        return s;
    }

    // calculate new complete state while assuming the action s allowed
    State newState(Action const& a) const
    {
        State ret = *this;

        //increase time
        if (isLast(ret.T))
        {
            ret.T = static_cast<TimeOfDay>(0);
        }
        else
        {
            ret.T++;
        }

        //apply single-battery action
        for (int i = 0; i < Nbattery; ++i)
        {
            ret.B[i] = newState(B[i], a[i]);

            // if measurement is performed, set new Test state.
            // measurement is only possible if Soc=50% or 100%
            // and Relax= 6h or 24h
            if (a[i] == SingleAction::MEASURE
                && getTestState(B[i]) != -1)
            {
                ret.Test[getTestState(B[i])] = true;
            }
        }
        return ret;
    }

    int cost(Action const& a) const
    {
        if (std::find(std::begin(Test), std::end(Test), false) == std::end(Test))
        {
            return 0;
        }
        return 1;
    }

};

//screen output
std::ostream& operator<<(std::ostream& os, State const& S)
{
    os << "{(";
    for (int i = 0; i < Ntest-1; ++i)
    {
        os << S.Test[i]<<",";
    }
    os << S.Test[Ntest-1] << "),"<<S.T<<",";

    for (int i = 0; i < Nbattery; ++i)
    {
        os << "(" << S.B[i].SoC << "," << S.B[i].Relax<<")";
    }
    os << "}";

    return os;
}

bool operator<(const State& s1, const State& s2)
{
    return std::tie(s1.Test, s1.T, s1.B) < std::tie(s2.Test, s2.T, s2.B);
}




struct BatteryCharger
{
    bool valueIteration()
    {
        // loop over all states with one specified Test state
        State S;

        int maxDiff=0;
        do
        {
            int prevValue = V.find(S)->second;
            int minCost = prevValue;

            // loop over all actions
            // and determine the one with minimal cost
            Action A;
            do
            {
                if (!S.isActionAllowed(A))
                {
                    continue;
                }

                auto Snew = S.newState(A);
                int newCost = S.cost(A) + V.find(Snew)->second;

                if (newCost < minCost)
                {
                    minCost = newCost;
                }
            }
            while (A.increase());

            V[S] = minCost;

            maxDiff = std::max(maxDiff, std::abs(prevValue - minCost));
        }
        while (S.increase());

        //return true if no changes occur
        return maxDiff!=0;
    }


    void showResults()
    {
        State S;
        do
        {
            auto Aopt = getOptimalAction(S);
            auto Snew = S.newState(Aopt);
            std::cout << S << "   " << Aopt << "   " << Snew << "   " << V[S] << "   " << V.find(Snew)->second << std::endl;
        }
        while (S.increase());
    }

    Action getOptimalAction(State const& S) const
    {
        Action Aopt;

        Action A;
        int minCost = std::numeric_limits<int>::max();
        do
        {
            if (!S.isActionAllowed(A))
            {
                continue;
            }

            auto Snew = S.newState(A);
            int newCost = S.cost(A) + V.find(Snew)->second;

            if (newCost < minCost)
            {
                minCost = newCost;
                Aopt = A;
            }
        }
        while (A.increase());

        return Aopt;
    }

    BatteryCharger()
    {
        State S;
        do
        {
            int ad = 0;
            for (int i = 0; i < Ntest; ++i)
            {
                if (!S.Test[i])
                    ad += 100;
            }
            V[S] = ad;
        }
        while (S.increase());
    }

    std::map<State, int> V;
};





int main(int argc, char* argv[])
{
    BatteryCharger BC;

    int count = 0;
    while (BC.valueIteration())
    {
        ++count;
    };
    std::cout << "Value Iteration converged after " << count << " iterations\n"<<std::endl;

    //start at 6:00 with no tests at all performed
    State S;
    S.Test[0] = false;
    S.Test[1] = false;
    S.Test[2] = false;
    S.Test[3] = false;
    S.T = TimeOfDay::H6;

    //get sequence of optimal actions
    auto Aopt = BC.getOptimalAction(S);
    while (BC.V[S] != 0)
    {
        std::cout << S << "    " << Aopt << "   " << BC.V[S] << std::endl;

        S = S.newState(Aopt);
        Aopt = BC.getOptimalAction(S);
    }
    std::cout << S << "    " << Aopt << "   " << BC.V[S] << std::endl;

    return 0;
}

这是一个可以玩的DEMO

结果

使用上面的代码,可以获得屏幕上打印的以下结果:

Value Iteration converged after 8 iterations

{(0,0,0,0),6:00 h,(P0,H0)(P0,H0)}    (RELAX,RECHARGE)   10
{(0,0,0,0),12:00 h,(P0,H6)(P50,H0)}    (RECHARGE,RECHARGE)   9
{(0,0,0,0),18:00 h,(P50,H0)(P100,H0)}    (RECHARGE,RELAX)   8
{(0,0,0,0),0:00 h,(P100,H0)(P100,H6)}    (RELAX,RELAX)   7
{(0,0,0,0),6:00 h,(P100,H6)(P100,H12)}    (MEASURE,RELAX)   6
{(0,0,1,0),12:00 h,(P50,H0)(P100,H18)}    (RELAX,RELAX)   5
{(0,0,1,0),18:00 h,(P50,H6)(P100,H24)}    (RELAX,MEASURE)   4
{(0,0,1,1),0:00 h,(P50,H12)(P50,H0)}    (RELAX,RELAX)   3
{(0,0,1,1),6:00 h,(P50,H18)(P50,H6)}    (RELAX,MEASURE)   2
{(1,0,1,1),12:00 h,(P50,H24)(P0,H0)}    (MEASURE,RELAX)   1
{(1,1,1,1),18:00 h,(P0,H0)(P0,H6)}    (RELAX,RELAX)   0

人们看到值迭代在8次迭代后收敛。在我的PC上和在发布模式下使用Visual Studio,大约需要半秒钟。因此,您可以安全地使问题更加详细,并且仍然希望得到一个可行的确切答案。

在下面的行中,您可以看到优化测试过程的示例。此处,初始状态为{(0,0,0,0),6:00 h,(P0,H0)(P0,H0)},即完整测试从早上6点开始使用未使用的电池(P0此处表示0%6:00 h表示早上6点或德语&#34 ; 6:00 Uhr&#34;)。下一列为您提供优化的操作{RELAX,RECHARGE}(导致下一行中的状态)。第三列中的数字是完成测试仍然需要的时间(以6小时为单位)。对于此示例案例,总共需要60个小时。

你有,模型问题解决了。为了检查不同的初始状态(他们所有已经优化了!),只需更改main中的以下行:

//start at 6:00 with no tests at all performed
State S;
S.Test = {false,false,false,false};
S.T = TimeOfDay::H6;

现在,玩得开心!在最好的情况下,您告诉我们您的进展情况以及成功程度。