2012-09-18

关于 Mathematica 的 Manipulate 函数无法释放CPU的解决办法

最近遇到一个小问题,就是在使用 Manipulate 函数时,一旦涉及到迭代、循环语句,代码就会反复执行(右侧方框加黑——而换用 Animate 函数则没有这种情况),只能手动中止,例如:
   
    Manipulate[
     date = Table[0, {num}];
     For[i = 1, i <= num, i++, date[[i]] = Sin[i]];
     ListPlot[{date}]
     , {{num, 2, "iteration num"}, 1, 20, 1}]

再来一个双层循环的例子:

    Manipulate[           
      buf = Table[0, {10}];
      Mw = Table[0, {10}];
      For[j = 1, j <= 10, j++,
       For[i = 1, i <= 10, i++,
        w = RandomVariate[NormalDistribution[0, var], 10];
        buf[[i]] = Mean[w];];
       Mw[[j]] = Mean@Cos[buf];];     
      ListLinePlot[Mw, PlotRange -> Full, DataRange -> All, Mesh -> Full,
       GridLines -> Automatic,
       GridLinesStyle -> Directive[Orange, Dashed], Frame -> True]     
     ,{{var, 3, "Var"}, 1, 5, 0.5}]
 
这两个例子还不是很严重,但是结构一旦再复杂一点, 就不太好办了,有时甚至会将前端卡死……

去网上搜索、发问,收到了一些具有启发性的回答。有人说到 Manipulate 是实时计算的,而 Animate 是统一计算所有值后再呈现结果,二者不同。

这样一来, 一旦 Manipulate 涉及到迭代、循环一类的语句,就会反复调用,无法退出,这也是逻辑上讲得通的,至少不使用迭代语句,Manipulate 就不会发生这种情况。

如果这是问题的原因,那么如何解决呢?

首先是修改算法,对于简单的问题,将循环语句转化为非循环语句或许行得通,可是对于很大一类迭代算法来说,这不是件轻易可以办到的事情,还是要从工具本身去需找答案。

于是我再去查看 Help文档,终于发现 Mathematica 的开发者早已考虑到这个问题,而且我遇到的问题也不能算作缺陷,只是一种非预设的情况罢了。

以下摘抄自「高级操作(Manipulate)功能」:

    每当步长被从零移开时,内容区会就会连续地更新,一个 CPU 监视器会指示 Mathematica 正在使用 CPU 时间. 你让这种情况持续多久,它就会持续多久.

    然而在某些情况下,持续的再计算是没有意义和不受欢迎的.
   
        例1:   
        Manipulate[
         temp = n;
         temp = temp^3;
         Graphics[{Thickness[0.01], Line[{{0, 0}, {n, temp}}]},
          PlotRange -> 1],
         {n, -1, 1}]
       
        例2:
        Manipulate[
         f[x_] := x^3;
         Graphics[{Thickness[0.01], Line[{{0, 0}, {n, f[n]}}]},
          PlotRange -> 1],
         {n, -1, 1}]
   
    在这两个情况下,这个问题都可以通过把引起问题的那些变量在一个 Module 里设成局部变量来解决. (这无论如何这都是一个好的编程习惯,远不止是为了避免无意义的更新)
   
        例1:
        Manipulate[Module[{temp},
          temp = n;
          temp = temp^3;
          Graphics[{Thickness[0.01], Line[{{0, 0}, {n, temp}}]},
           PlotRange -> 1]],
         {n, -1, 1}]
        
        例2:
        Manipulate[Module[{f},
          f[x_] := x^3;
          Graphics[{Thickness[0.01], Line[{{0, 0}, {n, f[n]}}]},
           PlotRange -> 1]],
         {n, -1, 1}]
    
    不管你对局部 Module 变量做什么都不会造成重新触发,因为这是 Module 定义的一部分,即一次调用后的变量值不会存留到下一次(所以下一轮的结果不会仅仅因为当前一轮运行中对局部变量所做的任何动作而有任何不同).
   
    另一个解决的办法是使用 Manipulate 的 TrackedSymbols (跟踪的符号)选项来控制哪一个变量能被允许引起更新行为. 默认的值, Full (全部),意味着所有在第一个自变量中明确出现(词汇的)的符号都会被跟踪. (这意味着,除了其它事情之外,在你使用的 Manipulate 例子中函数定义里的临时变量和其它如此的问题将不会引起无限重复计算问题,这是因为它们不在第一个自变量中明显地出现,而只是通过你调用的函数间接地出现.)

    来看第二个例子,如果由于某种原因你不想要 f 成为一个局部的 Module 变量,并且你不能把它的定义移到 Manipulate 之外(在更复杂的例子中,这两种情况有些时候都是很可能会出现的),你能用 TrackedSymbols 来取消由 f 触发的更新:

        Manipulate[
         f[x_] := x^3;
         Graphics[{Thickness[0.01], Line[{{0, 0}, {n, f[n]}}]},
          PlotRange -> 1],
         {n, -1, 1}, TrackedSymbols :> {n}]
    
    这个例子只在移动滑块从而改变 n 的数值时才更新内容区域.

   

以上内容完全解决了我的疑问,我将代码稍作改动,问题搞定,如下:

    Manipulate[Module[{i},
      date = Table[0, {num}];
      For[i = 1, i <= num, i++, date[[i]] = Sin[i]];
      ListPlot[{date}]]
     , {{num, 4, "iteration num"}, 1, 20, 1}]
    
    Manipulate[           
     buf = Table[0, {10}];
     Mw = Table[0, {10}];
     For[j = 1, j <= 10, j++,
      For[i = 1, i <= 10, i++,
       w = RandomVariate[NormalDistribution[0, var], 10];
       buf[[i]] = Mean[w];];
      Mw[[j]] = Mean@Cos[buf];];
     ListLinePlot[Mw, PlotRange -> Full, DataRange -> All, Mesh -> Full,
      GridLines -> Automatic, GridLinesStyle -> Directive[Orange, Dashed],
       Frame -> True]
     , {{var, 3, "Var"}, 1, 5, 0.5}
     , TrackedSymbols :> {var}]

正如高手所说的,工具本身的 Help 是最棒的专家系统。

此外,文档这一节的末尾提到这么一段话:「具体在什么时候一个给定的动态表达式会被更新这个话题是很复杂的,这在 "动态简介" 和 "高级动态功能" 中被提到了. 在阅读那些文件时,始终注意 Manipulate 只是在 Dynamic 中把它的第一个自变量包围起来并且把它的 TrackedSymbols 选项的数值传送个在那里面的 Refresh. 所有与更新有关的事情都是由那个 Dynamic 和 Refresh 处理的.」

于是我又去看了与 Dynamic 和 Refresh 有关的内容,这里摘抄其中一条与上文有些关联的例子:

    一个可能会使人伤脑筋的情况是 RandomReal. 每一次你计算 RandomReal[](随机实数 []),你都得到一个不同的答案,你也许就会认为 Dynamic[RandomReal[]](动态[随机实数 []])因此应该不停地尽快地更新自己. 但是在一般情况下这不会很有用,而且实际上会对一些在内部使用随机性的算法有负面的后果(例如,一个在 Dynamic 里面的 Monte Carlo 积分很可能不应该不停地更新,因为事实上它会每次给出一个稍微有些不同的答案).

        Dynamic[Refresh[RandomVariate@NormalDistribution[0, 1], UpdateInterval-> 1]]

这次关于 Mathematica 动态功能的探索就到此为止了,最大的获益就是:以后再遇到什么问题,首先去搜强大的 Help 文档。

没有评论:

发表评论