【问题标题】:SaveDefinitions considered dangerousSaveDefinitions 被认为是危险的
【发布时间】:2011-07-05 08:13:09
【问题描述】:

SaveDefinitionsManipulate 的一个不错的选择。它使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的定义:

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

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

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

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

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

显然,我很想说,这是不受欢迎的行为。现在,对于强制性问题:除了每次更改可能是他们用过吗?

【问题讨论】:

  • 我认为这里没有真正的问题。您展示的是两种初始化方法的冲突。如果您在Manipulate 中使用SaveDefinitions -> True 选项,则您不应该将初始化单元格用于同一任务!这类似于在同一个 Notebook 中为同一个符号创建两个具有不同定义的初始化单元......
  • @alexey 我不同意。两种初始化都有不同的目的。显然,SaveDefinitions 并不是要全局引入定义。它实际保存的内容是隐藏的。对于具有全局影响的东西来说不是很好。
  • 在这个意义上我同意你的看法。这只是Manipulate的糟糕设计。
  • @Sjoerd,尽管您所描述的完全有道理(我同意这很糟糕),但我似乎无法在这里重现它,尽管按照您的步骤操作!还有其他人设法复制吗?
  • 我也遇到过很多次这种情况……

标签: wolfram-mathematica mathematica-frontend


【解决方案1】:

这是一个尝试。这个想法是用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。我将使用另一个:通过UpValues 软重载Manipulate 一些自定义包装器,我将介绍它。我会称之为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-&gt;True 选项包装在 CloneSymbols 包装器中:

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

这不会影响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]]]]]

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

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

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

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

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

这个Manipulate 相当独立于任何东西,可以安全地复制和粘贴。这里发生的情况如下:我们首先找到所有具有非平凡DownValuesSubValuesUpValues 的符号(也可以添加OwnValues),然后使用Casesclone 创建他们的克隆在飞行中。然后,我们用Manipulate 中的克隆替换所有克隆符号,然后让Manipulate 保存克隆的定义。通过这种方式,我们对所涉及的功能进行了“快照”,但不会以任何方式影响原始功能。

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

编辑

根据@Sjoerd 的请求,我添加了一个代码,用于我们确实希望我们的Manipulate-s 更新函数的更改,但不希望它们主动干扰和更改任何全局定义。我建议一种“指针”技术的变体:我们将再次用新符号替换函数名称,但是,我们将使用ManipulateInitialization 选项来简单地制作这些符号,而不是在我们的函数之后克隆这些新符号指向我们函数的符号“指针”,例如Initialization:&gt;{new1:=f,new2:=g}。显然,重新评估此类初始化代码不会损害fg 的定义,同时我们的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 将响应我们函数中的更新,同时对主要函数的定义无害。付出的代价是它们不是独立的,如果主要功能未定义,将无法正确显示。因此,可以根据需要使用CloneSymbols 包装器或SavePointers

【讨论】:

  • 谢谢,列昂尼德。我必须比现在更仔细地研究这个问题,但是关于您的评论,即 Module 可能会在新会话中导致名称冲突的一个简短评论:我知道使用 DynamicModule 应该可以防止这种情况(参见:reference.wolfram.com/mathematica/tutorial/…
  • @Sjoerd 确实,我想到了使用DynamicModule 的选项。但是DynamicModule创建的变量属于前端,不知何故我更喜欢使用内核变量进行函数定义的想法。我可能完全错了,我什至似乎记得一些不鼓励这种做法的 MathGroup 讨论。
  • @Sjoerd 我简化了clone 的实现,并实现了一个自定义的unique 函数,该函数应该解决唯一性问题,也适用于不同的Mathematica 会话。现在的代码应该更容易理解了。
  • 好的,这个我研究了一段时间。这不是最容易掌握的,我不确定我是否理解它的每一点(例如,clone 中的new 参数对我来说并不完全清楚),但它看起来会阻止 Manipulate 面板从做鬼鬼祟祟的事情。干得好! OTOH,Manipulate 的一些功能现在似乎丢失了。如果您在 Manipulate 环境之外更改函数,Manipulate 不会像以前那样更新以反映这一点。不知道这是好是坏。
  • @Sjoerd new 参数是为了以防万一用户想要提供一个特定的符号作为原始符号的克隆(我最初编写这个函数是为了需要这个选项的情况) .然后将其清除并使用,否则将创建唯一符号。关于失去有用的功能,我对此有一些想法,但需要详细说明。希望能尽快好起来。
【解决方案2】:

答案是使用初始化单元格作为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 -&gt; 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

【讨论】:

  • 好的。我知道第一个如何提供帮助。但是,它要求所讨论的定义位于初始化单元中。我在我的示例中使用了它,但这并不是获得这种背后效果的必要条件。第二个有帮助,但前提是没有任何其他 Manipulates 使用相同的功能。您将不得不在任何地方复制定义。如果您更改定义,则更新所有副本。
  • 我目前正在旅行。我会在两周内检查。
猜你喜欢
  • 2021-01-01
  • 2011-11-09
  • 2014-11-29
  • 2020-04-04
  • 2010-09-16
  • 2021-08-30
  • 2015-07-06
  • 2012-02-06
相关资源
最近更新 更多