Python 约束传播-如何处理一对多方向的三元约束
我在以下代码中实现了一个框架(本书第二版第3.3.5节):Python 约束传播-如何处理一对多方向的三元约束,python,constraint-programming,Python,Constraint Programming,我在以下代码中实现了一个框架(本书第二版第3.3.5节): import abc import numpy as np class Connector: """An object to store observable values.""" def __init__(self): self._value = None self._informant = None self._constraints = [] def
import abc
import numpy as np
class Connector:
"""An object to store observable values."""
def __init__(self):
self._value = None
self._informant = None
self._constraints = []
def has_value(self,):
"""Return true if connector has a value."""
return self._informant is not None
def get_value(self,):
"""Return connector value."""
return self._value
def set_value(self, new_value, informant='-USER-'):
"""Set value if none exists and inform constraints."""
if not self.has_value():
self._value = new_value
self._informant = informant
for constraint in reversed(self._constraints):
if constraint != self._informant:
constraint.new_value()
elif not np.array_equal(new_value, self._value):
# Note: np needed as != is ambiguous on arrays.
raise Contradiction("{} vs. {}".format(self._value, new_value))
def forget_value(self, retractor='-USER-'):
"""Forget value and inform constraints."""
if retractor == self._informant:
self._informant = None
for constraint in reversed(self._constraints):
if constraint != retractor:
constraint.forget_value()
def connect(self, new_constraint):
"""Add a new constraint."""
if new_constraint not in self._constraints:
self._constraints.append(new_constraint)
if self.has_value():
new_constraint.new_value()
class Constraint(metaclass=abc.ABCMeta):
"""ABC for constraints among connectors."""
@abc.abstractmethod
def new_value(self,):
"""Notify constraint of a new connector value."""
pass
@abc.abstractmethod
def forget_value(self,):
"""Notify constraint to forget all observed connector values."""
pass
class Adder(Constraint):
"""Defines the constraint x + y == z"""
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
self.x.connect(self)
self.y.connect(self)
self.z.connect(self)
def new_value(self,):
if self.x.has_value() and self.y.has_value():
self.z.set_value(self.x.get_value() + self.y.get_value(), self)
elif self.x.has_value() and self.z.has_value():
self.y.set_value(self.z.get_value() - self.x.get_value(), self)
elif self.y.has_value() and self.z.has_value():
self.x.set_value(self.z.get_value() - self.y.get_value(), self)
def forget_value(self,):
self.z.forget_value(self)
self.x.forget_value(self)
self.y.forget_value(self)
self.new_value()
class Multiplier(Constraint):
"""Defines the constraint x * y == z."""
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
self.x.connect(self)
self.y.connect(self)
self.z.connect(self)
def new_value(self,):
if self.x.has_value() and self.y.has_value():
self.z.set_value(self.x.get_value() * self.y.get_value(), self)
elif self.x.has_value() and self.z.has_value():
self.y.set_value(self.z.get_value() / self.x.get_value(), self)
elif self.y.has_value() and self.z.has_value():
self.x.set_value(self.z.get_value() / self.y.get_value(), self)
def forget_value(self,):
self.z.forget_value(self)
self.x.forget_value(self)
self.y.forget_value(self)
self.new_value()
class Power(Constraint):
"""Defines the constraint x ** y == z."""
def __init__(self, x, y, z):
self.x = x
self.y = y
self.z = z
self.x.connect(self)
self.y.connect(self)
self.z.connect(self)
def new_value(self,):
if self.x.has_value() and self.y.has_value():
self.z.set_value(self.x.get_value() ** self.y.get_value(), self)
elif self.x.has_value() and self.z.has_value():
self.y.set_value(np.log(self.z.get_value()) / np.log(self.x.get_value()), self)
elif self.y.has_value() and self.z.has_value():
self.x.set_value(self.z.get_value() ** (1.0 / self.y.get_value()), self)
def forget_value(self,):
self.z.forget_value(self)
self.x.forget_value(self)
self.y.forget_value(self)
self.new_value()
class Equality(Constraint):
"""Defines the constraint x == y."""
def __init__(self, x, y):
self.x = x
self.y = y
self.x.connect(self)
self.y.connect(self)
def new_value(self,):
if self.x.has_value():
self.y.set_value(self.x.get_value(), self)
elif self.y.has_value():
self.x.set_value(self.y.get_value(), self)
def forget_value(self,):
self.y.forget_value(self)
self.x.forget_value(self)
self.new_value()
class Constant(Constraint):
"""Defines the constraint x == value."""
def __init__(self, x, value):
x.connect(self)
x.set_value(value, self)
def new_value(self):
raise Exception()
def forget_value(self):
raise Exception()
class Probe(Constraint):
"""Prints whenever connector has a new value."""
def __init__(self, connector, name):
self.name = name
self.connector = connector
self.connector.connect(self)
def new_value(self,):
self._print_probe(self.connector.get_value())
def forget_value(self,):
self._print_probe("?")
def _print_probe(self, value):
print("Probe: {} = {}".format(self.name, value))
class Contradiction(Exception):
"""Raised whenever a connector has an existing value but
is asked to change a different value.
"""
pass
该框架在两个方向上都能很好地复制SICP中的Celcius/Fahrenheit示例,如下面的doctest所示:
>>> def celcius_fahrenheit_converter(c, f):
... u = Connector()
... v = Connector()
... w = Connector()
... x = Connector()
... y = Connector()
... Multiplier(c, w, u)
... Multiplier(v, x, u)
... Adder(v, y, f)
... Constant(w, 9)
... Constant(x, 5)
... Constant(y, 32)
>>> c = Connector()
>>> f = Connector()
>>> celcius_fahrenheit_converter(c, f)
>>> c_probe = Probe(c, "Celcius temp")
>>> f_probe = Probe(f, "Fahrenheit temp")
>>> c.set_value(25.0)
Probe: Celcius temp = 25.0
Probe: Fahrenheit temp = 77.0
>>> f.set_value(212.0)
Traceback (most recent call last):
...
Contradiction: 77.0 vs. 212.0
>>> c.forget_value()
Probe: Celcius temp = ?
Probe: Fahrenheit temp = ?
>>> f.set_value(212.0)
Probe: Fahrenheit temp = 212.0
Probe: Celcius temp = 100.0
当我试图使用该框架解决一个物理问题(位移与给定加速度的时间之比)时,我注意到“反向”方向并没有在该网络中传播。我已经确定了一个简单的例子(在本例中,二次方程y=ax**2+bx+c)展示了相同的行为。如果我尝试求解y
给定的x
,它会完全传播,但如果我尝试求解x
给定的y
,它不会传播。请参阅以下doctest(探测内部连接器i1、i2和i3以说明问题):
当试图将约束从y
传播到x
时,运行上述doctest会产生以下错误:
Failed example:
y.set_value(301.0)
Expected:
Probe: y = 301.0
Probe: i1 = 300.0
Probe: i2 = 100.0
Probe: i3 = 200.0
Probe: x = 10.0
Got:
Probe: y = 301.0
Probe: i1 = 300.0
**********************************************************************
1 items had failures:
1 of 47 in __main__
***Test Failed*** 1 failures.
发生的事情是,当我告诉网络忘记x
的值时,连接i1
、i2
和i3
的加法器
约束忘记了所有3个值。因此,当传播沿y
方向返回到x
方向时,加法器
发现了两个未知数(i2
和i3
,其中i1
直接从y
和c
,这是已知的)。尽管等式只有一个未知项(即x
),但不幸的是,该未知项影响了损坏的加法器中发现的三个连接器中的两个
问题
如何修复约束网络的设计或传播算法,以确保在这个简单的二次示例中,我能够计算给定的x
和给定的y
我试过什么
我曾尝试研究数值约束满足算法,但没有发现任何适用于此的算法
我试图通过重新设计约束网络本身来进行推理,使得x
仅连接到一个约束(在这个特定情况下,它可能是二次方程本身),但a)我无法在本例中实现,b这似乎不是一般解
我曾尝试通过不同的约束算法(例如搜索)进行推理,但我认为对于一个完全由等式约束定义的网络来说,这可能是过分的
Failed example:
y.set_value(301.0)
Expected:
Probe: y = 301.0
Probe: i1 = 300.0
Probe: i2 = 100.0
Probe: i3 = 200.0
Probe: x = 10.0
Got:
Probe: y = 301.0
Probe: i1 = 300.0
**********************************************************************
1 items had failures:
1 of 47 in __main__
***Test Failed*** 1 failures.