说明

遗传算法,或者说其他的优化算法,本质上都是在无穷多的可能里找到可行解,在可行的时间内。所以,算法需要有一定的「方向」。这种方向或者是算法本身自带的,或者是通过指定范围减少的(约束),通常来说是二者的结合。我觉得就目前的情况来看(从应用的角度),最主要的就是表达约束。以下梳理一下在geatpy里表达几种约束的方式。

内容

我把约束分为四种类型:

  • 1 变量范围约束。
  • 2 等号约束。
  • 3 不等号约束。
  • 4 例外约束。

以商店配货的场景进行举例。

1 变量范围约束

一家门店通常只能在一天的某个时段收获,并且有时候不允许某些类型车辆送货

我将时间分为 0~144个slot, 根据geatpy提供的问题类定义,里面提供了lb(lower bound)up(upper bound)所以这部分可以这么写。每个lb, 和ub元素是一一对应的,这就减少了不必要的搜索。

# 决策变量下界
        lb = [72,0,0,        # s1.sku1:slot1, qty,vehicle
              122,0,0,       # s1.sku1:slot2, qty,vehicle
              
              72,0,0,        # s1.sku2:slot1, qty,vehicle
              122,0,0,       # s1.sku2:slot2, qty,vehicle
              
              120,0,0,        # s2.sku1:slot1, qty,vehicle
              120,0,0,        # s2.sku2:slot1, qty,vehicle
              120,0,0,        # s2.sku3:slot1, qty,vehicle              
              
              
             ]
        
        # 决策变量上界
        ub = [121,11,5,       # s1.sku1:slot1, qty,vehicle
              143,11,5,       # s1.sku1:slot2, qty,vehicle
              
              121,16,5,       # s1.sku2:slot1, qty,vehicle
              143,16,5,       # s1.sku2:slot1, qty,vehicle
              
              143,9,5,       # s2.sku1:slot1, qty,vehicle
              143,5,5,       # s2.sku2:slot1, qty,vehicle                 
              143,5,5,       # s2.sku3:slot1, qty,vehicle

2 等号约束

方案中总的配送量要恰好等于需求量

在之前的例子中,将一个方案按照门店和收货次数平展为一个很长的行向量。每个行向量是按照某个元素模板([slot, qty, vehicle])的规律重复叠加的。下面按照规律取出了各个门店,各个时隙的配送量。

# s1有两个sku在两个slot(两次收货)
        group_num = 3 # 每组元素的值
        
        s1_sku1_slot1 = Vars[:, [1 + 0*group_num]]         # 取出第一列得到所有个体的x1组成的列向量
        s1_sku1_slot2 = Vars[:, [1 + 1*group_num]]         
        s1_sku2_slot1 = Vars[:, [1 + 2*group_num]]         
        s1_sku2_slot2 = Vars[:, [1 + 3*group_num]]         
        
        
        s2_sku1_slot1 = Vars[:, [1 + 4*group_num]]         
        s2_sku2_slot1 = Vars[:, [1 + 5*group_num]]                
        s2_sku3_slot1 = Vars[:, [1 + 6*group_num]]

算法在搜索方案时是按照随机的方法前进的,有可能会出现不符合实际要求的情况,现在我们要约束配送量=需求量。

通过np.abs(x1 + x2 + x3... - demand)的方式进行等号约束。

cv1_s1_sku1 = np.abs(s1_sku1_slot1+s1_sku1_slot2 -11)
        cv2_s1_sku2 = np.abs(s1_sku2_slot1+s1_sku2_slot2 -16)
        cv3_s2_sku1 = np.abs(s2_sku1_slot1 -9)
        cv4_s2_sku2 = np.abs(s2_sku2_slot1 -5)
        cv5_s2_sku3 = np.abs(s2_sku3_slot1 -5)

我从算法运行的种群中抽出一个Phen来看:

population.Phen[:,[13] ] 
---
array([[9.],
       [9.],
       [9.],
       [9.],
       [9.],
       [9.],
       [9.],
       [9.],
       ...

可以看到种群的进化完全按照约束来的,这里要注意矩阵的形状。每个约束值是一个向量而不是标量。我们看看约束的实际表达

s2_sku1_slot1 = population.Phen[:,[13] ] 
cv3_s2_sku1 = np.abs(s2_sku1_slot1 -9)
cv3_s2_sku1
array([[0.],
       [0.],
       [0.],
       [0.],
       [0.],
       [0.],
       ...

注意里面的值是0(对应的逻辑意义是False),可以和后面在指定例外的时候对比一下。

3 不等号约束

方案中每辆车的配货要小于车辆本身的最大载荷

这段我还没写,基本上按照这个方程x1m1 + x2m2 + M1 + M2 - M <=0的方式去写。

cv2 = x1 * m1 + x2 * m2 + M1 + M2 - M

4 例外约束

方案中,存在每个门店某个时隙必须为同一辆车配送;或者更复杂一些,一辆车带了n家的货物,要确保这些货物的确可以在限定的时间内送完。

这里用一个简单的例外约束举例:第一家店不允许用012,4,5车。这个本来是可以在前面用变量范围约束的,但有时候我们也可以临时的添加,例如某个司机态度不好,门店不想让这个司机送货。

这个变量是我直接获取的一维列向量,要注意的是,最后处理完我要给每个标量升维。(这个主要由这个包里面的一些东西限定,我没去深究)

v1 = Vars[:, 2 + 0*group_num]
test_exd = (v1 <5).reshape(-1, 1)

实际数据

test_exd = population.Phen[:,2]
test_exd
---
array([2., 2., 2., 2., 2., 2., 2., 2.,...

test_exd <5 
---
array([ True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True,  True,  True,  True,  True,  True,  True,
        True,  True,  True...

# 升维
(test_exd<5).reshape(-1,1)
---
array([[ True],
       [ True],
       [ True],
       [ True],

约束写完以后水平叠加叠加

pop.CV = np.hstack([cv1_s1_sku1,cv2_s1_sku2,cv3_s2_sku1,cv4_s2_sku2,cv5_s2_sku3,test_exd])   # 将约束汇聚

举例:将两个约束的结果横向连起来。估计算法会对每个元素(一维向量)进行加总之类的。some_mat.sum(axis=1)

np.hstack([cv3_s2_sku1, cv3_s2_sku1])
array([[0., 0.],
       [0., 0.],
       [0., 0.],
       [0., 0.],
       ...

可以看到约束后,车号都是5了。

python求解带约束的非线性最小二乘发 python 约束求解_搜索