构造具有内存的函数的最佳方法

时间:2011-03-13 06:07:14

标签: wolfram-mathematica

美好的一天,

我有一些非常懒散和复杂的功能,比如f[x,y]。我需要构建详细的ContourPlot。此外,由于缺乏物理内存,函数f[x,y]有时会失败。在这种情况下,我必须自己停止评估并调查点{x,y}的问题情况。然后我应该将元素{x,y,f [x,y]}添加到f[x,y]的计算值列表(比如“缓存”)并重新开始ContourPlot的评估。 ContourPlot必须从缓存中获取f的所有已计算值。我宁愿将这样的列表存储在某个文件中,以便以后能够重用它。并且手动向该文件添加有问题的点可能更简单。

如果f的计算值列表可能包含10000-50000个点,那么实现此方法的最快方法是什么?

2 个答案:

答案 0 :(得分:29)

假设我们的慢函数具有签名f[x, y]

纯粹的记忆内方法

如果您对内存缓存感到满意,最简单的方法是使用memoization:

Clear@fmem
fmem[x_, y_] := fmem[x, y] = f[x, y]

每次使用之前没有见过的参数组合调用时,都会为自己添加一个定义。

文件支持的内存中方法

但是,如果在长时间计算期间内存不足或内核崩溃,则需要使用某种持久性来支持此缓存。最简单的方法是保留一个正在运行的日志文件:

$runningLogFile = "/some/directory/runningLog.txt";

Clear@flog
flog[x_, y_] := flog[x, y] = f[x, y] /.
  v_ :> (PutAppend[Unevaluated[flog[x, y] = v;], $runningLogFile]; v)

If[FileExistsQ[$runningLogFile]
, Get[$runningLogFile]
, Export[$runningLogFile, "", "Text"];
]

flogfmem相同,不同之处在于它还会在运行日志中写入一个条目,该条目可用于在以后的会话中恢复缓存的定义。最后一个表达式在找到现有日志文件时重新加载这些定义(或者如果文件不存在则创建该文件)。

当需要手动干预时,日志文件的文本性质很方便。请注意,浮点数的文本表示会引入不可避免的舍入错误,因此从日志文件重新加载值后,您可能会得到稍微不同的结果。如果这非常令人担忧,您可以考虑使用二进制DumpSave功能,尽管我会将该方法的详细信息留给读者,因为它不太容易保留增量日志。

SQL方法

如果内存非常紧张,并且您希望避免使用大型内存缓存为其他计算腾出空间,则之前的策略可能不合适。在这种情况下,您可以考虑使用Mathematica的内置SQL数据库完全在外部存储缓存:

fsql[x_, y_] :=
  loadCachedValue[x, y] /. $Failed :> saveCachedValue[x, y, f[x, y]]

我在下面定义loadCachedValuesaveCachedValue。基本思想是创建一个SQL表,其中每行包含xyf三元组。每次需要值时都会查询SQL表。请注意,此方法基本比内存缓存慢,因此当f的计算花费比SQL访问时间长得多时,它最有意义。 SQL方法不会受到影响文本日志文件方法的舍入错误的影响。

现在跟随loadCachedValuesaveCachedValue的定义,以及其他一些有用的辅助函数:

Needs["DatabaseLink`"]

$cacheFile = "/some/directory/cache.hsqldb";

openCacheConnection[] :=
  $cache = OpenSQLConnection[JDBC["HSQL(Standalone)", $cacheFile]]

closeCacheConnection[] :=
  CloseSQLConnection[$cache]

createCache[] :=
  SQLExecute[$cache,
    "CREATE TABLE cached_values (x float, y float, f float)
     ALTER TABLE cached_values ADD CONSTRAINT pk_cached_values PRIMARY KEY (x, y)"
  ]

saveCachedValue[x_, y_, value_] :=
  ( SQLExecute[$cache,
      "INSERT INTO cached_values (x, y, f) VALUES (?, ?, ?)", {x, y, value}
    ]
  ; value
  )

loadCachedValue[x_, y_] :=
  SQLExecute[$cache,
    "SELECT f FROM cached_values WHERE x = ? AND y = ?", {x, y}
  ] /. {{{v_}} :> v, {} :> $Failed}

replaceCachedValue[x_, y_, value_] :=
  SQLExecute[$cache,
    "UPDATE cached_values SET f = ? WHERE x = ? AND y = ?", {value, x, y}
  ]

clearCache[] :=
  SQLExecute[$cache,
    "DELETE FROM cached_values"
  ]

showCache[minX_, maxX_, minY_, maxY_] :=
  SQLExecute[$cache,
    "SELECT *
     FROM cached_values
     WHERE x BETWEEN ? AND ?
     AND y BETWEEN ? AND ?
     ORDER BY x, y"
  , {minX, maxX, minY, maxY}
  , "ShowColumnHeadings" -> True
  ] // TableForm

此SQL代码使用浮点值作为主键。这在SQL中通常是一个值得怀疑的做法,但在目前的情况下工作正常。

在尝试使用任何这些功能之前,您必须先致电openCacheConnection[]。完成后,您应该致电closeCacheConnection[]。仅限一次,您必须调用createCache[]来初始化SQL数据库。 replaceCachedValueclearCacheshowCache用于人工干预。

答案 1 :(得分:7)

执行此操作的最简单且可能最有效的方法是将缓存值设置为函数的特殊情况定义。由于散列,查找速度相当快。

功能:

In[1]:= f[x_, y_] := Cos[x] + Cos[y]

ContourPlot中使用了哪些点?

In[2]:= points = Last[
   Last[Reap[
     ContourPlot[f[x, y], {x, 0, 4 Pi}, {y, 0, 4 Pi}, 
      EvaluationMonitor :> Sow[{x, y}]]]]];

In[3]:= Length[points]

Out[3]= 10417

为10000个评估设置一个带有预先计算值的f版本:

In[4]:= Do[With[{x = First[p], y = Last[p]}, precomputedf[x, y] = f[x, y];], {p, 
   Take[points, 10000]}];

在上文中,您将使用precomputedf[x, y] = z而不是precomputed[x, y] = f[x, y]之类的内容,其中z是您存储在外部文件中的预先计算的值。

以下是仅考虑f:

的“else”情况
In[5]:= precomputedf[x_, y_] := f[x, y]

比较时间:

In[6]:= ContourPlot[f[x, y], {x, 0, 4 Pi}, {y, 0, 4 Pi}]; // Timing

Out[6]= {0.453539, Null}

In[7]:= ContourPlot[precomputedf[x, y], {x, 0, 4 Pi}, {y, 0, 4 Pi}]; // Timing

Out[7]= {0.440996, Null}

时间差别不大,因为在这个例子中f不是一个昂贵的功能。

针对您的特定应用的单独备注:也许您可以使用ListContourPlot代替。然后,您可以准确选择要评估的点。