wolfram mathematica - varias - La mejor manera de construir una función con memoria.
graficar funciones en wolfram mathematica (2)
Buen día,
Tengo una función muy lenta y complicada, digamos f[x,y]
. Y necesito construir ContourPlot
detallado de ello. Además, la función f[x,y]
veces falla debido a la falta de memoria física. En tales casos, tengo que detener la evaluación e investigar el caso problema del punto {x, y} por mí mismo. Luego debo agregar el elemento {x, y, f [x, y]} a una lista de valores computados de f[x,y]
(decir "caché") y reiniciar la evaluación de ContourPlot
. ContourPlot
debe tomar todos los valores ya calculados de f
de la caché. Preferiría almacenar dicha lista en algún archivo para poder reutilizarla más tarde. Y es probablemente más sencillo agregar puntos problemáticos a este archivo a mano.
¿Cuál es la forma más rápida de implementar esto si la lista de valores calculados de f
puede contener 10000-50000 puntos?
Asumamos que nuestra función lenta tiene la firma f[x, y]
.
Enfoque en memoria pura
Si está satisfecho con un caché en memoria, lo más simple sería usar memoization:
Clear@fmem
fmem[x_, y_] := fmem[x, y] = f[x, y]
Esto se agrega una definición a sí mismo cada vez que se llama con una combinación de argumentos que no ha visto antes.
Enfoque en memoria de archivos
Sin embargo, si se está quedando sin memoria o sufre bloqueos del kernel durante el cálculo prolongado, querrá realizar una copia de este caché con algún tipo de persistencia. Lo más simple sería mantener un archivo de registro en ejecución:
$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
es lo mismo que fmem
, excepto que también escribe una entrada en el registro en ejecución que puede usarse para restaurar la definición en caché en una sesión posterior. La última expresión vuelve a cargar esas definiciones cuando encuentra un archivo de registro existente (o crea el archivo si no existe).
La naturaleza textual del archivo de registro es conveniente cuando se requiere intervención manual. Tenga en cuenta que la representación textual de números de punto flotante introduce errores de redondeo inevitables, por lo que puede obtener resultados ligeramente diferentes después de volver a cargar los valores del archivo de registro. Si esto es motivo de gran preocupación, puede considerar el uso de la función binaria DumpSave
, aunque dejaré los detalles de ese enfoque al lector, ya que no es tan conveniente para mantener un registro incremental.
Enfoque SQL
Si la memoria es muy limitada y desea evitar tener un gran caché en la memoria para hacer espacio para los demás cálculos, la estrategia anterior podría no ser la adecuada. En ese caso, podría considerar el uso de la base de datos SQL incorporada de Mathematica para almacenar la memoria caché completamente de forma externa:
fsql[x_, y_] :=
loadCachedValue[x, y] /. $Failed :> saveCachedValue[x, y, f[x, y]]
loadCachedValue
y saveCachedValue
continuación. La idea básica es crear una tabla SQL donde cada fila contenga una x
, y
, f
triple. La tabla SQL se consulta cada vez que se necesita un valor. Tenga en cuenta que este enfoque es sustancialmente más lento que el caché en memoria, por lo que tiene más sentido cuando el cálculo de f
lleva mucho más tiempo que el tiempo de acceso a SQL. El enfoque de SQL no sufre los errores de redondeo que afectaron el enfoque del archivo de registro de texto.
Las definiciones de loadCachedValue
y saveCachedValue
ahora siguen, junto con algunas otras funciones de ayuda útiles:
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
Este código SQL usa valores de punto flotante como claves primarias. Esto suele ser una práctica cuestionable en SQL pero funciona bien en el contexto presente.
Debe llamar a openCacheConnection[]
antes de intentar usar cualquiera de estas funciones. Debe llamar a closeCacheConnection[]
después de que haya terminado. Solo una vez, debe llamar a createCache[]
para inicializar la base de datos SQL. replaceCachedValue
, clearCache
y showCache
se proporcionan para las intervenciones manuales.
La forma más sencilla y posiblemente más eficiente de hacer esto es simplemente configurar los valores almacenados en caché como definiciones de casos especiales para su función. La búsqueda es bastante rápida debido al hash.
Una función:
In[1]:= f[x_, y_] := Cos[x] + Cos[y]
¿Qué puntos se utilizan durante un 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
Configure una versión de f con valores precalculados para 10000 de las evaluaciones:
In[4]:= Do[With[{x = First[p], y = Last[p]}, precomputedf[x, y] = f[x, y];], {p,
Take[points, 10000]}];
En lo anterior, usaría algo como precomputedf[x, y] = z
lugar de precomputed[x, y] = f[x, y]
, donde z es su valor precomputado que ha almacenado en su archivo externo.
Aquí está el caso "else" que solo evalúa f:
In[5]:= precomputedf[x_, y_] := f[x, y]
Comparar horarios:
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}
No hay mucha diferencia en el tiempo porque en este ejemplo f no es una función costosa.
Un comentario por separado para su aplicación en particular: Tal vez podría usar ListContourPlot en ListContourPlot lugar. A continuación, puede elegir exactamente qué puntos se evalúan.