使用With对比绘图使用Block(Mathematica)绘图

时间:2011-06-04 11:23:17

标签: wolfram-mathematica

我想描述一下我Plot使用With来保持已定义参数'local'的问题。我不一定要求修复:我遇到的问题是理解。

有时我会使用如下构造来获取Plot:

方法1

plot1 = With[{vmax = 10, km = 10}, 
  Plot[Evaluate@((vmax x)/(km + x)), {x, 0, 100}, 
   AxesOrigin -> {0, 0}]]

我喜欢这种方法,即使对于非 Mathematica 用户来说也是相当清楚的。

当要绘制的方程式变得更加复杂时,我喜欢在绘图外部定义它们(使用SetDelayed)。例如:

f[x_] := (vmax x)/(km + x)

但是,以下不起作用

方法2

plot2 = With[{vmax = 10, km = 10}, 
  Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]]

我总是天真地认为它应该。但是,基于帮助声明

  

Plot将变量x视为本地,   有效地使用Block

我使用了各种变通办法,大多数情况如下

方法3

plot3 = Plot[With[{vmax = 10, km = 10}, Evaluate@f[x]], {x, 0, 100}, 
  AxesOrigin -> {0, 0}]

这个看起来很尴尬,通常需要进一步解释 Mathematica 用户。

绘制输出

enter image description here

然而,最近我偶然发现在方法2中用Block代替With与预期完全一样。

例如,我可以做类似下面的事情(这对我来说似乎是一种非常通用的方法):

plot4 = Block[{vmax = {10, 10, 10}, km = { 10, 100, 1000}}, 
  Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}, 
   PlotStyle -> {Red, Green, Blue}]]

给予

enter image description here

我的问题如下。方法1和2中With行为差异的解释是什么?我应该预期方法2不起作用吗?此外,对方法2中BlockWith的行为差异有何解释?我是否应该能够预测Block会起作用?

有趣的是,那些比我更有经验的人向我建议了许多变通办法,但没有人建议使用Block

最后,我需要保持vmaxkm本地。(它们已在别处以代数方式定义)

2 个答案:

答案 0 :(得分:62)

你的问题不在于Plot,而在于范围界定结构是如何运作的。这里的主要困惑是由于词汇和动态范围之间的差异。而罪魁祸首就是这个定义:

f[x_] := (vmax x)/(km + x)

它的问题在于它使f隐含地依赖于全局符号(变量)vmaxkm。我非常反对这种结构,因为它们导致了无限的混乱。现在,可以通过以下示例说明会发生什么:

In[55]:= With[{vmax =1, km = 2},f[x]]

Out[55]= (vmax x)/(km+x)

要理解为什么会发生这种情况,我们必须了解 lexical 范围的含义。我们知道With具有HoldAll属性。它的工作方式是它看起来就像字面上一样,并用声明列表中的值替换正文中字面的变量。这发生在变量绑定阶段,只有这样才能让身体进行评估。由此可见,以下内容将起作用:

In[56]:= With[{vmax =1, km = 2},Evaluate[f[x]]]

Out[56]= x/(2+x) 

这很有效,因为Evaluate会覆盖HoldAll With属性的“部分”,迫使身体在其他任何事情之前进行评估(变量绑定和后续身体评估)。因此,完全等同于使用上面的With[{vmax = 1, km = 2}, (vmax x)/(km + x)],正如您在Trace中看到的那样。这个难题的下一部分是为什么

With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100}, AxesOrigin -> {0, 0}]]

不起作用。这是因为这次我们首先评估身体。 Evaluate的存在仅影响f[x]内的Plot,而不影响PlotWith本身的评估。

说明了这一点
In[59]:= With[{vmax = 10, km = 10}, q[Evaluate@f[x]]]

Out[59]= q[(vmax x)/(km + x)]

此外,我们不希望首先评估Plot,因为此时不会定义vmaxkm的值。但是,With看到的所有内容都是f[x],因为参数vmaxkm不是字面上存在于那里(词汇作用域,请记住),不会进行替换。我们应该在这里使用Block,事情会有效,因为Block使用动态范围,这意味着它会及时重新定义值(如果你愿意,可以重新定义执行堆栈的一部分),而不是就地。因此,使用Block[{a =1, b =2}, ff[x]] ff隐式取决于ab({粗略)等同于a=1;b=2;ff[x](差异为a和在b范围保留后,Block恢复其全局值。所以,

In[60]:= Block[{vmax = 10, km = 10}, q[Evaluate@f[x]]]

Out[60]= q[(10 x)/(10 + x)]

要使With版本正常工作,您必须为f[x](r.h.s)注入表达式,例如:

In[63]:= Unevaluated[With[{vmax = 10, km = 10}, q[f[x]]]] /. DownValues[f]

Out[63]= q[(10 x)/(10 + x)]

请注意,这不起作用:

In[62]:= With[{fx = f[x]}, With[{vmax = 10, km = 10},  q[fx]]]

Out[62]= q[(vmax x)/(km + x)]

但是这里的原因是非常微妙的:当外部With在内部之前进行评估时,它会发现变量名称冲突并重命名其变量。规则更具破坏性,它们不尊重内部范围构造。

编辑

如果有人坚持使用嵌套With - s,那么人们就可以愚弄With的名称冲突解决机制并让它发挥作用:

In[69]:= With[{fx = f[x]}, With @@ Hold[{vmax = 10, km = 10}, q[fx]]]

Out[69]= q[(10 x)/(10 + x)]

由于外部With无法再检测到内部With的存在(使用Apply[With,Hold[...]]使内部With有效地动态生成),因此它不会进行任何重命名,然后它的工作原理。当你不想重命名时,这是愚弄词法作用域名称解析机制的一般技巧,尽管使用它的必要性通常表明设计不好。

结束编辑

但我离题了。总而言之,让你的第二种方法工作非常困难,并且需要非常奇怪的结构,比如

Unevaluated[ With[{vmax = 10, km = 10}, Plot[Evaluate@f[x], {x, 0, 100},
     AxesOrigin -> {0, 0}]]] /.  DownValues[f]

With[{fx = f[x]}, 
   With @@ Hold[{vmax = 10, km = 10}, 
       Plot[Evaluate@fx, {x, 0, 100}, AxesOrigin -> {0, 0}]]]

再一次:所有这一切都是因为With必须在代码中明确地“看到”变量才能进行替换。相比之下,Block不需要它,它会根据修改后的全局值在评估时动态替换值,就好像您进行了分配一样,这就是它工作的原因。

现在,真正的罪魁祸首是你对f的定义。如果您使用显式参数传递来定义f,则可以避免所有这些麻烦:

ff[x_, vmax_, km_] := (vmax x)/(km + x)

现在,这开箱即用:

With[{vmax = 10, km = 10}, 
   Plot[Evaluate@ff[x, vmax, km], {x, 0, 100}, AxesOrigin -> {0, 0}]]

因为参数显式出现在函数调用签名中,因此With可见。

总结一下:你观察到的是词汇和动态范围之间相互作用的结果。词法范围构造必须在变量绑定阶段(评估之前)在代码中明确地“看到”它们的变量,否则它们将无效。动态范围有效地修改了符号的,从这个意义上说要求不高(你付出的代价是使用大量动态范围的代码更难以理解,因为它混合了状态和行为)。出现问题的主要原因是函数定义会对全局符号(不在函数的形式参数列表中)进行隐式依赖。最好避免这种结构。仍然有可能使事情有效,但这要复杂得多(如上所述),至少在手头的情况下,没有充分的理由。

答案 1 :(得分:1)

只有两条评论:

  • 使用Block,您不需要使用Evaluate。也就是说,Block [{vmax = 10,km = 2},Plot [f [x],{x,0,100}]将起作用。

  • 另一种方法是定义替换规则: rule = {vmax - > 10,km - > 10};图[f [x] /。规则,{x,0,100}] 优点是您可以在其他语句中重复使用该规则。