为PVRP在AMPL中动态生成子行程消除约束

时间:2018-10-11 00:39:20

标签: constraints mathematical-optimization ampl

我正在尝试用AMPL中的一些库存限制来编码周期性车辆路径问题。我想动态添加subtour约束。为了做到这一点,我受到TSP制定的启发:

https://groups.google.com/d/msg/ampl/mVsFg4mAI1c/ZdfRHHRijfUJ

但是,我无法消除模型中的子轮廓。我在模型文件中使用了以下内容。

param T;            # Number of time-periods
param V;            # Number of vehicles
param F;            # Number of fuel types

set P ordered;      # Number of gas stations
param hpos {P} >= 0;
param vpos {P} >= 0;

set PAIRS := {p in P, j in P};

param dist {(p,j) in PAIRS}
    := sqrt((hpos[j]-hpos[p])**2 + (vpos[j]-vpos[p])**2);

# A binary variable to determine if an arc is traversed.

    var H{(p,j) in PAIRS, v in 1..V, t in 1..T} binary;

# A binary variable to determine if a delivery of fuel is made to a station in a given time period.

     var StationUsed{p in P, f in 1..F, v in 1..V, t in 1..T} binary;

minimize TransportationCost: 
    sum {(p,j) in PAIRS} sum {v in 1..V, t in 1..T} dist[p,j] * H[p,j,v,t];

param nSubtours >= 0 integer;
set SUB {1..nSubtours} within P;
subject to Subtour_Elimination {k in 1..nSubtours, m in SUB[k], v in 1..V, t in 1..T, f in 1..F}:
    sum {p in SUB[k], j in P diff SUB[k]} 
    if (p,j) in PAIRS then H[p,j,v,t] else H[j,p,v,t]  >=2 * StationUsed[m,f,v,t] ;

我添加了StationUsed变量,因为与TSP不同,我的问题不必每次都访问所有节点。 H是我的二元决策变量,用于声明车辆是否在一定时间段内沿弧线(p,j)行驶。

然后我在运行文件中使用了类似于TSP的公式:

     set NEWSUB;
     set EXTEND;
     let nSubtours := 0;

     repeat {
     solve;

     let NEWSUB := {};
     let EXTEND := {member(ceil(Uniform(0,card(P))),P)};

     repeat {
     let NEWSUB := NEWSUB union EXTEND;
     let EXTEND := {j in P diff NEWSUB: exists {p in NEWSUB, v in 1..V, t in 1..T}
        ((p,j) in PAIRS and H[p,j,v,t] = 1 or (j,p) in PAIRS and H[j,p,v,t] = 1)};
     } until card(EXTEND) = 0;

     if card(NEWSUB) < card(P) then {
     let nSubtours := nSubtours + 1;
     let SUB[nSubtours] := NEWSUB;
     display SUB;
     } else break;
     };

# Display the routes
display {t in 1..T, v in 1..V}: {(p,j) in PAIRS} H[p,j,v,t];

我不确定以上情况是否适用于我在多辆车和多个时间段内遇到的问题。我尝试在let EXTEND中定义v和t,使用H时需要使用它,但是我不确定这是否是正确的方法。按照上述公式运行时,我的模型可以运行,但是并不能消除子轮廓。你们在这方面有什么建议吗?


添加的问题:

我在用SAS / OR制定的这个模型中发现了一些启示: (阅读的内容有点广泛,对我的问题不是必需的)

http://support.sas.com/documentation/cdl/en/ormpex/67518/HTML/default/viewer.htm#ormpex_ex23_sect009.htm

它可以在d天之内动态消除子路线,我认为可以将其转换为多辆车辆和多个时期(天)的问题。

稍微说明一下我的问题。一个节点在一个时间段内只能访问一次车辆。不必在每个时间段都访问所有节点,这与TSP公式(其中所有节点都在周期中)有很大不同。

我尝试了以下方法:

模型文件中的约束与以前相同。

set P ordered;  # Number of nodes
set PAIRS := {p in P, j in P: ord(p) != ord(j)};

param nSubtours >= 0 integer;
param iter >= 0 integer;
set SUB {1..nSubtours} within P;

subject to Subtour_Elimination {s in 1..nSubtours, k in SUB[s], f in F, v in V, t in T}:
sum {p in SUB[s], j in P diff SUB[s]} 
      if (p,j) in PAIRS then H[p,j,v,t] else H[j,p,v,t]  >= 2 * StationUsed[k,f,v,t];

我的运行文件如下:

let nSubtours := 0;
let iter := 0;
param num_components {V, T};
set P_TEMP;
set PAIRS_SOL {1..iter, V, T} within PAIRS;
param component_id {P_TEMP};
set COMPONENT_IDS;
set COMPONENT {COMPONENT_IDS};
param cp;
param cj;

# loop until each day and each vehicles support graph is connected

repeat {
    let iter := iter + 1;

    solve;
    # Find connected components for each day

    for {v in V, t in T} {
        let P_TEMP := {p in P: exists {f in F} StationUsed[p,f,v,t] > 0.5};
        let PAIRS_SOL[iter, v, t] := {(p,j) in PAIRS: H[p, j, v, t] > 0.5};

        # Set each node to its own component

        let COMPONENT_IDS := P_TEMP;
        let num_components[v, t] := card(P_TEMP);
            for {p in P_TEMP} {
                let component_id[p] := p;
                let COMPONENT[p] := {p};
            };

        # If p and j are in different components, merge the two component

        for {(p,j) in PAIRS_SOL[iter, v, t]} {
            let cp := component_id[p];
            let cj := component_id[j];
            if cp != cj then {

                # update smaller component

                if card(COMPONENT[cp])  < card(COMPONENT[cj]) then {
                    for {k in COMPONENT[cp]} let component_id[k] := cj;
                    let COMPONENT[cj] := COMPONENT[cj] union COMPONENT[cp];
                    let COMPONENT_IDS := COMPONENT_IDS diff {cp};
                } else {
                    for {k in COMPONENT[cj]} let component_id[k] := cp;
                    let COMPONENT[cp] := COMPONENT[cp] union COMPONENT[cj];
                    let COMPONENT_IDS := COMPONENT_IDS diff {cj};   
                };
            };
        };
        let num_components[v, t] := card(COMPONENT_IDS);
        display num_components[v, t];

        # create subtour from each component not containing depot node

        for {k in COMPONENT_IDS: 1 not in COMPONENT[k]} { . #***
            let nSubtours := nSubtours + 1;
            let SUB[nSubtours] := COMPONENT[k];
            display SUB[nSubtours];
        };
    };
    display num_components;
} until (forall {v in V, t in T} num_components[v,t] = 1);

在运行模型时,我收到很多“无效的下标被丢弃”的信息:

_cmdno 43执行“ if”命令时出错 (文件amplin,第229行,偏移5372): 错误处理设置组件:     下标COMPONENT [4]无效。 _cmdno 63执行“ for”命令时出错 (文件amplin,第245行,偏移5951): 错误处理设置组件:     下标COMPONENT [3]无效。

(...) 发出10条警告后立即解除警铃。

我认为脚本正在执行我想要的操作,但是当它丢弃了10个无效的下标时,它就停止了。

尝试调试时,我测试了第二个for循环。

for {p in P_TEMP} {

        let component_id[p] := p;
    let COMPONENT[p] := {p};        
    display component_id[p];
    display COMPONENT[p];   
};

这显示正确,但没有出现一些“丢弃无效下标”的错误。看来p不在P_TEMP中运行了一些p。例如,当P_TEMP是一个由节点“ 1 3 4 5”组成的集合时,对于component_id [2]和COMPONENT [2],我将得到“废弃的无效下标”。我的猜测是,稍后在IF-ELSE语句中也会再次发生类似的情况。

如何避免这种情况?

谢谢你, 克里斯蒂安

1 个答案:

答案 0 :(得分:0)

(先前的答案文本已删除,因为我误解了实现)

我不确定这是否能完全解释您的问题,但我认为您在确定子路线时存在两个问题。

 repeat {
 solve;

 let NEWSUB := {};
 let EXTEND := {member(ceil(Uniform(0,card(P))),P)};

 repeat {
 let NEWSUB := NEWSUB union EXTEND;
 let EXTEND := {j in P diff NEWSUB: exists {p in NEWSUB, v in 1..V, t in 1..T}
    ((p,j) in PAIRS and H[p,j,v,t] = 1 or (j,p) in PAIRS and H[j,p,v,t] = 1)};
 } until card(EXTEND) = 0;

 if card(NEWSUB) < card(P) then {
 let nSubtours := nSubtours + 1;
 let SUB[nSubtours] := NEWSUB;
 display SUB;
 } else break;
 };

这是什么:

  • 解决了问题
  • 将NEWSUB设置为空
  • 从P中随机选择一个节点作为EXTEND的起点,并将其添加到NEWSUB
  • 查找当前在NEWSUB中的所有 not 节点,这些节点通过任意一天的车辆行程 连接到NEWSUB内的节点,并将它们添加到NEWSUB
  • 重复此过程,直到没有更多要添加的内容为止(即,NEWSUB等于P,整个节点集,或者直到在NEWSUB和非NEWSUB记录之间没有 个旅程)
  • 检查NEWSUB是否小于P(在这种情况下,它将NEWSUB标识为新的子轮廓,将其附加到SUB,然后返回到起点)。
  • 如果NEWSUB具有与P相同的大小(即等于P),则它将停止。

这应该只在一天之内解决单个车辆的问题,但是我认为这不会解决您的问题。造成这种情况的原因有两个:

  1. 如果您的解决方案在不同的日期有不同的子行程,则可能不会将它们识别为子行程。

例如,考虑两天的单车问题,您所在的城市是A,B,C,D,E,F。

假设第一天的解决方案选择AB,BC,CD,DE,EF,FA,第二天的解决方案选择AB,BC,CA,DE,EF,FD。第一天没有子行程,但是第二天有两个长3个子行程,因此这不是合法的解决方案。

但是,您的实现无法识别这一点。无论选择哪个节点作为NEWSUB的起点,第1天的路由都将其连接到所有其他节点,因此最终得到card(NEWSUB)= card(P)。并没有注意到第2天有一个小组游览,因此它将接受此解决方案。

我不确定您的问题是否允许多辆车在同一天访问同一节点。如果是这样,那么您将在这里遇到同样的问题,因为车辆2将子行程链接到P的其余部分,所以未标识车辆1的子行程。

其中一些问题可以通过每天和每辆车分别进行子巡视检查来解决。但是对于您所描述的问题,还有另一个问题...

  1. 一旦程序确定了一条封闭的路线(即一组相互链接的节点,而不是其他任何节点的链接),则需要确定是否应禁止此子游览。

对于基本的TSP,这很简单。我们有一辆需要访问每个节点的车辆-因此,如果子行程的基数小于所有节点的基数,则我们有一个非法的子行程。这由if card(NEWSUB) < card(P)处理。

但是,您声明:

  

与TSP不同,我的问题不必每次都访问所有节点

假设车辆1行驶A-B-C-A,车辆2行驶D-E-F-D。在这种情况下,这些路线将看起来像非法的子路线,因为ABC和DEF分别小于ABCDEF,并且没有链接它们的路线。如果将if card(NEWSUB) < card(P)用作应该禁止的子循环的标准,则最终将迫使每辆车访问所有节点,这对于基本TSP很好,但在这里并不需要。

可以通过确定车辆v在第t天访问了多少个节点,然后将子游程的长度与其总数进行比较来解决这一问题:如果总共有10个城市,则车辆1在第1天仅访问了其中的6个城市,而针对车辆1的“子巡视”访问了6个城市,那么就可以了,但是如果它访问8,并且有一个子巡视访问了6个城市,则意味着行进两个不相交的子循环,这很糟糕。

一个陷阱要当心:

假设第1天要求车辆1前往ABCDEF。如果我们得到的“解决方案”在一天中具有车辆1 ABCA和DEFD,则可能会将ABCA识别为应该避免的子行程。

但是,如果第2天有不同的要求,则可能是第1天第2天的合法解决方案是让车辆1行驶ABCA(并且没有其他节点)。在这种情况下,您不想在第2天禁止它只是因为它是第一天的非法解决方案的一部分。

类似地,您可能拥有“子路线”,该子路线对于一种车辆是合法的解决方案,而对于另一种车辆则是非法的。

为避免这种情况,您可能需要针对每辆车x天维护不同的禁止子路线列表,而不是全部使用一个列表。不幸的是,这会使您的实现更加复杂。