在遵守约束的同时将M个实验分配给N个实验室

时间:2019-01-25 14:26:35

标签: algorithm graph-algorithm smt constraint-satisfaction

我有以下问题:我必须将K个实验分配给N个实验室,同时要遵守一些一般性约束和某些特定性约束。

一般的是:

  1. 每个实验都必须完全分配给R个实验室
  2. 每个实验室的实验数量最多,M
  3. 理想情况下,每个实验室的实验分布接近统一(但可以稍微放松一些)
  4. 没有任何实验室

然后是特定的限制。由于并非所有实验室都具有相同的设备和试剂,因此每个实验室都会有自己的一套能够/不能执行的实验。

在我看来,这是满足约束条件的问题。我知道它们存在,但是我没有与他们合作的经验。

我想知道是否有办法通过将其映射到已知图问题或其他存在足够好的算法的方法来解决此问题,或者是否有一种方法可以优化搜索,如果需要的话被强行使用。

谢谢!

2 个答案:

答案 0 :(得分:3)

其中很大一部分可以表述为最大流量问题。为了发挥作用,请准备一个带有源,实验节点,实验室节点和接收器的流网络。从源到每个实验节点放置一条容量为R的弧。从每个实验节点到接收器放置一个容量为M的弧。从每个实验节点到每个实验节点放置一个容量为1的弧,以便该实验可以执行该实验。给定一个积分流,使源中的所有电弧饱和(如果存在,它将是最大流量),每个带有实验的实验到电弧都是指定的实验。

这满足1和2以及哪些实验室可以执行哪些实验的特定限制。我希望您可以调整M以满足约束3和4,但如果不满足,您可以将公式扩展为更通用的整数程序,并且对实验分布有更多约束。

(实际上,在反射时,您可以使用更一般但仍然易于解决的问题,即找到每个弧上的最小值和最大值都为最小值的流,并将4编码为实验室到沉弧的下限。)

答案 1 :(得分:1)

我建议使用建模为constraint satisfaction problemboolean satisfiability problem/SAT解决方案。

为此,请为实验和实验室的每种组合定义 K * N 个布尔变量。如果变量为true,则表示将在给定的实验室中执行给定的实验。

您可以使用这些变量对提供的约束进行建模。例如,如果变量名为 x(experiment,lab),并且我们有三个实验,并且希望在其中两个实验中进行实验1,则意味着存在约束:

( x(1,1) & x(1,2) & !x(1,3) ) | ( x(1,1) & !x(1,2) & x(1,3) ) | ( !x(1,1) & x(1,2) & x(1,3) )

所有其他约束可以类似地编写。但是,子句的这种指数爆炸令人痛苦。幸运的是,好的建模工具可以自动插入其他变量,以使这种 cardinality 约束更加有效。

下面,我已经开发了一个完整的实现,可以使用Z3解算器解决您的问题:

#!/usr/bin/env python3
#Richard Barnes (https://stackoverflow.com/users/752843/richard)
#May need to run `pip3 install z3-solver`

import functools
import itertools
import sys
import z3

class ExpLab:
  def __init__(self, num_experiments, num_labs):
    """Create binary indicator variables for each lab-experiment combination"""
    self.num_labs        = num_labs        #Number of labs
    self.num_experiments = num_experiments #Number of experiments

    #Create variables
    self.bvars = []
    for e in range(num_experiments):
      for l in range(num_labs):
        self.bvars += [ {"exp":e, "lab":l, "yn": z3.Bool("Run Experiment {0} at Lab {1}".format(e,l))} ]

  def getExpLab(self, exp, lab):
    """Get the variable indicating whether a particular experiment should be
    performed at a particular lab"""
    return [x['yn'] for x in self.bvars if x['exp']==exp and x['lab']==lab][0]

  def getExp(self, exp):
    """For a given experiment, get the indicator variables for all the labs the
    experiment might be performed at"""
    return [x['yn'] for x in self.bvars if x['exp']==exp]

  def getLab(self, lab):
    """For a given lab, get the variables associated for all of the experiments
    that might be performed at the lab"""
    return [x['yn'] for x in self.bvars if x['lab']==lab]    

  def numExperiments(self):
    return self.num_experiments

  def numLabs(self):
    return self.num_labs

#Create the binary indicator variables
el = ExpLab(num_experiments=6, num_labs=4)

s = z3.Solver()

R = 3 #Number of labs at which the experiment must be performed
M = 6 #Maximum number of experiments per lab

#See: https://stackoverflow.com/questions/43081929/k-out-of-n-constraint-in-z3py
#(1) each experiment has to be allocated to exactly 3 labs, 
for e in range(el.numExperiments()):
  s.add( z3.PbEq([(x,1) for x in el.getExp(e)], R) )

for l in range(el.numLabs()):
  #(2) there's a maximum number of experiments per lab (around 6)

  #NOTE: To make distributions more even, decreae the maximum number of
  #experiments a lab can perform. This isn't a perfect way of creating a smooth
  #distribution, but it will push solutions in that direction.
  experiments_at_this_lab = el.getLab(l)
  s.add( z3.PbLe([(x,1) for x in experiments_at_this_lab], M) )
  #(3) no lab is left out. 
  s.add( z3.PbGe([(x,1) for x in experiments_at_this_lab], 1) )

#Example of a specific constraint
#Don't run Experiment 3 at Lab 2
s.add( z3.Not(el.getExpLab(3,2)) )

#Check to see if the model 
if s.check()!=z3.sat:
  print("The problem has no solution!")
  sys.exit(-1)

#A solution to the problem exists... get it. Note: the solution generated is
#arbitrarily chosen from the set of all possible solutions.
m = s.model()
print(m)

由上述方法生成的解决方案是从所有可能的问题解决方案中“随机”选择的。如果您对解决方案不满意,可以通过将解决方案提供的所有输出相加,取反并将其添加为新约束来排除它。