解决序言中的连锁反应

Solving chain reactions in prolog

One of the recent Advent of code challenges 让我解决最小的输入量 material,我可以用它来应用一组给定的反应并获得 1 个单位的输出 material。

例如给定

10 ORE => 10 A
1 ORE => 1 B
7 A, 1 B => 1 C
7 A, 1 C => 1 D
7 A, 1 D => 1 E
7 A, 1 E => 1 FUEL

我们总共需要 31 个矿石来制造 1 个燃料(1 个矿石来生产一个单位的 B,然后 30 个矿石来制造所需的 28 个 A)。

今年,我一直在努力拓展我的编程语言视野,所以我已经完成了 SML/NJ 中的大部分挑战。这个看起来——似乎——很适合 Prolog,因为我对它知之甚少:逻辑编程、约束求解等

但是,我还没有能够成功地对约束进行建模。

我首先将这个简单的例子变成了一些事实:

makes([ore(10)], a(10)).
makes([ore(1)], b(1)).
makes([a(7), b(7)], c(1)).
makes([a(7), c(1)], d(1)).
makes([a(7), d(1)], e(1)).
makes([a(7), e(1)], fuel(1)).

老实说,我什至不确定列表参数是否是一个好的结构,或者函子符号 (ore(10)) 是否是一个好的模型。

然后我想建立允许你说的规则,例如,10 矿石足够 7 a:

% handles the case where we have leftovers?
% is this even the right way to model all this... when we have leftovers, we may
% have to use them in the "reaction"...
makes(In, Out) :-
    Out =.. [F,N],
    Val #>= N,
    OutN =.. [F,Val],
    makes(In, OutN).

这有效1,但我不确定它是否足够,因为我们可能关心剩菜(毕竟这是一个最小化问题)?

虽然我卡在了接下来的两篇文章中:

  1. 我可以问是什么让 7 A 得到 10 矿石,但我不能问什么才够 20 A:如何编写编码 multiplication/integer 因素的规则?
  2. 我可以说 7 A 和 1 E 产生 1 燃料,但我不能递归地说明:也就是说,我不能说明 14 A 和 1 D 制作1个燃料。我该如何编写对此进行编码的规则?

我愿意为我呈现的事实使用替代数据编码——最终,我将编写从 Advent 的输入到 Prolog 的事实的转换脚本,所以这是我最不担心的。我觉得如果我能让这个小例子运行起来,我就能解决更大的问题。


  1. ?- makes(X, a(7)). 无限回馈 X=[ore(10)](即,如果我在提示符下继续点击 ;,它会继续)。有办法解决这个问题吗?

不是您的具体问题的直接答案,但我对这个问题的第一个想法是在 Prolog 中使用 chr。

然后我想我会将链从 fuel 转发到我需要的 ore 数量。

基本约束:

:- chr_constraint ore/1, a/1, b/1,c/1, ab/1, bc/1, ca/1, fuel/0.

a(1),a(1) <=> ore(9).
b(1),b(1),b(1) <=> ore(8).
c(1),c(1),c(1),c(1),c(1) <=> ore(7).

ab(1) <=> a(3),b(4).
bc(1) <=> b(5),c(7).
ca(1) <=> c(4),a(1).
fuel <=> ab(2),bc(3),ca(4).

%Decompose foo/N into foo/1s

a(X) <=> X>1,Y#=X-1|a(Y),a(1).
b(X) <=> X>1,Y#=X-1|b(Y),b(1).
c(X) <=> X>1, Y#=X-1 | c(Y),c(1).

ab(X) <=> X>1, Y#=X-1|ab(Y),ab(1).
bc(X) <=> X>1,Y#=X-1| bc(Y),bc(1).
ca(X) <=> X>1, Y#= X-1| ca(Y),ca(1).

ore(X)<=>X >1, Y #= X -1 |ore(Y),ore(1).

%aggregation (for convenience) 
:- chr_constraint ore_add/1, total_ore/1.

total_ore(A), total_ore(Total) <=> NewTotal #= A + Total, total_ore(NewTotal).
ore_add(A) ==> total_ore(A).

ore(1) <=> ore_add(1).

查询:

?-fuel.
b(1),
b(1),
c(1),
c(1),
ore_add(1),
ore_add(1),
...
total_ore(150).

然后你需要添加一个搜索程序来消除两个 b/1s 和两个 c/1s.

我还没有实现这个但是:

?-fuel,b(1),c(3).
ore_add(1),
...
total_ore(165)

这只有 ore_add/1 个约束,是正确的结果。

示例中没有 "alternative" 路径,也没有多个 "ore sources",因此可以像这样使用 Prolog 以非常不灵活的方式对示例进行编码:

need(FUEL,OREOUT) :- need(FUEL,0,0,0,0,0,0,OREOUT).

need(FUEL,E,D,C,A,B,ORE,OREOUT) :- FUEL > 0, A2 is 7*FUEL+A, E2 is FUEL+E, need(0, E2, D, C, A2, B, ORE,OREOUT).
need(0,E,D,C,A,B,ORE,OREOUT)    :- E > 0, A2 is 7*E+A, D2 is E+D, need(0, 0, D2, C, A2, B, ORE,OREOUT).
need(0,0,D,C,A,B,ORE,OREOUT)    :- D > 0, A2 is 7*D+A, C2 is D+C, need(0, 0, 0, C2, A2, B, ORE,OREOUT).
need(0,0,0,C,A,B,ORE,OREOUT)    :- C > 0, A2 is 7*C+A, B2 is C+B, need(0, 0, 0, 0, A2, B2, ORE,OREOUT).
need(0,0,0,0,A,B,ORE,OREOUT)    :- X is A + B, X > 0, ORE2 is ORE + (A + 9)//10 + B, need(0, 0, 0, 0, 0, 0, ORE2,OREOUT).
need(0, 0, 0, 0, 0, 0, ORE, ORE).

然后

?- need(1011,ORE).
ORE = 3842

但这只是一种愚蠢而不优雅的尝试。

其下潜伏着一个重大的通用问题,包括解析任意复杂的反应有向无环图并构建合适的结构。好的想法是它是一个 DAG,所以不能从 "later one" 生成 "earlier ingredient"。

在煮咖啡的时候,这显然是 CLP(FD) 引擎的事情。

如果我们有定向无环反应图

  • 图右侧的FUEL节点和
  • 中间产品节点IP[i] (i in 0..n) 介于两者之间,可能
  • 几个 FUEL 节点,即产生 FUEL 的几种方式:FUEL[0] ... FUEL[v] 并且可能
  • 中间产品IP[i]的几个节点,即创建中间产品IP[i>0]的几种方式:IP[i,1] ... IP[i,ways(i)] and
  • IP[0] 标识为图表左侧的 ORE

最后两点为我们提供了一种选择产品组合策略的方法,然后:

FUEL_NEEDED   = mix[0] * FUEL[0] + ... + mix[v] * FUEL[v]

上面的所有内容都是一个变量

问题陈述给出以下内容,FUEL[0] ... FUEL[v] 变量和其余常量:

out_fuel[0] * FUEL[0] = ∑_j ( IP[j] * flow(IPj->FUEL0) )
⋮
out_fuel[v] * FUEL[v] = ∑_j ( IP[j] * flow(IPj->FUELv) )

并且对于每个 IP[i>0],IP[i] 变量和其余常量:

out_ip[i] * IP[i] = ∑_j≠i ( IP[j] * flow(IPj->IPi) )

如果有几种生成IP[i]的方法,我们混合(这就像从IP[i,j]的可能方式中为IP[i]的混合引入一个图节点):

out_ip[i]   * IP[i]   = ∑_j(0..ways(i)) ( IP[i,j] * mix[i,j] )

out_ip[i,1] * IP[i,1] = ∑_j≠i ( IP[j] * flow(IP[j]->IP[i,1]) )
⋮
out_ip[i,ways(i)] * IP[i,ways(i)] = ∑_j≠i ( IP[j] * flow(IP[j]->IP[i,ways(i)]) )

IP[0](即ORE)一个要最小化的自由变量。

您看到这里出现了一个未指定的线性规划问题,其中一个矩阵在对角线下方有零,因为它是一个 DAG,但它包含要在矩阵本身中优化的变量。如何攻击它?