python-constraint:根据函数的输出设置约束

发布于 2024-10-17 15:38:30 字数 2944 浏览 4 评论 0原文

我一直在制作一个系统,该系统接收有关司机、潜在乘客及其位置的数据,并尝试在给定一些限制的情况下优化可以由司机搭车的乘客数量。我正在使用 python-constraint 模块,并且决策变量是这样表示的:

p = [(passenger, driver) for driver in drivers for passenger in passengers]
driver_set = [zip(passengers, [e1]*len(drivers)) for e1 in drivers]
passenger_set = [zip([e1]*len(passengers), drivers) for e1 in passengers]
self.problem.addVariables(p, [0,1])

因此,当我打印 p 的值以及 driver_set 和user_set 时,我得到以下输出(给出我提供的测试数据):

[(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)] # p
[[(0, 0), (0, 1)], [(1, 0), (1, 1)], [(2, 0), (2, 1)]] # passenger_set
[[(0, 0), (1, 0)], [(0, 1), (1, 1)]] # driver_set

所以,有3 名乘客和 2 名司机:变量 (2,0) 表示乘客 2 在 0 号车内,依此类推。我添加了以下约束,以确保没有乘客乘坐超过一辆车,并且驾驶员不能拥有比座位更多的人:

for passenger in passenger_set:
        self.problem.addConstraint(MaxSumConstraint(1), passenger)
for driver in driver_set:
        realdriver = self.getDriverByOpId(driver[0][1])
        self.problem.addConstraint(MaxSumConstraint(realdriver.numSeats), driver)

这有效 - 生成的所有解决方案都满足这些约束。然而,我现在想添加一些限制,即任何解决方案都不应该让司机行驶超过一定的距离。我有一个函数,它接受司机(与 driver_set 中的实体格式相同)并计算司机接载所有乘客的最短距离。我尝试添加这样的约束:

for driver in driver_set:
        self.problem.addConstraint(MaxSumConstraint(MAX_DISTANCE), [self.getRouteDistance(self.getShortestRoute(driver))])

这给出了以下错误:

KeyError: 1.8725031790578293

我不确定应如何为 python-constraint 定义此约束:每个驱动程序只有一个最短距离值。我应该为此使用 lambda 函数吗?

编辑

我尝试实现它的 lambda 版本,但是我似乎没有 lambda 语法。我到处都看过,但似乎找不到这有什么问题。基本上我替换了最后一段代码(添加约束来限制 getRouteDistance(driver) 的值)并改为这样:

for driver in driver_set:
    self.problem.addConstraint(lambda d: self.getRouteDistance(d) <= float(MAX_DISTANCE), driver)

但后来我得到了这个错误(注意它不是从我编辑的行调用的,它来自 Problem.getSolutions( ):

File "allocation.py", line 130, in buildProblem
for solution in self.problem.getSolutions():
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 236, in getSolutions
return self._solver.getSolutions(domains, constraints, vconstraints)
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 529, in getSolutions
return list(self.getSolutionIter(domains, constraints, vconstraints))
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 506, in getSolutionIter
pushdomains):
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 939, in __call__
self.forwardCheck(variables, domains, assignments)))
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 891, in forwardCheck
if not self(variables, domains, assignments):
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 940, in __call__
return self._func(*parms)
TypeError: <lambda>() takes exactly 1 argument (3 given)

有其他人尝试过做这样的事情吗?我不明白为什么约束库不允许这样做。

I've been making a system that takes in data about drivers, potential passengers and their locations, and attempts to optimise the number of passengers that can get a lift with a driver given some constraints. I am using the python-constraint module, and the decision variables are represented thusly:

p = [(passenger, driver) for driver in drivers for passenger in passengers]
driver_set = [zip(passengers, [e1]*len(drivers)) for e1 in drivers]
passenger_set = [zip([e1]*len(passengers), drivers) for e1 in passengers]
self.problem.addVariables(p, [0,1])

So, when I print the value of p and the driver_set and passenger_set, I get the following output (given the test data I provided):

[(0, 0), (1, 0), (2, 0), (0, 1), (1, 1), (2, 1)] # p
[[(0, 0), (0, 1)], [(1, 0), (1, 1)], [(2, 0), (2, 1)]] # passenger_set
[[(0, 0), (1, 0)], [(0, 1), (1, 1)]] # driver_set

So, there are 3 passengers and 2 drivers: the variable (2,0) would mean that passenger 2 is in car 0, and so on. I have added the following constraints to make sure that no passenger goes in more than one car, and that a driver can't have more people than seats:

for passenger in passenger_set:
        self.problem.addConstraint(MaxSumConstraint(1), passenger)
for driver in driver_set:
        realdriver = self.getDriverByOpId(driver[0][1])
        self.problem.addConstraint(MaxSumConstraint(realdriver.numSeats), driver)

This worked - all the solutions generated satisfied these constraints. However, I would now like to add constraints saying that any solution shouldn't involve the drivers going more than a certain distance. I have a function that takes in a driver (same format as an entity from driver_set) and calculates the shortest distance for the driver to pick up all passengers. I have tried to add the constraints like this:

for driver in driver_set:
        self.problem.addConstraint(MaxSumConstraint(MAX_DISTANCE), [self.getRouteDistance(self.getShortestRoute(driver))])

This gave the following error:

KeyError: 1.8725031790578293

I'm not sure how this constraint should be defined for python-constraint: there's only one shortest distance value for each driver. Should I use a lambda function for this?

EDIT

I tried implementing a lambda version of this, however I don't seem to have the lambda syntax down. I've looked everywhere but can't seem to find what's wrong with this. Basically I replaced the last snippet of code (adding the constraint to limit the value of getRouteDistance(driver)) and instead put this:

for driver in driver_set:
    self.problem.addConstraint(lambda d: self.getRouteDistance(d) <= float(MAX_DISTANCE), driver)

But then I got this error (notice it's not called from the line I edited, it's from problem.getSolutions() which comes after):

File "allocation.py", line 130, in buildProblem
for solution in self.problem.getSolutions():
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 236, in getSolutions
return self._solver.getSolutions(domains, constraints, vconstraints)
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 529, in getSolutions
return list(self.getSolutionIter(domains, constraints, vconstraints))
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 506, in getSolutionIter
pushdomains):
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 939, in __call__
self.forwardCheck(variables, domains, assignments)))
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 891, in forwardCheck
if not self(variables, domains, assignments):
File "/Users/wadben/Documents/Dev/Python/sp-allocation/constraint.py", line 940, in __call__
return self._func(*parms)
TypeError: <lambda>() takes exactly 1 argument (3 given)

Has anyone else tried to do anything like this? I can't see why the constraint library wouldn't allow this.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

御守 2024-10-24 15:38:30

Python 中的 lambda 形式提供了一种创建匿名(无名)函数的方法。以下两个定义是等效的:

name = lambda arguments: expression

def name(arguments):
    return expression

由于 lambda 表达式的主体本身就是一个表达式,因此主体可能不包含任何语句(如 print)。

当向问题添加函数约束时,必须确保函数接受的参数与变量的数量一样多。应用约束时,每个参数都会传递一个当前绑定到相应变量的值(根据约定,驾驶员和乘客一起骑行为 1,否则为 0)。

由于与给定驾驶员关联的变量数量(等于乘客数量)可能会发生变化,因此约束中的函数接受任意数量的参数是明智的。这可以在 Python 中使用位置参数来完成。因此,对于给定的一组驱动程序变量(此处使用名称 driver_variables),约束采用以下形式:

problem.addConstraint(FunctionConstraint(lambda *values: ...), driver_variables)

参数值绑定到当前绑定到 driver_variables 列表中相应变量的值列表。编写 lambda 主体时应执行以下操作:

  1. 创建一个列表,将值列表中的每个值(0 或 1)与 driver_variables 列表中的相应变量关联起来;
  2. 从这个列表中,选择值为1的变量(对应于与司机一起乘坐的乘客)——这个列表形成了司机所走的路线;
  3. 查找路线距离(在本例中使用 get_route_distance 函数)并与最大值 (maximum_distance) 进行比较。

可以对 (1) 使用 zip(保证值的顺序与变量的顺序相同),对 (2) 使用列表理解,对 (3) 使用简单的函数调用和比较。这会产生一个采用以下 lambda 形式的函数:

lambda *values: get_route_distance([variable for variable, value in zip(driver_variables, values) if value == 1]) <= maximum_distance

使用 def 显式编写此函数可能有利于代码的可读性。

另外需要注意的是,上面定义 driver_set 的代码中存在一个错误。 driver_set 的正确值应该是:

[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)]]

在上面的示例中,由于 len(drivers) 为 2,因此 zip(passengers, [e1]*len(drivers)) 被截断为仅两项。解决此问题的一种方法是对 driver_set 使用表达式 zip(passengers, [e1]*len(passengers))(并对passengers_set 进行类似的更改)。然而,还有一种更Pythonic的方式。

可以使用以下语句生成正确的乘客和司机集(本例中为passengers_variables 和drivers_variables):

passengers_variables = [[(passenger, driver) for driver in drivers] for passenger in passengers]
drivers_variables = [[(passenger, driver) for passenger in passengers] for driver in drivers]

The lambda form in Python provides a way to create anonymous (nameless) functions. The following two definitions are equivalent:

name = lambda arguments: expression

def name(arguments):
    return expression

Since the body of a lambda expression is itself an expression, the body may not contain any statements (like print).

When adding a function constraint to a problem, one must make sure that the function accepts as many arguments as there are variables. When the constraint is applied, each argument is passed a value (according to your convention, 1 for driver and passenger riding together, 0 otherwise) currently bound to the corresponding variable.

Since the number of variables associated with a given driver (equal to the number of passengers) may change, it would be sensible for the function in the constraint to accept an arbitrary number of arguments. This can be accomplished in Python using positional arguments. Thus, for a given set of driver variables (one uses the name driver_variables here), the constraint takes the following form:

problem.addConstraint(FunctionConstraint(lambda *values: ...), driver_variables)

The argument values binds to a list of values currently bound to corresponding variables in the driver_variables list. The lambda body should be written so as to do the following:

  1. Make a list to associate each value (0 or 1) in the values list with the corresponding variable in the driver_variables list;
  2. From this list, choose variables that have the value 1 (corresponding to passengers riding with the driver)--this list forms the route taken by the driver;
  3. Find the route distance (using get_route_distance function in this example) and compare against the maximum (maximum_distance).

One may use zip for (1) (the order of values is guaranteed to be the same as the order of variables), list comprehension for (2) and a simple function call and comparison for (3). This yields a function that takes the following lambda form:

lambda *values: get_route_distance([variable for variable, value in zip(driver_variables, values) if value == 1]) <= maximum_distance

It may prove beneficial for readability of code to write this function explicitly using def.

On a separate note, there is a bug in the code for defining driver_set above. The proper value for driver_set should be:

[[(0, 0), (1, 0), (2, 0)], [(0, 1), (1, 1), (2, 1)]]

In the above example, since len(drivers) is 2, zip(passengers, [e1]*len(drivers)) is truncated to only two items. One way to fix this is to use the expression zip(passengers, [e1]*len(passengers)) for driver_set (and make a similar change for passenger_set). However, there is a more Pythonic way.

One may generate correct passenger and driver sets (passengers_variables and drivers_variables in this example) using the following statements:

passengers_variables = [[(passenger, driver) for driver in drivers] for passenger in passengers]
drivers_variables = [[(passenger, driver) for passenger in passengers] for driver in drivers]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文