解决关系集的算法
An algorithm to solve sets of relations
我正在编写一个程序来处理各种方程式。我有一个方程列表,以及一个可以求解给定方程的未知值的函数。
例如:a + b + c = 0 -> 可以求解任何变量,给定其他 2。
我现在需要编写一个算法,该算法采用关系列表、已知值列表和目标,然后找到为找到未知值而必须采取的一系列步骤。
例如:(a = b, b = c) a = 1, 求c:
结果将显示您首先将 a -> b 转换为“a = b”,然后将 b -> c 转换为“b = c”
如果能帮助我研究算法或搜索词,我们将不胜感激
假设我们有:
- 方程式列表
eqs
;
- 初始变量列表
xs
;
- 一个函数
f(eq, xs)
,给定方程 eq
和变量名称列表 xs
,returns 变量列表 ys
,其值如果我们知道 xs
; 中变量的值,可以使用等式 eq
找到
- 一个目标变量
target
.
那么你的问题有点像“最短路径”问题,试图找到从初始变量列表到包含目标变量的列表的路径。
我们可以执行 breadth-first-search,尝试扩展已知变量列表,直到它包含目标。
eqs = '''3 * a + 2 * b + c == 0
a + b = 12
c + 4 * b = 4'''.split('\n')
def f(eq, xs, all_variables='abc'):
ys = set(eq).intersection(all_variables).difference(xs)
return ys.pop() if len(ys) == 1 else None
target = 'c'
xs = ['a']
def find_steps(eqs, xs, f, target):
if target in xs:
return (('', xs),)
paths = [(('', frozenset(xs)),)]
for _ in range(len(eqs)):
new_paths = []
for path in paths:
_,xs = path[-1]
for eq in eqs:
y = f(eq, xs)
if y is not None:
new_path = path + ((eq, xs.union({y})),)
if y == target:
return new_path
new_paths.append(new_path)
paths = new_paths
return None
print( find_steps(eqs, xs, f, target) )
# (('', {'a'}),
# ('a + b = 12', {'b', 'a'}),
# ('3 * a + 2 * b + c == 0', {'b', 'c', 'a'}))
一些解释。外循环 for _ in range(len(eqs)):
在概念上不是 for
循环。这是一个 运行s 的循环,直到找到目标(在这种情况下循环被 return new_path
打破)或者直到循环 运行 太久而没有找到目标:最长路径不能长于方程的数量。或者可以吗?如果可以,则需要修改停止条件。
我们实现了 breadth-first-search。在外循环的迭代 i
处,paths
是长度为 i
的所有路径的列表。对于每个长度为 i
的路径,我们尝试以所有可能的方式将其扩展为长度为 i+1
的路径,并将这些新路径添加到 new_paths
.
路径是一系列步骤。一步是一对 (equation, known_variables)
。初始步骤始终是 ('', initial_known_variables)
。如果新步骤包含新的已知变量,我们只会扩展路径。
你构建了一个依赖图。然后你填写给定的变量,计算取决于什么,计算什么取决于你刚刚计算的等等
这当然并不总是奏效,您可能会立即或在执行某些此类步骤后陷入死胡同。最简单的例子是方程组:
a + b + c + 3 = 0
a - b + c - 5 = 0
你得到的是 c = 42,a 是多少?或者更糟的是,依赖于 a 的其他变量是什么(并且可能是其他方程组的一部分)?您可以在此处执行高斯消去法来计算 a,但是如果您有一个 non-linear 系统或您无法分析的方程怎么办?然后你需要将一堆方程式发送给求解器。
所以现在您没有依赖于其他变量的变量,而是 组 依赖于其他组的变量。在上面的例子中,{a, b} 依赖于 c(并且 {a, c} 依赖于 b,而 {b, c} 依赖于 a)。
人们可能会想在 组变量 的图表上 运行 某种 shortest-path 算法。然而,在一个包含 50 个方程和 50 个变量的系统中,依赖于其他集合的集合的数量超出了我们的计算能力。然而,一次用数值求解整个系统是可行的。
所以有一个解决这个问题的通用方法。只需将其作为一个大方程组全部提供给求解器即可。你得到一堆变量赋值,没有任何明确的解决步骤序列(除非你考虑 Newton-Raphson 的迭代或任何“解决步骤”)。然后有特定的方法来解决特定的受限类型的系统,为您提供清晰的解决方案步骤(线性系统、single-variable 方程组及其组合)。当然,你可以有一些明确定义的求解步骤,然后一次求解一大组方程,因为你没有其他方法可以求解它们,然后有一些更明确的步骤,然后求解一个不同的方程组,等等。
因此,您可以尝试遍历依赖关系图并生成解决方案步骤,但您总是有可能会卡在某个只能一次解决所有问题的大型系统中。更糟糕的是,可能有不同的解决方案,所有这些都是通过求解不同的方程组,当然有些系统表现不佳(具有不稳定的解决方案,或者难以猜测合适的近似值的解决方案,等等) .
还不如省去所有这些麻烦,把你所有的东西都扔给数值求解器,让它来解决这个问题。
(方程式是指任何看起来像 'f(x, y, ...) = 0' 的对象,其中 f 是某个函数。有时你会知道函数是什么,即它是象征性地给你的,有时你不知道t,即它是由对您不透明的外部算法计算得出的)。
我正在编写一个程序来处理各种方程式。我有一个方程列表,以及一个可以求解给定方程的未知值的函数。
例如:a + b + c = 0 -> 可以求解任何变量,给定其他 2。
我现在需要编写一个算法,该算法采用关系列表、已知值列表和目标,然后找到为找到未知值而必须采取的一系列步骤。
例如:(a = b, b = c) a = 1, 求c:
结果将显示您首先将 a -> b 转换为“a = b”,然后将 b -> c 转换为“b = c”
如果能帮助我研究算法或搜索词,我们将不胜感激
假设我们有:
- 方程式列表
eqs
; - 初始变量列表
xs
; - 一个函数
f(eq, xs)
,给定方程eq
和变量名称列表xs
,returns 变量列表ys
,其值如果我们知道xs
; 中变量的值,可以使用等式 - 一个目标变量
target
.
eq
找到
那么你的问题有点像“最短路径”问题,试图找到从初始变量列表到包含目标变量的列表的路径。
我们可以执行 breadth-first-search,尝试扩展已知变量列表,直到它包含目标。
eqs = '''3 * a + 2 * b + c == 0
a + b = 12
c + 4 * b = 4'''.split('\n')
def f(eq, xs, all_variables='abc'):
ys = set(eq).intersection(all_variables).difference(xs)
return ys.pop() if len(ys) == 1 else None
target = 'c'
xs = ['a']
def find_steps(eqs, xs, f, target):
if target in xs:
return (('', xs),)
paths = [(('', frozenset(xs)),)]
for _ in range(len(eqs)):
new_paths = []
for path in paths:
_,xs = path[-1]
for eq in eqs:
y = f(eq, xs)
if y is not None:
new_path = path + ((eq, xs.union({y})),)
if y == target:
return new_path
new_paths.append(new_path)
paths = new_paths
return None
print( find_steps(eqs, xs, f, target) )
# (('', {'a'}),
# ('a + b = 12', {'b', 'a'}),
# ('3 * a + 2 * b + c == 0', {'b', 'c', 'a'}))
一些解释。外循环 for _ in range(len(eqs)):
在概念上不是 for
循环。这是一个 运行s 的循环,直到找到目标(在这种情况下循环被 return new_path
打破)或者直到循环 运行 太久而没有找到目标:最长路径不能长于方程的数量。或者可以吗?如果可以,则需要修改停止条件。
我们实现了 breadth-first-search。在外循环的迭代 i
处,paths
是长度为 i
的所有路径的列表。对于每个长度为 i
的路径,我们尝试以所有可能的方式将其扩展为长度为 i+1
的路径,并将这些新路径添加到 new_paths
.
路径是一系列步骤。一步是一对 (equation, known_variables)
。初始步骤始终是 ('', initial_known_variables)
。如果新步骤包含新的已知变量,我们只会扩展路径。
你构建了一个依赖图。然后你填写给定的变量,计算取决于什么,计算什么取决于你刚刚计算的等等
这当然并不总是奏效,您可能会立即或在执行某些此类步骤后陷入死胡同。最简单的例子是方程组:
a + b + c + 3 = 0
a - b + c - 5 = 0
你得到的是 c = 42,a 是多少?或者更糟的是,依赖于 a 的其他变量是什么(并且可能是其他方程组的一部分)?您可以在此处执行高斯消去法来计算 a,但是如果您有一个 non-linear 系统或您无法分析的方程怎么办?然后你需要将一堆方程式发送给求解器。
所以现在您没有依赖于其他变量的变量,而是 组 依赖于其他组的变量。在上面的例子中,{a, b} 依赖于 c(并且 {a, c} 依赖于 b,而 {b, c} 依赖于 a)。
人们可能会想在 组变量 的图表上 运行 某种 shortest-path 算法。然而,在一个包含 50 个方程和 50 个变量的系统中,依赖于其他集合的集合的数量超出了我们的计算能力。然而,一次用数值求解整个系统是可行的。
所以有一个解决这个问题的通用方法。只需将其作为一个大方程组全部提供给求解器即可。你得到一堆变量赋值,没有任何明确的解决步骤序列(除非你考虑 Newton-Raphson 的迭代或任何“解决步骤”)。然后有特定的方法来解决特定的受限类型的系统,为您提供清晰的解决方案步骤(线性系统、single-variable 方程组及其组合)。当然,你可以有一些明确定义的求解步骤,然后一次求解一大组方程,因为你没有其他方法可以求解它们,然后有一些更明确的步骤,然后求解一个不同的方程组,等等。
因此,您可以尝试遍历依赖关系图并生成解决方案步骤,但您总是有可能会卡在某个只能一次解决所有问题的大型系统中。更糟糕的是,可能有不同的解决方案,所有这些都是通过求解不同的方程组,当然有些系统表现不佳(具有不稳定的解决方案,或者难以猜测合适的近似值的解决方案,等等) .
还不如省去所有这些麻烦,把你所有的东西都扔给数值求解器,让它来解决这个问题。
(方程式是指任何看起来像 'f(x, y, ...) = 0' 的对象,其中 f 是某个函数。有时你会知道函数是什么,即它是象征性地给你的,有时你不知道t,即它是由对您不透明的外部算法计算得出的)。