我建议使用建模为布尔可满足性问题/SAT将其作为约束满足问题来解决。
为此,请为实验和实验室的每个组合定义K*N布尔变量。如果变量为真,则表示将在给定实验室进行给定实验。
然后可以使用这些变量对您提供的约束进行建模。例如,如果变量名为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) )
您的所有其他约束都可以类似地编写。然而,条款的这种指数级爆炸是令人痛苦的。幸运的是,好的建模工具可以自动插入额外的变量,从而使这种基数约束更加有效。
下面,我开发了一个完整的实现来使用 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)
上述生成的解决方案是从问题的所有可能解决方案中“随机”选择的。如果您对解决方案不满意,您可以通过将解决方案提供的所有输出与在一起、否定并将其添加为新约束来排除它。