Лучший способ построить функцию с памятью - PullRequest
19 голосов
/ 13 марта 2011

Добрый день,

У меня очень запутанная и сложная функция, скажем, f[x,y].И мне нужно построить подробный ContourPlot этого.Кроме того, функция f[x,y] иногда дает сбой из-за недостатка физической памяти.В таких случаях я должен сам прекратить оценку и исследовать проблемный случай точки {x, y}.Затем я должен добавить элемент {x, y, f [x, y]} в список вычисленных значений f[x,y] (скажем, «кеш») и перезапустить оценку ContourPlot.ContourPlot должен взять все уже вычисленные значения f из кэша.Я бы предпочел хранить такой список в каком-то файле, чтобы иметь возможность использовать его позже.И, вероятно, проще добавить проблемные точки в этот файл вручную.

Какой самый быстрый способ реализовать это, если список вычисленных значений f может содержать 10000-50000 точек?

Ответы [ 2 ]

29 голосов
/ 13 марта 2011

Давайте предположим, что наша медленная функция имеет сигнатуру 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 предназначены для ручного вмешательства.

7 голосов
/ 13 марта 2011

Самый простой и, возможно, самый эффективный способ сделать это - просто установить кэшированные значения в качестве специальных определений для вашей функции.Поиск довольно быстрый из-за хэширования.

Функция:

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 .Затем вы можете выбрать, какие именно баллы оцениваются.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...