Il modo migliore per build una funzione con la memoria

Buona giornata,

Ho alcune funzioni molto slooooow e complicate, ad esempio f[x,y] . E ho bisogno di build ContourPlot dettagliato di esso. Inoltre la funzione f[x,y] volte fallisce a causa della mancanza di memoria fisica. In questi casi devo interrompere la valutazione e indagare il caso problematico del punto {x, y} da solo. Quindi dovrei aggiungere l’elemento {x, y, f [x, y]} a una lista di valori calcolati di f[x,y] (ad esempio “cache”) e riavviare la valutazione di ContourPlot . ContourPlot deve prendere tutti i valori già calcolati di f dalla cache. Preferirei archiviare tale elenco in alcuni file per avere la possibilità di riutilizzarlo in seguito. Ed è probabilmente più semplice aggiungere punti problematici a questo file a mano.

Qual è il modo più veloce per implementare questo se l’elenco dei valori calcolati di f può contenere 10000-50000 punti?

Supponiamo che la nostra funzione lenta abbia la firma f[x, y] .

Approccio puro in memoria

Se sei soddisfatto di una cache in memoria, la cosa più semplice da fare sarebbe utilizzare la memoizzazione:

 [email protected] fmem[x_, y_] := fmem[x, y] = f[x, y] 

Questo aggiunge una definizione a se stesso ogni volta che viene chiamato con una combinazione di argomenti che non ha mai visto prima.

Approccio in memoria basato su file

Tuttavia, se si sta esaurendo la memoria o si verificano dei crash del kernel durante il calcolo a lungo termine, si vorrà eseguire il backup di questa cache con una certa persistenza. La cosa più semplice sarebbe mantenere un file di registro in esecuzione:

 $runningLogFile = "/some/directory/runningLog.txt"; [email protected] 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 è lo stesso di fmem , tranne che scrive anche una voce nel log in esecuzione che può essere utilizzata per ripristinare la definizione della cache in una sessione successiva. L’ultima espressione ricarica quelle definizioni quando trova un file di registro esistente (o crea il file se non esiste).

La natura testuale del file di registro è utile quando è richiesto l’intervento manuale. Tieni presente che la rappresentazione testuale dei numeri in virgola mobile introduce inevitabili errori di arrotondamento, pertanto potresti ottenere risultati leggermente diversi dopo aver ricaricato i valori dal file di registro. Se ciò è di grande interesse, potresti prendere in considerazione l’uso della funzione di DumpSave binario, anche se lascerò i dettagli di tale approccio al lettore poiché non è altrettanto conveniente per mantenere un registro incrementale.

Approccio SQL

Se la memoria è davvero stretta e si desidera evitare di avere una grande cache in memoria per fare spazio agli altri calcoli, la strategia precedente potrebbe non essere appropriata. In tal caso, potresti prendere in considerazione l’utilizzo del database SQL incorporato di Mathematica per archiviare la cache completamente esternamente:

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

Definisco loadCachedValue e saveCachedValue seguito. L’idea di base è creare una tabella SQL in cui ogni riga contiene una x , y , f tripla. La tabella SQL viene interrogata ogni volta che è necessario un valore. Si noti che questo approccio è notevolmente più lento della cache in memoria, quindi ha più senso quando il calcolo di f richiede molto più tempo rispetto al tempo di accesso SQL. L’approccio SQL non risente degli errori di arrotondamento che affliggono l’approccio del file di registro del testo.

saveCachedValue ora le definizioni di loadCachedValue e saveCachedValue , insieme ad altre utili funzioni di aiuto:

 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 

Questo codice SQL utilizza valori a virgola mobile come chiavi primarie. Questa è normalmente una pratica discutibile in SQL ma funziona bene nel contesto attuale.

È necessario chiamare openCacheConnection[] prima di tentare di utilizzare una di queste funzioni. Dovresti chiamare closeCacheConnection[] dopo aver finito. Una volta sola, è necessario chiamare createCache[] per inizializzare il database SQL. replaceCachedValue , clearCache e showCache sono forniti per gli interventi manuali.

Il modo più semplice ed efficace per farlo è semplicemente impostare i valori memorizzati nella cache come definizioni di casi speciali per la tua funzione. La ricerca è abbastanza veloce a causa dell’hashing.

Una funzione:

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

Quali punti sono usati 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 

Impostare una versione di f con valori precalcolati per 10000 delle valutazioni:

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

In quanto sopra, dovresti usare qualcosa come precomputedf[x, y] = z invece di precomputed[x, y] = f[x, y] , dove z è il valore precalcolato che hai memorizzato nel tuo file esterno.

Ecco il caso “else” che valuta solo f:

 In[5]:= precomputedf[x_, y_] := f[x, y] 

Confronta i tempi:

 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} 

Non c’è molta differenza nei tempi perché in questo esempio f non è una funzione costosa.

Un’osservazione separata per la tua applicazione specifica: potresti invece utilizzare ListContourPlot . Quindi puoi scegliere esattamente quali punti sono valutati.