SaveDefinitions被认为是危险的

Sjo*_*ies 20 wolfram-mathematica mathematica-frontend

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

然而,所有这些善良都有它的黑暗面,如果你不知道它,它会让你真的很难受.我已经在我已经工作了几天的笔记本中使用了这个,但是我向您展示了一个重新创建问题的分步玩具示例场景.

在这种情况下,你想创建一个Manipulate漂亮的波浪函数的图,所以你定义这个(请创建一个像这样的窗口大小,这很重要):

在此输入图像描述

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

f[x_] := x^2

Manipulate[
 Plot[n f[x], {x, -3, 3}],
 {n, 1, 4},
 SaveDefinitions -> True
 ]
Run Code Online (Sandbox Code Playgroud)

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

在此输入图像描述

只是做你的偏执自我,检查定义是否正常:

在此输入图像描述

是的,一切仍然检查出来.精细.但现在你想到一个更好的波浪函数是正弦,所以你改变定义,执行和偏执,检查:

在此输入图像描述

一切都还好.你已经准备好从一天的努力中挽救你的工作并退出.[退出内核]

明天.你重新开始工作了.您评估笔记本中的初始化单元格.定义还好吗?校验.

在此输入图像描述

现在,您向下滚动到Manipulate框(不需要重新执行SaveDefinitions),使用滑块稍微玩一下.并向上滚动.

在此输入图像描述

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

在此输入图像描述

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

发生了什么?嗯,这Manipulate当然是.A FullForm揭示其内部结构:

Manipulate[Plot[n*f[x],{x, -3, 3}],{{n, 2.44}, 1, 4},Initialization:>{f[x_] := x^2}]
Run Code Online (Sandbox Code Playgroud)

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

当然,在这个玩具示例中,很明显发生了一些奇怪的事情.在我的情况下,我在一个更大的笔记本中有一个更大的模块,在经过一些调试后,我改变了一小部分.它似乎工作,但第二天,同样的错误,在再次击中之前曾经惹过我.我花了几个小时才意识到我用来研究各方面问题的几个操作中的一个是这样做的.

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

Leo*_*rin 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]]
Run Code Online (Sandbox Code Playgroud)

有几种方法可以实现函数本身.一种是使用其他名称引入该功能,以相同的参数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];
Run Code Online (Sandbox Code Playgroud)

这是一个使用示例:

f[x_] := Sin[x];
g[x_] := x^2;
Run Code Online (Sandbox Code Playgroud)

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

Manipulate[Plot[ f[n g[x]], {x, -3, 3}], {n, 1, 4}, 
          CloneSymbols[SaveDefinitions -> True]]
Run Code Online (Sandbox Code Playgroud)

操纵

这不会影响内部代码中原始符号的定义Manipulate,因为它们的克隆定义已保存并现在用于初始化.我们可以看看FullForm为此Manipulate确认:

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]]]]]
Run Code Online (Sandbox Code Playgroud)

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

f[x_]:=Cos[x];
g[x_]:=x;
Run Code Online (Sandbox Code Playgroud)

然后移动Manipulate上面生成的滑块,然后检查函数定义

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

?g
Global`g
g[x_]:=x
Run Code Online (Sandbox Code Playgroud)

Manipulate完全独立于任何东西,可以安全地复制和粘贴.这里发生的是:我们首先找到所有非平凡的符号DownValues,SubValues或者UpValues(也可以添加一个OwnValues),并使用Cases和动态clone创建它们的克隆.然后我们用词法替换所有克隆的符号Manipulate,然后Manipulate保存克隆的定义.通过这种方式,我们可以对所涉及的功能进行"快照",但不会以任何方式影响原始功能.

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

编辑

根据@Sjoerd的请求,当我们希望我们的Manipulate-s更新到函数的更改时,我会为一个案例添加代码,但不希望它们主动干涉和更改任何全局定义.我建议使用"指针"技术的变体:我们将再次用新符号替换函数名称,但是,不是在我们的函数之后克隆那些新符号,我们将使用Manipulate's Initialization选项简单地使这些符号"指向"我们的功能,例如像Initialization:>{new1:=f,new2:=g}.显然,重新评估这样的初始化代码不会损害定义,f或者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]]]
Run Code Online (Sandbox Code Playgroud)

使用与以前相同的定义:

ClearAll[f, g];
f[x_] := Sin[x];
g[x_] := x^2;
Run Code Online (Sandbox Code Playgroud)

这是一个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]]]]
Run Code Online (Sandbox Code Playgroud)

新生成的符号用作我们函数的"指针".Manipulate使用这种方法构建的-s将对我们的函数中的更新做出响应,同时对主要函数的定义无害.付出的代价是它们不是自包含的,如果主要功能未定义则无法正确显示.因此,可以使用CloneSymbols包装器,也可以SavePointers根据需要使用.


Ale*_*kov 6

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

Manipulate[
 Plot[n f[x], {x, -3, 3}], {n, 1, 4}, 
 Initialization :> FrontEndTokenExecute["EvaluateInitialization"]]
Run Code Online (Sandbox Code Playgroud)

您还可以使用DynamicModule:

DynamicModule[{f},
 f[x_] := x^2;
 Manipulate[Plot[n f[x], {x, -3, 3}], {n, 1, 4}]]
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)