21

SaveDefinitions是一个不错的选择Manipulate。它会导致Manipulate在 Manipulate 面板中存储用于创建它的任何定义。以这种方式制作的 Manipulate 可以复制到一个空的笔记本中,并且仍然可以独立工作。此外,您的包含许多此类 Manipulates 的工作笔记本也不会在打开时变成一堆粉红色的盒子,并在其下方打印错误消息。伟大的!

然而,所有这些善良都有它的阴暗面,如果你不知道的话,它会咬你一口。我已经在我已经工作了几天的笔记本中找到了这个,但我向您展示了一个逐步重现问题的玩具示例场景。

在这种情况下,您想创建一个Manipulate显示漂亮波浪函数的图,因此您定义了这个(请制作这样的窗口大小,这很重要):

在此处输入图像描述

这个定义很好,所以我们下次保留它并使其成为一个初始化单元。接下来我们添加Manipulate, 并执行它。

f[x_] := x^2

Manipulate[
 Plot[n f[x], {x, -3, 3}],
 {n, 1, 4},
 SaveDefinitions -> True
 ]

一切都很好,Manipulate 真的很闪耀,这是美好的一天。

在此处输入图像描述

只是作为你偏执的自我,你检查定义是否正确:

在此处输入图像描述

是的,一切仍在检查中。美好的。但是现在你突然想到一个更好的波形函数应该是一个正弦函数,所以你改变了定义,执行,并且偏执,检查:

在此处输入图像描述

一切都还好。你已经准备好从一天的辛勤工作中拯救你的工作并退出。[退出内核]

明天。你重新开始你的工作。您评估笔记本中的初始化单元。清晰度还不错?查看。

在此处输入图像描述

现在,您向下滚动到您的 Manipulate 框(由于有了 ,无需重新执行SaveDefinitions),稍微玩一下滑块。并向上滚动。

在此处输入图像描述

作为偏执狂的你,你再次检查 f 的定义:

在此处输入图像描述

瞧,有人在你背后改变了定义!Information根据 In[] 数字(In[1]: def of f, In[2]first ?, In[3]second ?) ,在您的第一次和第二次(?)检查之间没有执行任何操作。

发生了什么?嗯,这是Manipulate当然的。AFullForm揭示了它的内部结构:

Manipulate[Plot[n*f[x],{x, -3, 3}],{{n, 2.44}, 1, 4},Initialization:>{f[x_] := x^2}]

你有罪魁祸首。Manipulate盒子的初始化部分再次定义了 f,但它是旧版本,因为我们在修改它的定义后没有重新评估它。一旦操纵框出现在屏幕上,就会对其进行评估,并且您会恢复原来的定义。全球!

当然,在这个玩具示例中,很明显正在发生一些奇怪的事情。就我而言,我在一个更大的笔记本中有一个更大的模块,经过一些调试,我在其中更改了一小部分。它似乎起作用了,但是第二天,在我之前一直困扰我的同一个错误再次出现。我花了几个小时才意识到我用来从各个方面研究手头问题的几个 Manipulates 之一就是这样做的。

显然,我很想说,这是不受欢迎的行为。现在,对于一个强制性的问题:除了每次更改可能被他们使用的定义时重新执行笔记本中的 every 之外,我们还能做些什么来防止这种背后行为的发生?ManipulateManipulate

4

2 回答 2

10

这是一个尝试。这个想法是在您的操纵代码中识别符号DownValues或其他符号...Values,并使用唯一的变量/符号代替它们自动重命名它们。在克隆符号功能的帮助下,这里的想法可以相当优雅地执行,我发现它有时很有用。下面的函数clone将克隆给定符号,生成具有相同全局定义的符号:

Clear[GlobalProperties];
GlobalProperties[] :=
  {OwnValues, DownValues, SubValues, UpValues, NValues, FormatValues, 
      Options, DefaultValues, Attributes};


Clear[unique];
unique[sym_] :=
 ToExpression[
    ToString[Unique[sym]] <> 
       StringReplace[StringJoin[ToString /@ Date[]], "." :> ""]];


Attributes[clone] = {HoldAll};
clone[s_Symbol, new_Symbol: Null] :=
  With[{clone = If[new === Null, unique[Unevaluated[s]], ClearAll[new]; new],
        sopts = Options[Unevaluated[s]]},
     With[{setProp = (#[clone] = (#[s] /. HoldPattern[s] :> clone)) &},
        Map[setProp, DeleteCases[GlobalProperties[], Options]];
        If[sopts =!= {}, Options[clone] = (sopts /. HoldPattern[s] :> clone)];
        HoldPattern[s] :> clone]]

关于如何实现函数本身有几种选择。一种是用另一个名称引入函数,采用与 相同的参数Manipulate,比如myManipulate。我将使用另一个:Manipulate通过UpValues一些自定义包装器软重载,我将介绍它。我会打电话CloneSymbols的。这是代码:

ClearAll[CloneSymbols];
CloneSymbols /: 
Manipulate[args___,CloneSymbols[sd:(SaveDefinitions->True)],after:OptionsPattern[]]:=
   Unevaluated[Manipulate[args, sd, after]] /.
     Cases[
       Hold[args],
       s_Symbol /; Flatten[{DownValues[s], SubValues[s], UpValues[s]}] =!= {} :> 
          clone[s],
       Infinity, Heads -> True];

这是一个使用示例:

f[x_] := Sin[x];
g[x_] := x^2;

请注意,要使用新功能,必须将SaveDefinitions->True选项包装在CloneSymbols包装器中:

Manipulate[Plot[ f[n g[x]], {x, -3, 3}], {n, 1, 4}, 
          CloneSymbols[SaveDefinitions -> True]]

操纵

这不会影响内部代码中原始符号的定义Manipulate,因为它是它们的克隆,其定义已被保存并现在用于初始化。我们可以查看一下FullFormManipulate确认:

Manipulate[Plot[f$37782011751740542578125[Times[n,g$37792011751740542587890[x]]],
   List[x,-3,3]],List[List[n,1.9849999999999999`],1,4],RuleDelayed[Initialization,
     List[SetDelayed[f$37782011751740542578125[Pattern[x,Blank[]]],Sin[x]],
       SetDelayed[g$37792011751740542587890[Pattern[x,Blank[]]],Power[x,2]]]]]

特别是,您可以将函数的定义更改为

f[x_]:=Cos[x];
g[x_]:=x;

然后移动上面制作的滑块Manipulate,然后查看函数定义

?f
Global`f
f[x_]:=Cos[x]

?g
Global`g
g[x_]:=x

Manipulate合理地独立于任何东西,并且可以安全地复制和粘贴。这里发生的情况如下:我们首先找到所有带有 non-trivial DownValues, SubValuesor的符号UpValues(也可以添加OwnValues),然后使用Casesclone动态创建它们的克隆。然后我们用它们在里面的克隆来替换所有克隆的符号Manipulate,然后让我们Manipulate保存克隆的定义。通过这种方式,我们对所涉及的功能进行了“快照”,但不会以任何方式影响原始功能。

该功能已解决了克隆(符号)的唯一性unique。但是请注意,虽然Manipulate以这种方式获得的 -s 不会威胁到原始函数定义,但它们通常仍将依赖于它们,因此不能认为它们完全独立于任何东西。必须沿着依赖关系树向下移动并克隆那里的所有符号,然后重建它们的相互依赖关系,以在 Manipulate 中构建一个完全独立的“快照”。这是可行的,但更复杂。

编辑

根据@Sjoerd 的请求,当我们确实希望我们的Manipulate-s 更新函数的更改但不希望它们主动干扰和更改任何全局定义时,我添加了代码。我建议一种“指针”技术的变体:我们将再次用新符号替换函数名称,但是,我们将使用Manipulate'Initialization选项简单地将这些符号“指针”到我们的函数之后,而不是在我们的函数之后克隆这些新符号。函数,例如Initialization:>{new1:=f,new2:=g}. 显然,重新评估此类初始化代码不会损害for的定义g,同时我们的Manipulate-s 将响应这些定义的变化。

第一个想法是我们可以简单地用新符号替换函数名,然后让Manipulate初始化自动完成剩下的工作。不幸的是,在那个过程中,它会遍历依赖树,因此,我们函数的定义也将被包括在内——这是我们试图避免的。因此,相反,我们将显式构造该Initialize选项。这是代码:

ClearAll[SavePointers];
SavePointers /: 
Manipulate[args___,SavePointers[sd :(SaveDefinitions->True)],
after:OptionsPattern[]] :=
Module[{init},
  With[{ptrrules = 
    Cases[Hold[args], 
      s_Symbol /; Flatten[{DownValues[s], SubValues[s], UpValues[s]}] =!= {} :> 
         With[{pointer = unique[Unevaluated[s]]},
            pointer := s;
            HoldPattern[s] :> pointer], 
            Infinity, Heads -> True]},
           Hold[ptrrules] /. 
              (Verbatim[HoldPattern][lhs_] :> rhs_ ) :> (rhs := lhs) /. 
               Hold[defs_] :> 
                 ReleaseHold[
                      Hold[Manipulate[args, Initialization :> init, after]] /. 
                            ptrrules /. init :> defs]]]

与之前的定义相同:

ClearAll[f, g];
f[x_] := Sin[x];
g[x_] := x^2;

这是一个FullForm生产的Manipulate

In[454]:= 
FullForm[Manipulate[Plot[f[n g[x]],{x,-3,3}],{n,1,4},
     SavePointers[SaveDefinitions->True]]]

Out[454]//FullForm=   
Manipulate[Plot[f$3653201175165770507872[Times[n,g$3654201175165770608016[x]]],
List[x,-3,3]],List[n,1,4],RuleDelayed[Initialization,
List[SetDelayed[f$3653201175165770507872,f],SetDelayed[g$3654201175165770608016,g]]]]

新生成的符号充当我们函数的“指针”。用这种方法构造的Manipulate-s 将响应我们函数中的更新,同时对主要函数的定义无害。付出的代价是它们不是独立的,如果主要功能未定义,将无法正确显示。因此,可以使用CloneSymbolswrapper 或SavePointers,这取决于需要什么。

于 2011-07-05T09:08:52.163 回答
6

答案是使用初始化单元作为初始化Manipulate

Manipulate[
 Plot[n f[x], {x, -3, 3}], {n, 1, 4}, 
 Initialization :> FrontEndTokenExecute["EvaluateInitialization"]]

您还可以使用DynamicModule

DynamicModule[{f},
 f[x_] := x^2;
 Manipulate[Plot[n f[x], {x, -3, 3}], {n, 1, 4}]]

SaveDefinitions -> True在这种情况下您不需要。

编辑

回应 Sjoerd 的评论。使用以下简单技术,您无需在任何地方复制定义并在更改定义时更新所有副本(但您仍需要重新评估代码以获取更新Manipulate):

DynamicModule[{f}, f[x_] := x^2;
  list = Manipulate[Plot[n^# f[x], {x, -3, 3}], {n, 2, 4}] & /@ Range[3]];
list // Row
于 2011-07-05T09:00:59.920 回答