未评估的[[i]]形式

时间:2012-01-05 11:24:03

标签: wolfram-mathematica operator-precedence

考虑以下简单的说明性示例

cf = Block[{a, x, degree = 3},
  With[{expr = Product[x - a[[i]], {i, degree}]},
   Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr]
   ]
  ]

这是在Compile语句主体中传输代码的可能方法之一。它产生Part :: partd错误,因为[[i]]在评估时不是列表。

简单的解决方案是忽略此消息或将其关闭。当然还有其他方法。例如,在编译之前,可以通过在Compile-body 中替换它来规避对[[i]]的评估

cf = ReleaseHold[Block[{a, x, degree = 3},
   With[{expr = Product[x - a[i], {i, degree}]},
    Hold[Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr]] /. 
     a[i_] :> a[[i]]]
   ]
  ]

如果编译后的函数有很多代码,那么Hold,Release和最后的替换会对我的漂亮代码的想法产生一些影响。我还没有考虑过一个简短而好的解决方案吗?

回答Szabolcs的帖子

  

你能告诉我为什么你在这里使用吗?

是的,这与我无法使用的原因有关:= here。我使用With,在C中有类似#define的东西,这意味着在我需要的地方进行代码替换。使用:= in延迟评估和Compile的主体看到的不是它应该编译的最后一段代码。因此,

<< CompiledFunctionTools`
cf = Block[{a, x, degree = 3}, 
   With[{expr := Product[x - a[[i]], {i, degree}]}, 
    Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr]]];
CompilePrint[cf]

向您显示,在编译函数

中调用了Mathematica内核
I4 = MainEvaluate[ Function[{x, a}, degree][ R0, T(R1)0]]

这很糟糕,因为Compile应该只使用局部变量来计算结果。

更新

Szabolcs解决方案适用于这种情况,但它使整个表达式无法评估。让我解释一下,为什么在编译之前扩展表达式很重要。我不得不承认,我的玩具示例并不是最好的。因此,让我们尝试使用With和SetDelayed更好的一个,就像在Szabolcs

的解决方案中一样
Block[{a, x}, With[
  {expr := D[Product[x - a[[i]], {i, 3}], x]}, 
  Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr]
  ]
 ]

假设我有一个3阶多项式,我需要在Compile中得到它的导数。在上面的代码中,我希望Mathematica计算未分配根a [[i]]的导数,这样我就可以在编译代码中经常使用公式 very 。看看上面的编译代码 人们看到,D [..]不能像产品那样编译得很好而且没有评估

11  R1 = MainEvaluate[ Hold[D][ R5, R0]]

因此,我更新的问题是:是否可以在不评估Part []的情况下评估一段代码 - 其中的访问比使用

更好/更好
Block[{a, x}, With[
  {expr = D[Quiet@Product[x - a[[i]], {i, 3}], x]}, 
  Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr]
  ]
 ]

编辑:我把Quiet放到它所属的地方。我在代码块前面让它对所有人都可见,我在这里使用Quiet来抑制警告。正如Ruebenko已经指出的那样,它应该在实际代码中始终尽可能接近它所属的位置。使用这种方法,您可能不会错过其他重要的警告/错误。

更新2

由于我们正在摆脱最初的问题,我们应该将这个讨论转移到新的主题。我不知道我应该向谁提出最佳答案 - 因为我们讨论了 Mathematica和范围而不是如何抑制[[i]]问题。

更新3

为了给出最终解决方案:我只是简单地抑制(不幸的是我一直都这样做)使用Quiet的[[i]]警告。在下面的一个实际例子中,我必须在完整Block之外使用Quiet来抑制警告。

要将所需的代码注入Compile的主体,我使用一个纯函数并将代码作为参数提供内联。这与Michael Trott使用的方法相同,例如他的数学书。这有点像Haskell中的where子句,您可以在其中定义之后使用的内容。

newtonC = Function[{degree, f, df, colors},
   Compile[{{x0, _Complex, 0}, {a, _Complex, 1}},
    Block[{x = x0, xn = 0.0 + 0.0 I, i = 0, maxiter = 256, 
      eps = 10^(-6.), zeroId = 1, j = 1},
     For[i = 0, i < maxiter, ++i,
      xn = x - f/(df + eps);
      If[Abs[xn - x] < eps,
       Break[]
       ];
      x = xn;
      ];
     For[j = 1, j <= degree, ++j,
      If[Abs[xn - a[[j]]] < eps*10^2,
        zeroId = j + 1;
        Break[];
        ];
      ];
     colors[[zeroId]]*(1 - (i/maxiter)^0.3)*1.5
     ],
    CompilationTarget -> "C", RuntimeAttributes -> {Listable}, 
    RuntimeOptions -> "Speed", Parallelization -> True]]@@
    (Quiet@Block[{degree = 3, polynomial, a, x},
     polynomial = HornerForm[Product[x - a[[i]], {i, degree}]];
     {degree, polynomial, HornerForm[D[polynomial, x]], 
      List @@@ (ColorData[52, #] & /@ Range[degree + 1])}])

此函数现在足够快,可以计算多项式的牛顿分形,其中根的位置不固定。因此,我们可以动态调整根。 随意调整n。在这里它可以流畅地运行到n = 756

(* ImageSize n*n, Complex plange from -b-I*b to b+I*b *)
With[{n = 256, b = 2.0},
 DynamicModule[{
   roots = RandomReal[{-b, b}, {3, 2}],
   raster = Table[x + I y, {y, -b, b, 2 b/n}, {x, -b, b, 2 b/n}]},
  LocatorPane[Dynamic[roots],
   Dynamic[
    Graphics[{Inset[
       Image[Reverse@newtonC[raster, Complex @@@ roots], "Real"],
       {-b, -b}, {1, 1}, 2 {b, b}]}, PlotRange -> {{-b, b}, {-
         b, b}}, ImageSize -> {n, n}]], {{-b, -b}, {b, b}}, 
   Appearance -> Style["\[Times]", Red, 20]
   ]
  ]
 ]

预告:

Dynamic Newton fractal

4 个答案:

答案 0 :(得分:10)

好的,这是我用于各种目的的代码生成框架的非常过度简化版本:

ClearAll[symbolToHideQ]
SetAttributes[symbolToHideQ, HoldFirst];
symbolToHideQ[s_Symbol, expandedSymbs_] :=! MemberQ[expandedSymbs, Unevaluated[s]];

ClearAll[globalProperties]
globalProperties[] := {DownValues, SubValues, UpValues (*,OwnValues*)};

ClearAll[getSymbolsToHide];
Options[getSymbolsToHide] = {
     Exceptions -> {List, Hold, HoldComplete, 
        HoldForm, HoldPattern, Blank, BlankSequence, BlankNullSequence, 
       Optional, Repeated, Verbatim, Pattern, RuleDelayed, Rule, True, 
       False, Integer, Real, Complex, Alternatives, String, 
       PatternTest,(*Note-  this one is dangerous since it opens a hole 
                    to evaluation leaks. But too good to be ingored *)
       Condition, PatternSequence, Except
      }
 };

getSymbolsToHide[code_Hold, headsToExpand : {___Symbol}, opts : OptionsPattern[]] :=
  Join @@ Complement[
       Cases[{
          Flatten[Outer[Compose, globalProperties[], headsToExpand]], code},
            s_Symbol /; symbolToHideQ[s, headsToExpand] :> Hold[s],
            Infinity,
            Heads -> True
       ],
       Hold /@ OptionValue[Exceptions]];

ClearAll[makeHidingSymbol]
SetAttributes[makeHidingSymbol, HoldAll];
makeHidingSymbol[s_Symbol] := 
    Unique[hidingSymbol(*Unevaluated[s]*) (*,Attributes[s]*)];

ClearAll[makeHidingRules]
makeHidingRules[symbs : Hold[__Symbol]] :=
     Thread[List @@ Map[HoldPattern, symbs] -> List @@ Map[makeHidingSymbol, symbs]];

ClearAll[reverseHidingRules];
reverseHidingRules[rules : {(_Rule | _RuleDelayed) ..}] :=
   rules /. (Rule | RuleDelayed)[Verbatim[HoldPattern][lhs_], rhs_] :> (rhs :> lhs);


FrozenCodeEval[code_Hold, headsToEvaluate : {___Symbol}] :=   
   Module[{symbolsToHide, hidingRules, revHidingRules,  result}, 
      symbolsToHide = getSymbolsToHide[code, headsToEvaluate];
      hidingRules = makeHidingRules[symbolsToHide];
      revHidingRules = reverseHidingRules[hidingRules];
      result = 
         Hold[Evaluate[ReleaseHold[code /. hidingRules]]] /. revHidingRules;
      Apply[Remove, revHidingRules[[All, 1]]];
      result];

代码的工作原理是暂时隐藏大多数符号和一些虚拟符号,并允许某些符号进行评估。以下是这将如何工作:

In[80]:= 
FrozenCodeEval[
  Hold[Compile[{{x,_Real,0},{a,_Real,1}},D[Product[x-a[[i]],{i,3}],x]]],
  {D,Product,Derivative,Plus}
]

Out[80]= 
Hold[Compile[{{x,_Real,0},{a,_Real,1}},
  (x-a[[1]]) (x-a[[2]])+(x-a[[1]]) (x-a[[3]])+(x-a[[2]]) (x-a[[3]])]]

因此,要使用它,您必须将代码包装在Hold中,并指出您要评估的头部。剩下的就是将ReleseHold应用于它。请注意,上面的代码只是说明了这些想法,但仍然非常有限。我的方法的完整版本涉及其他步骤,使其更强大,但也更复杂。

编辑

虽然上面的代码仍然太有限,无法容纳许多非常有趣的案例,但这里有一个额外的简洁示例,即使用传统的评估控制工具rather hard to achieve

In[102]:= 
FrozenCodeEval[
  Hold[f[x_, y_, z_] := 
    With[Thread[{a, b, c} = Map[Sqrt, {x, y, z}]], 
       a + b + c]], 
  {Thread, Map}]

Out[102]= 
Hold[
  f[x_, y_, z_] := 
    With[{a = Sqrt[x], b = Sqrt[y], c = Sqrt[z]}, a + b + c]]

答案 1 :(得分:4)

编辑 - 大警告!! 使用WithFunction将代码注入使用Compile的{​​{1}} 1}}的局部变量不可靠!请考虑以下事项:

Compile

请注意在第一种情况下将In[63]:= With[{y=x},Compile[x,y]] Out[63]= CompiledFunction[{x$},x,-CompiledCode-] In[64]:= With[{y=x},Compile[{{x,_Real}},y]] Out[64]= CompiledFunction[{x},x,-CompiledCode-] 重命名为x。我建议您阅读有关本地化herehere的信息。 (是的,这很令人困惑!)我们可以猜测为什么这只发生在第一种情况而不是第二种情况,但我的观点是这种行为可能不是故意的(称之为错误,暗角或未定义的行为),所以依靠它是脆弱的......

基于x$的解决方案,就像我的withRules function一样有效(这不是我对该功能的预期用途,但它在这里很合适......)

Replace

原始答案

您可以在In[65]:= withRules[{y->x},Compile[x,y]] Out[65]= CompiledFunction[{x},x,-CompiledCode-] 中使用:=,如下所示:

With

它将避免评估cf = Block[{a, x, degree = 3}, With[{expr := Product[x - a[[i]], {i, degree}]}, Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr] ] ] 和来自expr的错误。

通常,Part=在所有:=WithModule中按预期工作。


你能告诉我为什么你在这里使用Block吗? (我确定你有充分的理由,我从这个简化的例子中看不出来。)


其他答案

解决@ halirutan关于编译期间没有内联With的担忧

我认为这与我们在degree中使用的全局变量定义完全相同。举个例子:

Compile

这是一个常见问题。解决方案是将In[18]:= global=1 Out[18]= 1 In[19]:= cf2=Compile[{},1+global] Out[19]= CompiledFunction[{},1+global,-CompiledCode-] In[20]:= CompilePrint[cf2] Out[20]= No argument 3 Integer registers Underflow checking off Overflow checking off Integer overflow checking on RuntimeAttributes -> {} I0 = 1 Result = I2 1 I1 = MainEvaluate[ Function[{}, global][ ]] 2 I2 = I0 + I1 3 Return 告诉inline globals,如下所示:

Compile

您可以检查现在主要评估者没有回调。


或者,您可以使用额外的cf = Block[{a, x, degree = 3}, With[{expr := Product[x - a[[i]], {i, degree}]}, Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr, CompilationOptions -> {"InlineExternalDefinitions" -> True}]]]; CompilePrint[cf] 层代替degree来注入With的值。这将使你非常希望something like this


Mathematica中的宏扩展

这有些不相关,但您在帖子中提到您使用Block进行宏扩展。这是我在Mathematica中实现宏扩展的第一个(可能是错误的)。这还没有经过充分测试,可以试着打破并发表评论。

With

说明

Clear[defineMacro, macros, expandMacros] macros = Hold[]; SetAttributes[defineMacro, HoldAllComplete] defineMacro[name_Symbol, value_] := (AppendTo[macros, name]; name := value) SetAttributes[expandMacros, HoldAllComplete] expandMacros[expr_] := Unevaluated[expr] //. Join @@ (OwnValues /@ macros) 是要扩展的所有符号的(保留)列表。 macros将创建一个新宏。 defineMacro将扩展表达式中的宏定义。

注意:我没有实现宏重新定义,这在使用expandMacros进行扩展时不起作用。还要注意递归宏定义和无限循环。

<强>用法:

通过定义$Pre

对所有输入进行宏扩展
$Pre

$Pre = expandMacros; 定义为值1:

a

defineMacro[a, 1] 设置延迟定义:

b

请注意,b := a + 1 的定义尚未完全评估,但b已展开。

a

关闭宏扩展(如果我的代码中存在错误,?b Global`b b:=1+1 可能会有危险):

$Pre

答案 2 :(得分:2)

一种方式:

cf = Block[{a, x, degree = 3}, 
  With[{expr = Quiet[Product[x - a[[i]], {i, degree}]]}, 
   Compile[{{x, _Real, 0}, {a, _Real, 1}}, expr]]]
但是要小心,你真的想要这个。

答案 3 :(得分:0)

原始代码:

newtonC = Function[{degree, f, df, colors},
Compile[{{x0, _Complex, 0}, {a, _Complex, 1}},
Block[{x = x0, xn = 0.0 + 0.0 I, i = 0, maxiter = 256, 
...
RuntimeOptions -> "Speed", Parallelization -> True]]@@
(Quiet@Block[{degree = 3, polynomial, a, x},
 polynomial = HornerForm[Product[x - a[[i]], {i, degree}]];
...

修改后的代码:

newtonC = Function[{degree, f, df, colors},
Compile[{{x0, _Complex, 0}, {a, _Complex, 1}},
Block[{x = x0, xn = 0.0 + 0.0 I, i = 0, maxiter = 256, 
...
RuntimeOptions -> "Speed", Parallelization -> True],HoldAllComplete]@@
( (( (HoldComplete@@#)/.a[i_]:>a[[i]] )&)@Block[{degree = 3, polynomial, a, x},
 polynomial = HornerForm[Product[x - a[i], {i, degree}]];
...

HoldAllComplete属性添加到函数中。

a[i]代替a[[i]]

Quiet替换为(( (HoldComplete@@#)/.a[i_]:>a[[i]] )&)

生成相同的代码,没有Quiet,所有Hold内容都在一个地方。