Лучший способ построить функцию с памятью
Добрый день,
У меня есть очень запутанная и сложная функция, скажем, f[x,y]
, И мне нужно построить подробно ContourPlot
этого Кроме того, функция f[x,y]
иногда происходит сбой из-за недостатка физической памяти. В таких случаях я должен сам прекратить оценку и исследовать проблемный случай точки {x,y}. Тогда я должен добавить элемент {x,y,f[x,y]} в список вычисленных значений f[x,y]
(скажем "кэш") и перезапустите оценку ContourPlot
, ContourPlot
должны принять все уже вычисленные значения f
из кеша. Я бы предпочел хранить такой список в каком-то файле, чтобы иметь возможность использовать его позже. И, возможно, проще добавить проблемные точки в этот файл вручную.
Какой самый быстрый способ реализовать это, если список вычисленных значений f
может содержать 10000-50000 баллов?
2 ответа
Давайте предположим, что наша медленная функция имеет подпись f[x, y]
,
Чистый подход в памяти
Если вас устраивает кэш в памяти, самое простое, что вы можете сделать, это использовать памятку:
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"];
]
flog
такой же как fmem
за исключением того, что он также записывает запись в текущий журнал, который можно использовать для восстановления кэшированного определения в более позднем сеансе. Последнее выражение перезагружает эти определения, когда находит существующий файл журнала (или создает файл, если он не существует).
Текстовый характер файла журнала удобен, когда требуется ручное вмешательство. Помните, что текстовое представление чисел с плавающей запятой приводит к неизбежным ошибкам округления, поэтому вы можете получить немного другие результаты после перезагрузки значений из файла журнала. Если это вызывает серьезную озабоченность, вы можете рассмотреть возможность использования бинарного DumpSave
Хотя я оставлю подробности этого подхода читателю, поскольку он не так удобен для ведения инкрементного журнала.
Подход SQL
Если памяти очень мало, и вы хотите избежать большого кеша в памяти, чтобы освободить место для других вычислений, предыдущая стратегия может быть неуместной. В этом случае вы можете рассмотреть возможность использования встроенной базы данных Mathematica для хранения кеша полностью снаружи:
fsql[x_, y_] :=
loadCachedValue[x, y] /. $Failed :> saveCachedValue[x, y, f[x, y]]
Я определяю loadCachedValue
а также saveCachedValue
ниже. Основная идея заключается в создании таблицы SQL, в которой каждая строка содержит x
, y
, f
тройная. Таблица SQL запрашивается каждый раз, когда требуется значение. Обратите внимание, что этот подход значительно медленнее, чем в кэш-памяти в памяти, поэтому он имеет смысл, когда вычисление f
занимает гораздо больше времени, чем время доступа SQL. Подход SQL не страдает от ошибок округления, которые мешали подходу файла текстового журнала.
Определения loadCachedValue
а также saveCachedValue
Теперь следуйте вместе с некоторыми другими полезными вспомогательными функциями:
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. replaceCachedValue
, clearCache
а также showCache
предоставляются для ручного вмешательства.
Самый простой и, возможно, самый эффективный способ сделать это - просто установить кэшированные значения в качестве специальных определений для вашей функции. Поиск довольно быстрый из-за хеширования.
Функция:
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
Установите версию f с предварительно вычисленными значениями для 10000 оценок:
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 - ваше предварительно вычисленное значение, которое вы сохранили во внешнем файле.
Вот случай "else", который просто оценивает f:
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. Затем вы можете выбрать, какие именно баллы оцениваются.