Mic*_*rge 7 recursion wolfram-mathematica dynamic-programming exponential
在Mathematica 8.0中,假设我有一些常量:
a:=7
b:=9
c:=13
d:=.002
e:=2
f:=1
Run Code Online (Sandbox Code Playgroud)
我想用它们来评估一些相互关联的功能
g[0,k_]:=0
g[t_,0]:=e
g[t_,k_]:=g[t-1,k]*a+h[t-1,k-1]*b
h[0,k_]:=0
h[t_,0]:=f
h[t_,k_]:=h[t-1,k]*c+g[t-1,k-1]*d
Run Code Online (Sandbox Code Playgroud)
但这真的很慢,需要动态编程,否则你会出现指数减速:
g[0, k_] := 0
g[t_, 0] := e
g[t_, k_] := g[t, k] = g[t - 1, k]*a + h[t - 1, k - 1]*b
h[0, k_] := 0
h[t_, 0] := f
h[t_, k_] := h[t, k] = h[t - 1, k]*c + g[t - 1, k - 1]*d
Run Code Online (Sandbox Code Playgroud)
现在,它的真快,但是如果我们想改变常量(比如,在操纵功能,使用此),我们必须Clear g和h每个时间.如果我们有复杂的相互依赖性,这可能是真的很烦人,以清除它们全力以赴我们希望从一个值每一次g和h.
有一种简单的方式来运行g,并h在一个Module或Block或相似,这样我就可以在每次没有指数放缓评估真实时间取得一个新的结果回来?或者甚至是一种快速的方式来建立一个结果表,g并h以一种很好的方式?正如所说,我希望能够计算g并h在一个Manipulate功能.
Leo*_*rin 10
这是一种方法,Block按您的要求使用:
ClearAll[defWrap];
SetAttributes[defWrap, HoldFirst];
defWrap[fcall_] :=
Block[{g, h},
(* Same defintions with memoization as you had, but within Block*)
g[0, k_] := 0;
g[t_, 0] := e;
g[t_, k_] := g[t, k] = g[t - 1, k]*a + h[t - 1, k - 1]*b;
h[0, k_] := 0;
h[t_, 0] := f;
h[t_, k_] := h[t, k] = h[t - 1, k]*c + g[t - 1, k - 1]*d;
(* Our function call, but within a dynamic scope of Block *)
fcall];
Run Code Online (Sandbox Code Playgroud)
我们将使用它来定义f和h作为
ClearAll[g, h];
g[tt_, kk_] := defWrap[g[tt, kk]];
h[tt_, kk_] := defWrap[h[tt, kk]];
Run Code Online (Sandbox Code Playgroud)
我们现在打电话:
In[1246]:= g[20,10]//Timing
Out[1246]= {0.,253809.}
In[1247]:= h[20,10]//Timing
Out[1247]= {6.50868*10^-15,126904.}
Run Code Online (Sandbox Code Playgroud)
每次调用后都没有留下全局的memoized定义 - Block在执行退出之前注意销毁它们Block.特别是,在这里我将更改参数并再次调用它们:
In[1271]:=
a:=1
b:=2
c:=3
d:=.01
e:=4
f:=5
In[1279]:= g[20,10]//Timing
Out[1279]= {0.015,0.808192}
In[1280]:= h[20,10]//Timing
Out[1280]= {0.,1.01024}
Run Code Online (Sandbox Code Playgroud)
该方案的替代方案是显式地传递所有参数,例如a,b,c,d,e,f函数,扩展它们的形式参数列表(签名),但是这样做的缺点是不会自动清除对应于不同过去参数值的较旧的存储定义.该方法的另一个问题是结果代码将更脆弱,参数数量的变化等.
编辑
但是,如果要构建一个结果表,这可能会更快一些,因为您一劳永逸地执行此操作,在这种情况下,您确实希望保留所有已记忆的定义.所以,这是代码:
ClearAll[g, h];
g[0, k_, _] := 0;
g[t_, 0, {a_, b_, c_, d_, e_, f_}] := e;
g[t_, k_, {a_, b_, c_, d_, e_, f_}] :=
g[t, k, {a, b, c, d, e, f}] =
g[t - 1, k, {a, b, c, d, e, f}]*a + h[t - 1, k - 1, {a, b, c, d, e, f}]*b;
h[0, k_, _] := 0;
h[t_, 0, {a_, b_, c_, d_, e_, f_}] := f;
h[t_, k_, {a_, b_, c_, d_, e_, f_}] :=
h[t, k, {a, b, c, d, e, f}] =
h[t - 1, k, {a, b, c, d, e, f}]*c + g[t - 1, k - 1, {a, b, c, d, e, f}]*d;
Run Code Online (Sandbox Code Playgroud)
你调用它,显式传递参数:
In[1317]:= g[20,10,{a,b,c,d,e,f}]//Timing
Out[1317]= {0.,253809.}
Run Code Online (Sandbox Code Playgroud)
(我使用的是原始参数).您可以在此方法中检查已记住的定义是否保留在全局规则库中.下次调用具有完全相同参数的函数时,它将获取memoized定义而不是重新计算.除了上面概述的这种方法的问题,你还应该注意内存使用情况,因为没有任何东西被清除.
使用辅助符号进行记忆
在这个问题表现出来的记忆化技术可以被修改,使得的定义g和h不需要重新建立每当高速缓存需要清除.我们的想法是将备忘值存储在辅助符号上,而不是直接存储在g和h:
g[0,k_] = 0;
g[t_,0] = e;
g[t_,k_] := memo[g, t, k] /. _memo :> (memo[g, t, k] = g[t-1,k]*a+h[t-1,k-1]*b)
h[0,k_] = 0;
h[t_,0] = f;
h[t_,k_] := memo[h, t, k] /. _memo :> (memo[h, t, k] = h[t-1,k]*c+g[t-1,k-1]*d)
Run Code Online (Sandbox Code Playgroud)
这些定义与原始的memoized版本基本相同,g并且h除了memo引入了新符号之外.有了这些定义,就可以简单地清除缓存Clear@memo- 无需重新定义g和h重新生成.更妙的是,高速缓存可以通过将被本地化memo的Block,因此:
Block[{memo, a = 7, b = 9, c = 13, d = 0.002, e = 2, f = 1}
, Table[g[t, k], {t, 0, 100}, {k, 0, 100}]
]
Run Code Online (Sandbox Code Playgroud)
退出块时将丢弃缓存.
使用建议进行记忆
原始和修订的记忆技术需要在功能g和内部进行有创改变h.有时,事后引入memoization很方便.一种方法是使用建议技术- 一种类似于OO编程中子类化的函数式编程.一个函数建议具体的实现定期出现在StackOverflow上的网页.然而,该技术也是侵入性的.让我们考虑一种替代技术,即在更改全局定义g和h不更改全局定义的情况下添加建议.
诀窍是暂时重新定义g并h在一个Block.重新定义将首先检查缓存中的结果,如果失败,则从块外部调用原始定义.让我们回到最初的定义,g并且h幸福地没有意识到记忆:
g[0,k_]:=0
g[t_,0]:=e
g[t_,k_]:=g[t-1,k]*a+h[t-1,k-1]*b
h[0,k_]:=0
h[t_,0]:=f
h[t_,k_]:=h[t-1,k]*c+g[t-1,k-1]*d
Run Code Online (Sandbox Code Playgroud)
此技术的基本架构如下所示:
Module[{gg, hh}
, copyDownValues[g, gg]
; copyDownValues[h, hh]
; Block[{g, h}
, m:g[a___] := m = gg[a]
; m:h[a___] := m = hh[a]
; (* ... do something with g and h ... *)
]
]
Run Code Online (Sandbox Code Playgroud)
临时符号gg和hh引入举行的原始定义g和h.然后,g并h在本地反弹到新的缓存定义,根据需要委托原始定义.这是定义copyDownValues:
ClearAll@copyDownValues
copyDownValues[from_Symbol, to_Symbol] :=
DownValues[to] =
Replace[
DownValues[from]
, (Verbatim[HoldPattern][from[a___]] :> d_) :> (HoldPattern[to[a]] :> d)
, {1}
]
Run Code Online (Sandbox Code Playgroud)
为了使这篇文章保持简短(呃),这个"复制"功能只关注低值.一般建议工具还需要考虑上升值,子值,符号属性等.
如果单调乏味,这种一般模式很容易实现自动化.以下宏函数memoize执行此操作,几乎没有注释:
ClearAll@memoize
SetAttributes[memoize, HoldRest]
memoize[symbols:{_Symbol..}, body_] :=
Module[{pairs, copy, define, cdv, sd, s, m, a}
, pairs = Rule[#, Unique[#, Temporary]]& /@ symbols
; copy = pairs /. (f_ -> t_) :> cdv[f, t]
; define = pairs /. (f_ -> t_) :> (m: f[a___]) ~sd~ (m ~s~ t[a])
; With[{ temps = pairs[[All, 2]]
, setup1 = Sequence @@ copy
, setup2 = Sequence @@ define }
, Hold[Module[temps, setup1; Block[symbols, setup2; body]]] /.
{ cdv -> copyDownValues, s -> Set, sd -> SetDelayed }
] // ReleaseHold
]
Run Code Online (Sandbox Code Playgroud)
经过多次努力之后,我们现在可以在非缓存版本g和外部强制进行memoization h:
memoize[{g, h}
, Block[{a = 7, b = 9, c = 13, d = .002, e = 2, f = 1}
, Table[g[t, k], {t, 0, 100}, {k, 0, 100}]
]
]
Run Code Online (Sandbox Code Playgroud)
总而言之,我们现在可以创建一个响应Manipulate块:
Manipulate[
memoize[{g, h}
, Table[g[t, k], {t, 0, tMax}, {k, 0, kMax}] //
ListPlot3D[#, InterpolationOrder -> 0, PlotRange -> All, Mesh -> None] &
]
, {{tMax, 10}, 5, 25}
, {{kMax, 10}, 5, 25}
, {{a, 7}, 0, 20}
, {{b, 9}, 0, 20}
, {{c, 13}, 0, 20}
, {{d, 0.002}, 0, 20}
, {{e, 2}, 0, 20}
, {{f, 1}, 0, 20}
, LocalizeVariables -> False
, TrackedSymbols -> All
]
Run Code Online (Sandbox Code Playgroud)

在LocalizeVariables和TrackedSymbols选项是依赖关系的假象g和h对全局符号a通过f.