您的Mathematica工具箱里有什么?

159
我们都知道Mathematica非常棒,但它经常缺乏关键功能。你使用哪些外部软件包/工具/资源来与Mathematica一起使用? 我会编辑(并邀请其他人也这样做)这个主帖,以包括专注于科学研究的通用性资源,并且尽可能多的人会发现它们有用。随意贡献任何东西,甚至是小的代码片段(正如我在下面为计时例程所做的那样)。 此外,请包含简短的描述或评论,说明某些东西的优点或提供的实用程序。如果您使用带有附属链接的Amazon图书,请在链接后面提到它,例如通过在链接后面放置您的姓名。

程序包:

  1. LevelScheme是一个扩展Mathematica能力的包,可以生成漂亮的图形。我使用它,不仅因为它可以更好地控制框架/轴刻度,而且因为其最新版本称为SciDraw,将在今年发布。
  2. David Park的Presentation Package(50美元-更新免费)
  3. Jeremy Michelson的grassmannOps包提供了使用Grassmann变量和算子进行代数和微积分运算的资源,这些变量和算子具有非平凡的对易关系。
  4. John Brown的GrassmannAlgebra包和书籍用于处理Grassmann和Clifford代数。
  5. RISC(符号计算研究所)有多个可供下载的Mathematica(和其他语言)包。特别是,有Theorema用于自动定理证明,以及在Algorithmic Combinatorics group's software page上的多个包,用于符号求和、差分方程等。

工具:

  1. MASH 是 Daniel Reeves 出色的 Perl 脚本,基本上为 Mathematica v7 提供了脚本支持。 (现在已经作为 Mathematica 8 的内置功能,使用 -script 选项.)
  2. 一款带有 GNU readline 输入的备用 Mathematica shell (仅限于使用 python 的 *nix 系统)
  3. ColourMaths 包允许您直观地选择表达式的部分并对其进行操作。 http://www.dbaileyconsultancy.co.uk/colour_maths/colour_maths.html

资源:

  1. 沃尔夫拉姆自己的存储库 MathSource 有很多有用但比较狭窄的笔记本,适用于各种应用。还要查看其他部分,例如:

  2. Mathematica Wikibook

书籍:

  1. 如果你想在Mathematica中做更多的事情而不仅仅是使用For循环,那么Leonid Shifrin的《Mathematica programming: an advanced introduction》(webpdf)是必读的。我们很荣幸有Leonid本人在这里回答问题。
  2. James F. Feagin的《Quantum Methods with Mathematica》(amazon
  3. Stephen Wolfram的《The Mathematica Book》(amazon)(web
  4. Schaum's Outline(amazon
  5. Stan Wagon的《Mathematica in Action》(amazon}})- 600页整洁的示例,适用于Mathematica版本7及以上。可视化技术尤其出色,您可以在作者的Demonstrations Page上看到一些示例。
  6. Richard Gaylord的《Mathematica Programming Fundamentals》(pdf)- 对Mathematica编程的大部分必要知识进行了简明扼要的介绍。
  7. Sal Mangano所著的《Mathematica Cookbook》由O'Reilly于2010年出版,共832页。 - 采用著名的O'Reilly Cookbook风格:问题-解决方案。适用于中级用户。
  8. Martha L. Abell和James P. Braselton所著的《Differential Equations with Mathematica, 3rd Ed. Elsevier 2004 Amsterdam》- 893页。适用于初学者,可以同时学习解决微分方程和Mathematica。

未经记录(或很少记录)的功能:

  1. 如何自定义Mathematica键盘快捷键。请参见this question
  2. 如何检查Mathematica自身函数使用的模式和函数。请参见this answer
  3. 如何在Mathematica中实现GraphPlots的一致大小?请参见this question
  4. 如何使用Mathematica制作文档和演示文稿。请参见this question

2
Mathematica 8已经发布,具有更好的shell脚本集成功能。http://www.wolfram.com/mathematica/new-in-8/mathematica-shell-scripts/ - Joshua Martell
2
+1,支持LevelScheme。虽然有时速度有点慢,但它有一个合理的方法来创建刻度线,而且比“Grid”或类似的工具更容易创建适合期刊的图形布局。 - rcollyer
2
正如Alexey在这个问题的评论中提出的建议https://dev59.com/I1TTa4cB1Zd3GeqPvcev,我在这里提出了Mathematica的标签重命名:http://meta.stackexchange.com/questions/81152/retag-mathematica-to-wr-mathematica-or-something-similar。请看一下,如果您同意,请点赞。我在这里发布是因为这个问题在Mma社区中有很多收藏。 - Dr. belisarius
1
这个问题应该是社区维基的,因为它没有正确答案,更像是一个列表。我向所有从这个问题中获得声望的人道歉。 - rcollyer
2
这些回答对于这个问题是有建设性的,应该重新开放。 - M.R.
显示剩余9条评论
26个回答

59

Mathematica的笔记本界面有一个好处,就是它可以评估任何语言的表达式,而不仅仅是Mathematica。举个简单例子,考虑创建一种新的Shell输入单元格类型,该类型将包含的表达式传递给操作系统shell进行评估。

首先,定义一个函数,将文本命令的评估委托给外部shell:

shellEvaluate[cmd_, _] := Import["!"~~cmd, "Text"]

第二个参数是必需的,但出于后面会变得明显的原因而被忽略。接下来,我们想要创建一个名为"Shell"的新样式:
  1. 打开一个新笔记本。
  2. 选择菜单项"格式/编辑样式表..."
  3. 在对话框中,在"输入样式名称:"旁边键入Shell
  4. 选择新样式旁边的单元括号。
  5. 选择菜单项"单元格/显示表达式"
  6. 用下面给出的步骤6文本覆盖单元格表达式。
  7. 再次选择菜单项"单元格/显示表达式"
  8. 关闭对话框。
请使用以下单元格表达式作为步骤6文本
Cell[StyleData["Shell"],
 CellFrame->{{0, 0}, {0.5, 0.5}},
 CellMargins->{{66, 4}, {0, 8}},
 Evaluatable->True,
 StripStyleOnPaste->True,
 CellEvaluationFunction->shellEvaluate,
 CellFrameLabels->{{None, "Shell"}, {None, None}},
 Hyphenation->False,
 AutoQuoteCharacters->{},
 PasteAutoQuoteCharacters->{},
 LanguageCategory->"Formula",
 ScriptLevel->1,
 MenuSortingValue->1800,
 FontFamily->"Courier"]

大部分表达式都是直接从内置的Program样式中复制而来。关键更改在于以下几行代码:
 Evaluatable->True,
 CellEvaluationFunction->shellEvaluate,
 CellFrameLabels->{{None, "Shell"}, {None, None}},

Evaluatable使单元格具有SHIFT+ENTER功能。评估将调用CellEvaluationFunction,将单元格内容和内容类型作为参数传递(shellEvaluate忽略后一个参数)。CellFrameLabels只是一个美观的细节,让用户知道这个单元格很不寻常。

有了这些,我们现在可以输入和评估shell表达式:

  1. 在上面步骤中创建的笔记本中,创建一个空单元格并选择单元格括号。
  2. 选择菜单项格式/样式/Shell
  3. 在单元格中键入有效的操作系统shell命令(例如Unix上的'ls'或Windows上的'dir')。
  4. 按SHIFT+ENTER。

最好将此定义的样式保存在一个集中的样式表中。此外,像shellEvaluate这样的评估函数最好使用init.m中的DeclarePackage定义为存根。这些活动的详细信息超出了本响应的范围。

有了这个功能,人们可以创建包含任何感兴趣的语法的输入表达式的笔记本。评估函数可以用纯Mathematica编写,也可以将评估的任何部分全部委托给外部机构。请注意,还有其他与单元格评估相关的钩子,如CellEpilogCellPrologCellDynamicExpression

一个常见的模式是将输入表达式文本写入临时文件中,用某种语言编译文件,运行程序并捕获输出以在输出单元格中显示。 实现这种完整解决方案时需要处理很多细节(例如正确捕获错误消息),但人们必须欣赏到这样做不仅是可能的,而且是实际可行的。
就个人而言,正是这些功能使得笔记本界面成为我的编程宇宙的中心。
更新
以下辅助函数对于创建这样的单元格非常有用:
evaluatableCell[label_String, evaluationFunction_] :=
  ( CellPrint[
      TextCell[
        ""
      , "Program"
      , Evaluatable -> True
      , CellEvaluationFunction -> (evaluationFunction[#]&)
      , CellFrameLabels -> {{None, label}, {None, None}}
      , CellGroupingRules -> "InputGrouping"
      ]
    ]
  ; SelectionMove[EvaluationNotebook[], All, EvaluationCell]
  ; NotebookDelete[]
  ; SelectionMove[EvaluationNotebook[], Next, CellContents]
  )

它的使用方法如下:
shellCell[] := evaluatableCell["shell", Import["!"~~#, "Text"] &]

现在,如果评估shellCell[],输入单元格将被删除并替换为一个新的输入单元格,该单元格将评估其内容作为一个shell命令。

3
@WReach +100!我希望我早些知道这个!对我来说,这是非常有用的内容。感谢您的分享! - Leonid Shifrin
这看起来相当不错!我认为CellEvaluationFunction也可以用于低级语法黑客。 - Mr.Wizard
@Mr.Wizard - 就前端而言,我认为是的! - Leonid Shifrin
2
另外:还有另一个与单元格评估相关的“Cell”选项 - Evaluator ->“EvaluatorName”。 “EvaluatorName”的含义可以通过Evaluation :: Kernel Configuration Options ...对话框进行配置。我仍然不知道是否可以通过编程来配置它...这种技术允许在一个笔记本中的不同Cell中使用不同的MathKernels。这些MathKernels可以来自安装的不同版本的Mathematica - Alexey Popkov
1
@Szabolcs 我使用这种技术的所有情况都涉及到类似上面所示的_stdin_/_stdout_方法,或者是一个自包含的远程请求,例如SQL查询或HTTP操作。你可以尝试设置一个Python REPL Web应用程序(例如此处),并使用Import与其交互,或者启动一个外部Python进程并通过其流进行通信(例如使用Java ProcessBuilder)。我相信有更好的Mathematica方法--听起来像是一个不错的SO问题 :) - WReach
显示剩余3条评论

41

Todd Gayley(来自 Wolfram Research)给我发来了一个很棒的技巧,可以用任意代码“包装”内置函数。我觉得有必要分享这个非常有用的工具。以下是 Todd 在我的问题上的回答。

有趣的历史(?):那种“包装”内置函数的技巧最早是在1994年由 Robby Villegas 和我发明的,具体而言是针对我当时为 Mathematica Journal 写的一个名为 ErrorHelp 的包中的 Message 函数。从那时起,它已经被许多人多次使用。虽然这是一种内部技巧,但我认为可以说它已经成为向内置函数注入自己的代码的规范方式。它可以很好地完成任务。当然,你可以将 $inMsg 变量放在任何私有上下文中。

Unprotect[Message];

Message[args___] := Block[{$inMsg = True, result},
   "some code here";
   result = Message[args];
   "some code here";
   result] /; ! TrueQ[$inMsg]

Protect[Message];

@Alexey,我不太理解这个。你能解释一下它是如何工作的吗?应该在某个地方加上Unprotect[Message]吧?这个例子是否包含无限递归?而且,! TrueQ[$inMsg] 这样做有意义吗?因为$inMsg在Block作用域内被定义,在Block外部未定义。 - Sjoerd C. de Vries
10
据我所了解,确实需要使用 Unprotect,只是被省略了。Block(动态作用域)和 $inMsg 的作用正是为了防止无限递归。因为 $inMsg 在外部是未定义的(这是一个重要的要求),所以一开始 TrueQ 结果为 False,我们进入函数体。但是当函数体内有函数调用时,由于变量已经被Block重新定义,条件评估结果为 False。因此,用户定义的规则不匹配,而是使用了内置规则。 - Leonid Shifrin
1
我刚刚发现这个技巧是由沃尔夫拉姆研究公司的Robby Villegas在1999年开发者大会上讨论的。请参见“使用未求值表达式”笔记本,它发布在这里。在这个笔记本中,Robby Villegas在“用于捕获内置函数调用的My Block技巧”子部分中讨论了这个技巧。 - Alexey Popkov
没有这个,我不可能回答Simon的悬赏问题。 - Mr.Wizard
1
@Mr.Wizard 这不是唯一的方法。很长一段时间,我使用了一种在运行时重新定义DownValues的版本,您可以查看此帖子http://groups.google.com/group/comp.soft-sys.math.mathematica/browse_thread/thread/c000439b48751078,以获取示例(`SetDelayed`重新定义)。但我的方法不够优雅,不够健壮,更容易出错,并且使得从递归中退出变得更加不平凡。因此,在大多数情况下,@Alexey描述的方法胜出。 - Leonid Shifrin
@Leonid 让我跟着回应一下:“哇,看起来很复杂。” :-) - Mr.Wizard

30

我之前提到过这个,但我发现最有用的工具是使用ReapSow的一个应用程序,它模仿/扩展了GatherBy的行为:

SelectEquivalents[x_List,f_:Identity, g_:Identity, h_:(#2&)]:=
   Reap[Sow[g[#],{f[#]}]&/@x, _, h][[2]];
这使我能够按照任何标准对列表进行分组并在此过程中进行转换。它的工作方式是标准函数 (f) 标记列表中的每个项目,然后第二个提供的函数 (g) 转换每个项目,并由第三个函数 (h) 控制特定输出。函数 h 接受两个参数: 标记和具有该标记的收集项目列表。项目保留其原始顺序,因此如果您设置 h = #1&,则会获得未排序的 Union,就像 Reap示例一样。但是,它可以用于二次处理。
作为其效用的一个例子,我一直在使用Wannier90,它将空间相关哈密顿量输出到文件中,其中每行都是矩阵中的不同元素,如下所示。
rx ry rz i j Re[Hij] Im[Hij]

为将该列表转换为矩阵集,我收集了所有包含相同坐标的子列表,将元素信息转换为规则(即{ i,j} -> Re [Hij] + I Im [Hij]),然后将收集到的规则全部转换为 SparseArray,这可以通过一行代码实现:

SelectEquivalents[hamlst, 
      #[[;; 3]] &, 
      #[[{4, 5}]] -> (Complex @@ #[[6 ;;]]) &, 
      {#1, SparseArray[#2]} &]
老实说,这是我的瑞士军刀,它使得复杂的事情变得非常简单。我其它大多数工具都是领域特定的,所以我可能不会发帖分享它们。然而,大多数(如果不是全部)都引用了SelectEquivalents编辑:它并不能像GatherBy那样完全模仿,因为它不能像GatherBy一样简单地对表达式的多个层级进行分组。不过,对于我大部分的需求,Map也能很好地工作。

例子:@Yaroslav Bulatov 要求提供一个自包含的例子。这是我研究中被极大简化的一个例子。假设我们有一个平面上的点集。
In[1] := pts = {{-1, -1, 0}, {-1, 0, 0}, {-1, 1, 0}, {0, -1, 0}, {0, 0, 0}, 
 {0, 1, 0}, {1, -1, 0}, {1, 0, 0}, {1, 1, 0}}

我们希望通过一组对称操作减少点的数量。 (好奇的话,我们正在生成每个点的小群。)在这个例子中,让我们使用绕z轴的四重旋转轴。

In[2] := rots = RotationTransform[#, {0, 0, 1}] & /@ (Pi/2 Range[0, 3]);

使用SelectEquivalents,我们可以根据下列操作将产生相同图像集合的点分组,即它们是等价的。

In[3] := SelectEquivalents[ pts, Union[Through[rots[#] ] ]& ] (*<-- Note Union*)
Out[3]:= {{{-1, -1, 0}, {-1, 1, 0}, {1, -1, 0}, {1, 1, 0}},
          {{-1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {1, 0, 0}},
          {{0,0,0}}}
使用Union十分重要,因为它确保每个点产生了相同的图像。如果使用Sort,当一个点在对称轴上时,它在该轴上的旋转不变性会产生自身的额外图像。因此,Union可以消除这些额外的图像。在这种情况下,这些点已经处于我将使用的形式,但我只需要每个分组中的代表点和等效点的数量。由于我不需要转换每个点,所以我在第二个位置使用Identity函数。对于第三个函数,我们需要小心。传递给它的第一个参数将是点在旋转下的图像,对于点{0,0,0},它是四个相同元素的列表,使用它会导致计数失误。然而,第二个参数只包含具有该标记的所有元素,因此它只包含{0,0,0}。代码如下:
In[4] := SelectEquivalents[pts,  
             Union[Through[rots[#]]]&, #&, {#2[[1]], Length[#2]}& ]
Out[4]:= {{{-1, -1, 0}, 4}, {{-1, 0, 0}, 4}, {{0, 0, 0}, 1}}

请注意,最后这一步同样可以轻松地通过完成

In[5] := {#[[1]], Length[#]}& /@ Out[3]

不过,通过这个简化示例和上面的较为完整的示例,可以很容易地看出即使使用最少量的代码也可以实现非常复杂的转换。


原始的Fortran77代码在1996年感恩节当天重构,因此多年被称为turkey.f。非常漂亮的图形,顺便提一下。让我想起了Falicov的怪物... :D - Dr. belisarius
@belisarius,我之前没有看过这个历史,真有趣。我刚开始使用Wannier90,但它是我见过的最有组织和书写良好的Fortran代码之一。这让我几乎考虑使用Fortran... - rcollyer
我想知道你是否能够添加一个自包含的SelectEquivalents示例。 - Yaroslav Bulatov
@Yaroslav Bulatov,根据要求添加了一个示例。如果这不起作用,我们会看看能做什么。 - rcollyer
@belisarius 看起来像是手绘的,太酷了。 - rcollyer
显示剩余3条评论

25

这不是一个完整的资源,所以我把它放在答案部分,但当解决速度问题时(不幸的是,这是Mathematica编程的重要部分)我发现它非常有用。

timeAvg[func_] := Module[
{x = 0, y = 0, timeLimit = 0.1, p, q, iterTimes = Power[10, Range[0, 10]]},
Catch[
 If[(x = First[Timing[(y++; Do[func, {#}]);]]) > timeLimit,
    Throw[{x, y}]
    ] & /@ iterTimes
 ] /. {p_, q_} :> p/iterTimes[[q]]
];
Attributes[timeAvg] = {HoldAll};

使用方法就是简单地调用timeAvg@funcYouWantToTest

编辑:Mr. Wizard提供了一个更简单的版本,不需要使用ThrowCatch,更易于理解:

SetAttributes[timeAvg, HoldFirst]
timeAvg[func_] := Do[If[# > 0.3, Return[#/5^i]] & @@ 
                     Timing @ Do[func, {5^i}]
                     ,{i, 0, 15}]

编辑:这里有一个版本来自acl(取自这里):

timeIt::usage = "timeIt[expr] gives the time taken to execute expr, \
  repeating as many times as necessary to achieve a total time of 1s";

SetAttributes[timeIt, HoldAll]
timeIt[expr_] := Module[{t = Timing[expr;][[1]], tries = 1},
  While[t < 1., tries *= 2; t = Timing[Do[expr, {tries}];][[1]];]; 
  t/tries]

1
这段代码的一个问题(或许这只是完美主义者的观点)是我们可以捕获我们没有抛出的东西,并将其解释为不正确的时间结果。CatchThrow都应该使用唯一的异常标签。 - Leonid Shifrin
2
Timo,我很高兴你喜欢我的演绎并将其包含在内。谢谢你也给了我信用。我对你重新格式化我的代码的方式很好奇。我在自己的代码中没有遵循任何特定的指南,除了使自己易于阅读;你重新格式化的背后是否有一种思想流派,还是只是个人偏好?由于Mathematica重新排列输入的方式,它不鼓励精确的代码格式,但在这里发布代码让我开始考虑它。顺便说一句,我认为你的意思是“ThrowCatch”,而不是“ReapSow”。 - Mr.Wizard
@Mr. 实际上,Wolfram Workbench 包括使用 Eclipse 格式化的标准... - Dr. belisarius
1
@Simon,Mr.Wizard,我使用这种方法来计时小型函数的不同版本,这些函数将被调用很多次。不一定在循环结构中,但肯定在MMA优化的结构中。在这种情况下,计时循环的执行是有意义的,并且性能将接近实际应用程序。对于计时大型复杂函数(甚至整个初始化单元),Simon的方法将给出更好的结果。总的来说,我更关心相对值,任何一种方法都可以在那里工作。 - Timo
3
现在有RepeatedTiming可以做到这一点。 - masterxilo
显示剩余9条评论

21

Internal`InheritedBlock

最近我了解到一个非常有用的函数Internal`InheritedBlock,从官方新闻组中的Daniel Lichtblau的这条信息中。

据我所知,Internal`InheritedBlock允许在Block范围内传递一个外部函数的副本:

In[1]:= Internal`InheritedBlock[{Message},
Print[Attributes[Message]];
Unprotect[Message];
Message[x___]:=Print[{{x},Stack[]}];
Sin[1,1]
]
Sin[1,1]
During evaluation of In[1]:= {HoldFirst,Protected}
During evaluation of In[1]:= {{Sin::argx,Sin,2},{Internal`InheritedBlock,CompoundExpression,Sin,Print,List}}
Out[1]= Sin[1,1]
During evaluation of In[1]:= Sin::argx: Sin called with 2 arguments; 1 argument is expected. >>
Out[2]= Sin[1,1]

我认为这个函数对于需要暂时修改内置函数的所有人来说都非常有用!

与块的比较

让我们定义一些函数:

a := Print[b]

现在我们希望将这个函数的一个副本传递到Block作用域中。但是朴素的尝试并不能得到我们想要的结果:

In[2]:= Block[{a = a}, OwnValues[a]]

During evaluation of In[9]:= b

Out[2]= {HoldPattern[a] :> Null}

现在尝试在 Block 的第一个参数中使用延迟定义(它也是一个未记录的特性):

In[3]:= Block[{a := a}, OwnValues[a]]
Block[{a := a}, a]

Out[3]= {HoldPattern[a] :> a}

During evaluation of In[3]:= b
我们可以看到在这种情况下,a 可以工作,但我们没有在 Block 作用域中得到原始 a 的副本。
现在让我们尝试使用 Internal`InheritedBlock:
In[5]:= Internal`InheritedBlock[{a}, OwnValues[a]]

Out[5]= {HoldPattern[a] :> Print[b]}

我们已经在Block作用域内获得了变量a的原始定义副本,因此我们可以自由地修改它,而不会影响全局定义的变量a


非常方便!又有一个工具放在你的包里了,离编辑特权又近了10分。 - Mr.Wizard
对我来说,这似乎是早期、晚期、无和完全评估的一种变体。 - Steffen Jaeschke

20

Mathematica是一个强大的工具,但它可能会因为其有些未定义行为神秘的诊断信息雪崩而让你感到困惑。解决这个问题的方法之一是按照以下规则定义函数:

ClearAll@zot
SetAttributes[zot, ...]
zot[a_] := ...
zot[b_ /; ...] := ...
zot[___] := (Message[zot::invalidArguments]; Abort[])

这其中有很多样板代码,我常常被诱惑跳过它们。特别是在 Mathematica 中经常进行原型设计时更是如此。因此,我使用一个名为define的宏,使我能够保持纪律性,减少许多样板代码。

define的基本用法如下:

define[
  fact[0] = 1
; fact[n_ /; n > 0] := n * fact[n-1]
]

fact[5]

120

起初看起来不起眼,但实际上有一些隐藏的好处。 define 提供的第一个服务是自动将 ClearAll 应用于正在定义的符号。这确保没有剩余的定义——在函数初始开发过程中经常出现的情况。

第二项服务是正在定义的函数自动“关闭”。我的意思是,如果使用未与其中一个定义匹配的参数列表调用该函数,则该函数将发出消息并中止:

fact[-1]

define::badargs: There is no definition for 'fact' applicable to fact[-1].
$Aborted

这是define的主要价值,它可以捕捉一类非常常见的错误。

另一个方便之处是以简洁的方式指定正在定义的函数的属性。让我们将函数设为Listable

define[
  fact[0] = 1
; fact[n_ /; n > 0] := n * fact[n-1]
, Listable
]

fact[{3, 5, 8}]

{6, 120, 40320}

除了所有常规属性之外,define 还接受一个名为 Open 的附加属性。这可以防止 define 将万能错误定义添加到函数中。
define[
  successor[x_ /; x > 0] := x + 1
, Open
]

successor /@ {1, "hi"}

{2, successor["hi"]}

函数可以定义多个属性:

define[
  flatHold[x___] := Hold[x]
, {Flat, HoldAll}
]

flatHold[flatHold[1+1, flatHold[2+3]], 4+5]

Hold[1 + 1, 2 + 3, 4 + 5]

话不多说,这里是define的定义:

ClearAll@define
SetAttributes[define, HoldAll]
define[body_, attribute_Symbol] := define[body, {attribute}]
define[body:(_Set|_SetDelayed), attributes_List:{}] := define[CompoundExpression[body], attributes]
define[body:CompoundExpression[((Set|SetDelayed)[name_Symbol[___], _])..], attributes_List:{}] :=
  ( ClearAll@name
  ; SetAttributes[name, DeleteCases[attributes, Open]]
  ; If[!MemberQ[attributes, Open]
    , def:name[___] := (Message[define::badargs, name, Defer@def]; Abort[])
    ]
  ; body
  ;
  )
def:define[___] := (Message[define::malformed, Defer@def]; Abort[])

define::badargs = "There is no definition for '``' applicable to ``.";
define::malformed = "Malformed definition: ``";

这个实现不支持up-values和currying,也不支持比简单函数定义更一般的模式。然而,它仍然是有用的。


2
+1 - 这是非常有用的东西。我一直在使用类似的工具。宏(以及内省和其他元编程技术)可以非常强大,但在Mathematica社区中似乎普遍被低估,或者至少这是我迄今为止的印象。 - Leonid Shifrin
我刚刚定义了类似的东西。为CompoundExpression提供+1支持,以便进行多个定义,使用Abort[](好像比更多的消息更好),并且使用Open(例如构造函数很好)。 - masterxilo

16

PDF/EMF导出的常见问题和解决方案

1) 令人完全意外且未记录的是,Mathematica将图形以PDF和EPS格式导出并保存,使用的样式定义集与用于在屏幕上显示笔记本电脑的样式环境不同。默认情况下,笔记本电脑在“工作”样式环境下在屏幕上显示(这是全局$ FrontEnd 选项中 ScreenStyleEvironment 的默认值),但在“打印”样式环境下打印(这是全局$ FrontEnd 选项中PrintingStyleEnvironment 的默认值)。当您将图形导出为GIF、PNG或EMF格式时,Mathematica会生成看起来与Notebook内部一模一样的图形。在这种情况下,似乎使用"Working"样式环境进行渲染。但是,如果您将任何内容导出/保存为PDF或EPS格式,情况就不是这样了!在这种情况下,默认使用"Printout"样式环境,与“工作”样式环境非常不同。首先,"Printout"样式环境将Magnification设置为80%。其次,它使用自己的值来设置不同样式的字体大小,这会导致生成的PDF文件中的字体大小与原始屏幕表示相比发生不一致的变化。后者可以称为字体大小波动,非常令人讨厌。 不过,通过将全局$ FrontEnd 选项中的PrintingStyleEnvironment设置为“工作”,可以轻松避免此问题

SetOptions[$FrontEnd, PrintingStyleEnvironment -> "Working"]

2) 导出为EMF格式的常见问题是,大多数程序(不仅仅是 Mathematica)生成的文件在默认大小下看起来很好,但是放大后变得丑陋。这是因为元文件以屏幕分辨率精度进行采样。通过对原始图形对象进行Magnify,可以增强生成的EMF文件的质量,从而使原始图形的采样精度更加精确。比较两个文件:

graphics1 = 
  First@ImportString[
    ExportString[Style["a", FontFamily -> "Times"], "PDF"], "PDF"];
graphics2 = Magnify[graphics1, 10];
Export["C:\\test1.emf", graphics1]
Export["C:\\test2.emf", graphics2]

如果您将这些文件插入到Microsoft Word中并进行缩放,您会发现第一个 "a" 上有锯齿状,而第二个则没有(在Mathematica 6中测试)。

另一种方法是通过ImageResolution,由Chris Degnen建议(此选项至少从Mathematica 8开始生效):

Export["C:\\test1.emf", graphics1]
Export["C:\\test2.emf", graphics1, ImageResolution -> 300]

3) 在Mathematica中,我们有三种将图形转换为元文件的方式:通过Export"EMF"(强烈推荐的方式:产生最高质量的元文件),通过Save selection As...菜单项(生成远不如精确的图像,不推荐),以及通过Edit ► Copy As ► Metafile菜单项(我强烈建议不要使用此方法)。


16

启动时不打开空白笔记本

我对Mathematica启动时打开一个空白笔记本感到困扰。我可以通过脚本关闭这个笔记本,但它仍会短暂地闪现。我的解决方法是创建一个名为 Invisible.nb 的文件,其中包含以下内容:

Notebook[{},Visible->False]

并将以下内容添加到我的Kernel\init.m文件中:

If[Length[Notebooks["Invisible*"]] > 0, 
  NotebookClose[Notebooks["Invisible*"][[1]]]
]

SetOptions[$FrontEnd,
  Options[$FrontEnd, NotebooksMenu] /. 
    HoldPattern["Invisible.nb" -> {__}] :> Sequence[]
]

我现在通过打开Invisible.nb启动Mathematica。

可能有更好的方法,但这个方法对我很有效。


自定义的FoldFoldList

Fold[f, x]等价于Fold[f, First@x, Rest@x]

顺便说一下,我认为这可能会出现在未来版本的Mathematica中。

惊喜!尽管目前没有记录,但已经实现了此功能。 我被告知这是由Oliver Ruebenkoenig在2011年实现的,显然不久之后我发布了这篇文章。 谢谢Oliver Ruebenkoenig!

Unprotect[Fold, FoldList]

Fold[f_, h_[a_, b__]] := Fold[f, Unevaluated @ a, h @ b]
FoldList[f_, h_[a_, b__]] := FoldList[f, Unevaluated @ a, h @ b]

(* Faysal's recommendation to modify SyntaxInformation *)
SyntaxInformation[Fold]     = {"ArgumentsPattern" -> {_, _, _.}};
SyntaxInformation[FoldList] = {"ArgumentsPattern" -> {_, _., {__}}};

Protect[Fold, FoldList]

更新后可以这样做:

SetAttributes[f, HoldAll]
Fold[f, Hold[1 + 1, 2/2, 3^3]]
f[f[1 + 1, 2/2], 3^3]

"动态分区"

查看Mathematica.SE post #7512获取此函数的新版本。

经常我想根据序列长度来对列表进行分区。

伪代码示例:

partition[{1,2,3,4,5,6}, {2,3,1}]

输出:{{1,2}, {3,4,5}, {6}}

我想出了以下方法:

dynP[l_, p_] := 
 MapThread[l[[# ;; #2]] &, {{0} ~Join~ Most@# + 1, #} &@Accumulate@p]

然后我用以下代码进行了补充,并包括了参数检验:

dynamicPartition[l_List, p : {_Integer?NonNegative ..}] :=
  dynP[l, p] /; Length@l >= Tr@p

dynamicPartition[l_List, p : {_Integer?NonNegative ..}, All] :=
  dynP[l, p] ~Append~ Drop[l, Tr@p] /; Length@l >= Tr@p

dynamicPartition[l_List, p : {_Integer?NonNegative ..}, n__ | {n__}] :=
  dynP[l, p] ~Join~ Partition[l ~Drop~ Tr@p, n] /; Length@l >= Tr@p

第三个参数控制超出拆分规范的元素的处理方式。


Szabolcs的Mathematica技巧

我最常使用的是“粘贴表格数据面板”。

CreatePalette@
 Column@{Button["TSV", 
    Module[{data, strip}, 
     data = NotebookGet[ClipboardNotebook[]][[1, 1, 1]];
     strip[s_String] := 
      StringReplace[s, RegularExpression["^\\s*(.*?)\\s*$"] -> "$1"];
     strip[e_] := e;
     If[Head[data] === String, 
      NotebookWrite[InputNotebook[], 
       ToBoxes@Map[strip, ImportString[data, "TSV"], {2}]]]]], 
   Button["CSV", 
    Module[{data, strip}, 
     data = NotebookGet[ClipboardNotebook[]][[1, 1, 1]];
     strip[s_String] := 
      StringReplace[s, RegularExpression["^\\s*(.*?)\\s*$"] -> "$1"];
     strip[e_] := e;
     If[Head[data] === String, 
      NotebookWrite[InputNotebook[], 
       ToBoxes@Map[strip, ImportString[data, "CSV"], {2}]]]]], 
   Button["Table", 
    Module[{data}, data = NotebookGet[ClipboardNotebook[]][[1, 1, 1]];
     If[Head[data] === String, 
      NotebookWrite[InputNotebook[], 
       ToBoxes@ImportString[data, "Table"]]]]]}

Compile内部修改外部数据

最近,Daniel Lichtblau展示了我从未见过的这种方法。我认为它显著地扩展了Compile的实用性。

ll = {2., 3., 4.};
c = Compile[{{x}, {y}}, ll[[1]] = x; y];

c[4.5, 5.6]

ll

(* Out[1] = 5.6  *)

(* Out[2] = {4.5, 3., 4.}  *)

3
一个很好的集合!关于Compile内部的外部修改——我在这里的整个帖子:https://dev59.com/OlXTa4cB1Zd3GeqPxwz3#5251034,旨在展示这种可能性在一个非平凡的情境下(问题的更简短、更快速的解决方案已经发布)。在我看来,最大的优势在于能够模拟传递引用并将大型编译函数分解为更易管理和可重用的块。 - Leonid Shifrin
1
您还可以在新定义中调整Fold和FoldList的语法信息: SyntaxInformation[Fold] = {"ArgumentsPattern" -> { _ , _. , _}}; SyntaxInformation[FoldList] = {"ArgumentsPattern" -> { _ , _., {__}}}; - faysou

13

表达式缓存

我发现这些函数非常有用,可以缓存任何表达式。这两个函数的有趣之处在于,保存的表达式本身被用作哈希表/符号缓存或缓存索引的键,与数学中众所周知的记忆化不同,记忆化只能缓存函数定义为 f[x_] := f[x] = ... 这样的结果。因此,您可以缓存代码的任何部分,如果要多次调用函数但某些代码部分不必重新计算,则这非常有用。

独立缓存表达式而不考虑其参数。

SetAttributes[Cache, HoldFirst];
c:Cache[expr_] := c = expr;

Ex: Cache[Pause[5]; 6]
Cache[Pause[5]; 6]

第二次表达式不等待就返回6。
使用一个别名表达式缓存一个表达式,该别名表达式可以依赖于已缓存表达式的参数。
SetAttributes[CacheIndex, HoldRest];
c:CacheIndex[index_,expr_] := c = expr;

Ex: CacheIndex[{"f",2},x=2;y=4;x+y]

如果表达式计算需要一些时间,那么评估例如{"f",2}以检索缓存结果要快得多。
对于这些函数的变化,为了具有本地化缓存(即缓存内存在Block结构之外自动释放),请参见此帖子避免重复调用插值
删除缓存值
当您不知道函数定义的数量时,可以删除缓存值。我认为定义在其参数中的某个位置具有Blank。
DeleteCachedValues[f_] := 
       DownValues[f] = Select[DownValues[f], !FreeQ[Hold@#,Pattern]&];

当您知道函数的定义数量时(速度稍快),可以删除缓存值。

DeleteCachedValues[f_,nrules_] := 
       DownValues[f] = Extract[DownValues[f], List /@ Range[-nrules, -1]];

这利用了函数定义在其DownValues列表的末尾,缓存值在前面的事实。

使用符号存储数据和类似对象的函数

这里还有一些有趣的函数可以使用符号来模拟对象。

众所周知,您可以将数据存储在符号中,并使用DownValues快速访问它们。

mysymbol["property"]=2;

您可以使用以下函数访问符号的键(或属性)列表,这些函数基于dreeves在此网站上提交的帖子:

SetAttributes[RemoveHead, {HoldAll}];
RemoveHead[h_[args___]] := {args};
NKeys[symbol_] := RemoveHead @@@ DownValues[symbol(*,Sort->False*)][[All,1]];
Keys[symbol_] := NKeys[symbol] /. {x_} :> x;

我经常使用这个函数来显示一个符号中包含的所有DownValues信息:

PrintSymbol[symbol_] :=
  Module[{symbolKeys},
    symbolKeys = Keys[symbol];
    TableForm@Transpose[{symbolKeys, symbol /@ symbolKeys}]
  ];

最后,这里有一种简单的方法来创建一个行为类似于面向对象编程中的对象的符号(它只是复制了OOP的最基本行为,但我觉得语法很优雅):

Options[NewObject]={y->2};
NewObject[OptionsPattern[]]:=
  Module[{newObject},
    newObject["y"]=OptionValue[y];

    function[newObject,x_] ^:= newObject["y"]+x;
    newObject /: newObject.function2[x_] := 2 newObject["y"]+x;

    newObject
  ];

属性以DownValues的形式存储,方法以延迟的Upvalues的形式存储在由Module创建并返回的符号中。我发现函数2的语法是Mathematica中的Tree数据结构中函数的通常OO语法。

有关每个符号拥有的现有值类型列表,请参见http://reference.wolfram.com/mathematica/tutorial/PatternsAndTransformationRules.htmlhttp://www.verbeia.com/mathematica/tips/HTMLLinks/Tricks_Misc_4.html

例如,请尝试这个

x = NewObject[y -> 3];
function[x, 4]
x.function2[5]

如果你想模拟对象继承,可以使用一个名为InheritRules的包,可以在这里找到http://library.wolfram.com/infocenter/MathSource/671/

你也可以将函数定义存储在类型符号中,因此,如果NewObject返回type[newObject]而不是newObject,则可以在NewObject之外定义function和function2,并且与以前一样使用。

function[type[object_], x_] ^:= object["y"] + x;
type /: type[object_].function2[x_] := 2 object["y"]+x;

使用UpValues[type]可以查看函数和函数2在类型符号中的定义。
有关此最后一种语法的更多想法在这里介绍: https://mathematica.stackexchange.com/a/999/66
SelectEquivalents的改进版本
@rcollyer:非常感谢您将SelectEquivalents带到表面上,它是一个令人惊叹的函数。 这是SelectEquivalents的改进版本,具有更多可能性并使用选项,这使其更易于使用。
Options[SelectEquivalents] = 
   {
      TagElement->Identity,
      TransformElement->Identity,
      TransformResults->(#2&) (*#1=tag,#2 list of elements corresponding to tag*),
      MapLevel->1,
      TagPattern->_,
      FinalFunction->Identity
   };

SelectEquivalents[x_List,OptionsPattern[]] := 
   With[
      {
         tagElement=OptionValue@TagElement,
         transformElement=OptionValue@TransformElement,
         transformResults=OptionValue@TransformResults,
         mapLevel=OptionValue@MapLevel,
         tagPattern=OptionValue@TagPattern,
         finalFunction=OptionValue@FinalFunction
      }
      ,
      finalFunction[
         Reap[
            Map[
               Sow[
                  transformElement@#
                  ,
                  {tagElement@#}
               ]&
               , 
               x
               , 
               {mapLevel}
            ] 
            , 
            tagPattern
            , 
            transformResults
         ][[2]]
      ]
   ];

以下是此版本的使用示例:

正确使用Mathematica Gather/Collect

如何在Mathematica中执行PivotTable函数?

Mathematica快速二维分箱算法

Internal`Bag

Daniel Lichtblau在这里描述了一种有趣的内部数据结构,用于增长列表。

在Mathematica中实现Quadtree

调试函数

这两个帖子指向了有用的调试函数:

在编写小或大代码时如何进行调试?使用Mathematica Workbench? MMA调试器?还是其他什么?(ShowIt)

https://stackoverflow.com/questions/5459735/the-clearest-way-to-represent-mathematicas-evaluation-sequence/5527117#5527117 (TraceView)

这是另一个基于Reap和Sow的函数,用于从程序的不同部分提取表达式并将其存储在符号中。

SetAttributes[ReapTags,HoldFirst];
ReapTags[expr_]:=
   Module[{elements},
      Reap[expr,_,(elements[#1]=#2/.{x_}:>x)&];
      elements
   ];

这是一个例子

ftest[]:=((*some code*)Sow[1,"x"];(*some code*)Sow[2,"x"];(*some code*)Sow[3,"y"]);
s=ReapTags[ftest[]];
Keys[s]
s["x"]
PrintSymbol[s] (*Keys and PrintSymbol are defined above*)

其他资源

这里是一些有趣的学习链接:

Mathematica学习资源收集

更新在此处:https://mathematica.stackexchange.com/a/259/66


相关:"构建具有记忆功能的函数的最佳方法"。WReach在那里提供了一个惊人的简单函数示例,不仅可以记住其值,还可以将它们写入文件并在重新启动时向后读取。 - Alexey Popkov
1
相关:"Mathematica:如何清除符号的缓存,即取消模式无关的DownValues"。该问题展示了如何使用标准的f[x_]: = f[x] = some code记忆化来清除缓存。 - Simon
7
+1 有一个很好的符号约定,可以消除缓存函数中重复定义左侧的需要,例如:c:Cache[expr_] := c = expr - WReach
不错的SelectEquivalents变体。我认为,我会将TagOnElement保留为第二个参数,默认为Identity,因为它们中最常用的是这种情况。我认为我也不会包括FinalOp,因为它可以在OpOnTaggedElems中处理。我还会缩短选项名称,因为它们的长度使得输入变得很麻烦。尝试TagFunctionTransformElementTransformResultsTagPatternTagPatternMapLevel都是功能的很好的补充,整体来说是一个不错的重写。 - rcollyer
感谢您的评论rcollyer。我已经考虑到了它,并改善了代码的可读性。我保留FinalFunction,因为它对Reap的结果进行操作,例如如果您想按标签对最终结果进行排序,则可以保留它们。 - faysou

13

基于众多要求,使用SO API编写了生成排名前十的SO回答者图表的代码(不包括注解)。

enter image description here

getRepChanges[userID_Integer] :=
 Module[{totalChanges},
  totalChanges = 
   "total" /. 
    Import["http://api.stackoverflow.com/1.1/users/" <> 
      ToString[userID] <> "/reputation?fromdate=0&pagesize=10&page=1",
      "JSON"];
  Join @@ Table[
    "rep_changes" /. 
     Import["http://api.stackoverflow.com/1.1/users/" <> 
       ToString[userID] <> 
       "/reputation?fromdate=0&pagesize=10&page=" <> ToString[page], 
      "JSON"],
    {page, 1, Ceiling[totalChanges/10]}
    ]
  ]

topAnswerers = ({"display_name", 
      "user_id"} /. #) & /@ ("user" /. ("top_users" /. 
      Import["http://api.stackoverflow.com/1.1/tags/mathematica/top-\
answerers/all-time", "JSON"]))

repChangesTopUsers =
  Monitor[Table[
    repChange = 
     ReleaseHold[(Hold[{DateList[
              "on_date" + AbsoluteTime["January 1, 1970"]], 
             "positive_rep" - "negative_rep"}] /. #) & /@ 
        getRepChanges[userID]] // Sort;
    accRepChange = {repChange[[All, 1]], 
       Accumulate[repChange[[All, 2]]]}\[Transpose],
    {userID, topAnswerers[[All, 2]]}
    ], userID];

pl = DateListLogPlot[
  Tooltip @@@ 
   Take[({repChangesTopUsers, topAnswerers[[All, 1]]}\[Transpose]), 
    10], Joined -> True, Mesh -> None, ImageSize -> 1000, 
  PlotRange -> {All, {10, All}}, 
  BaseStyle -> {FontFamily -> "Arial-Bold", FontSize -> 16}, 
  DateTicksFormat -> {"MonthNameShort", " ", "Year"}, 
  GridLines -> {True, None}, 
  FrameLabel -> (Style[#, FontSize -> 18] & /@ {"Date", "Reputation", 
      "Top-10 answerers", ""})]

1
Brett发布了一个问题,几乎要求与此代码完全相同。也许将其放在那里是最合适的,稍微调整一下以适应该问题。与此问题相比,我认为它实际上值得被赞赏。 - rcollyer
@rcollyer是正确的。这是“社区维基”。 - Dr. belisarius
@belisarius 我只是把它复制到了Brett的问题答案中... - Sjoerd C. de Vries
@belisarius,实际上我希望你能承担起那个任务... ;-) - Sjoerd C. de Vries
API 1.1现在已经崩溃了 :( - masterxilo
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接