Come posso scrivere un codice agnostico dimensionalmente?


19

Mi ritrovo spesso a scrivere un codice molto simile per le versioni uno, due e tridimensionali di una determinata operazione / algoritmo. Mantenere tutte queste versioni può diventare noioso. La semplice generazione di codice funziona abbastanza bene, ma sembra che ci debba essere un modo migliore.

Esiste un modo relativamente semplice per scrivere un'operazione una volta e farla generalizzare a dimensioni superiori o inferiori?

Un esempio concreto è: supponiamo che abbia bisogno di calcolare il gradiente di un campo di velocità nello spazio spettrale. In tre dimensioni, i loop di Fortran sarebbero simili:

do k = 1, n
  do j = 1, n
    do i = 1, n
      phi(i,j,k) = ddx(i)*u(i,j,k) + ddx(j)*v(i,j,k) + ddx(k)*w(i,j,k)
    end do
  end do
end do

dove l' ddxarray è definito in modo appropriato. (Si potrebbe anche fare questo con moltiplicazioni di matrici.) Il codice per un flusso bidimensionale è quasi esattamente lo stesso, tranne: la terza dimensione viene eliminata dai loop, dagli indici e dal numero di componenti. C'è un modo migliore per esprimere questo?

Un altro esempio è: supponiamo che io abbia velocità fluide definite in senso puntuale su una griglia tridimensionale. Per interpolare la velocità in una posizione arbitraria (cioè, non corrispondente ai punti della griglia), è possibile utilizzare l' algoritmo Neville monodimensionale in successione su tutte e tre le dimensioni (cioè riduzione dimensionale). Esiste un modo semplice per eseguire la riduzione dimensionale data l'implementazione monodimensionale di un semplice algoritmo?

Risposte:


13

Osserva come lo fa deal.II ( http://www.dealii.org/ ) - lì, l'indipendenza della dimensione è al centro della libreria ed è modellata come argomento modello per la maggior parte dei tipi di dati. Vedi, ad esempio, il solutore Laplace indipendente dalla dimensione nel programma tutorial step-4:

http://www.dealii.org/developer/doxygen/deal.II/step_4.html

Guarda anche

https://github.com/dealii/dealii/wiki/Frequently-Asked-Questions#why-use-templates-for-the-space-dimension


Sono fortemente d'accordo. Non ho trovato un approccio migliore di quello che Deal.II sta facendo. Usano i modelli in un modo molto interessante per aggirare questo problema.
Eldila,

1
Una buona risorsa, ma piuttosto intimidatoria se non si creano modelli C ++.
meawoppl

@Wolfgang Bangerth: deal.ii definisce anche gli iteratori usando i template?
Matthew Emmett,

@MatthewEmmett: Sì.
Wolfgang Bangerth,

@meawoppl: In realtà no. Insegno regolarmente a lezioni in trattativa.II, e all'inizio dico semplicemente agli studenti che tutto ciò che dice ClassWhatever <2> è in 2d, ClassWhatever <3> è in 3d e ClassWhatever <dim> è in dim-d. Io porto la lezione sui modelli da qualche parte in settimana 3, e mentre è probabile che gli studenti non capiscono come funziona prima di questo, sono perfettamente funzionante utilizzando comunque.
Wolfgang Bangerth,

12

La domanda evidenzia che la maggior parte dei linguaggi di programmazione "semplici" (C, Fortran, almeno) non ti consentono di farlo in modo pulito. Un ulteriore vincolo è che si desidera convenienza notazionale e buone prestazioni.

Pertanto, anziché scrivere un codice specifico per dimensione, prendere in considerazione la possibilità di scrivere un codice che generi un codice specifico per dimensione. Questo generatore è indipendente dalla dimensione, anche se il codice di calcolo non lo è. In altre parole, aggiungi uno strato di ragionamento tra la tua notazione e il codice che esprime il calcolo. I modelli C ++ equivalgono alla stessa cosa: al contrario, sono integrati nel linguaggio. Unico inconveniente, sono piuttosto ingombranti da scrivere. Ciò riduce la domanda su come realizzare praticamente il generatore di codice.

OpenCL ti consente di generare codice in modo abbastanza pulito durante l'esecuzione. Inoltre, crea una divisione molto chiara tra "programma di controllo esterno" e "circuiti interni / kernel". Il programma di generazione esterno è molto meno vincolato dalle prestazioni e quindi potrebbe anche essere scritto in un linguaggio comodo, come Python. Questa è la mia speranza per come PyOpenCL si abituerà - scusate la rinnovata spina spudorata.


Andreas! Benvenuto in scicomp! Sono contento di averti sul sito, penso che tu sappia come contattarmi se hai qualche domanda.
Aron Ahmadia,

2
+10000 per la generazione automatica di codice come soluzione a questo problema invece della magia C ++.
Jeff,

9

Questo può essere realizzato in qualsiasi lingua con il seguente prototipo mentale approssimativo:

  1. Crea un elenco delle estensioni di ogni dimensione (qualcosa come forma () in MATLAB credo)
  2. Crea un elenco della posizione corrente in ogni dimensione.
  3. Scrivi un ciclo su ogni dimensione, contenente un ciclo su cui cambia la dimensione in base al ciclo esterno.

Da lì, si tratta di combattere la sintassi della tua lingua per mantenere il codice conforme.

Avendo scritto un risolutore di fluidodinamica n-dimensionale , ho scoperto che è utile avere un linguaggio che supporti la decompressione di un elenco come un oggetto come argomento di una funzione. Vale a dire a = (1,2,3) f (a *) -> f (1,2,3). Inoltre iteratori avanzati (come ndenumerate in numpy) rendono il codice un ordine di grandezza più pulito.


La sintassi di Python per farlo sembra carina e concisa. Mi chiedo se c'è un modo carino per farlo con Fortran ...
Matthew Emmett il

1
È un po 'doloroso gestire la memoria dinamica in Fortran. Probabilmente la mia lamentela maggiore con la lingua.
meawoppl

5

È possibile scrivere un algoritmo per una griglia e quindi specializzarsi su dimensioni inferiori impostando .n j = 1n1×n2×n3nj=1


Quindi, per essere indipendente dalla dimensione, il tuo codice deve essere scritto per dimensioni maxdim + 1, dove maxdim è la dimensione massima possibile che l'utente possa mai incontrare. Diciamo maxdim = 100. Quanto è utile il codice risultante?
Jeff,

4

Le risposte chiare se si desidera mantenere la velocità di Fortran sono l'uso di un linguaggio che abbia una corretta generazione di codice come Julia o C ++. I modelli C ++ sono già stati menzionati, quindi citerò gli strumenti di Julia qui. Le funzioni generate da Julia ti consentono di utilizzare la sua metaprogrammazione per creare funzioni su richiesta tramite informazioni sul tipo. Quindi essenzialmente quello che puoi fare qui è fare

@generated function f(x)
   N = ndims(x)
   quote
     # build the code for the function
   end
end

e poi usi il Nper costruire programmaticamente il codice che vorresti eseguire dato che è Ndimensionale. Quindi la libreria cartesiana di Julia o pacchetti come le espressioni Einsum.jl possono essere facilmente creati per la Nfunzione dimensionale.

La cosa bella di Julia qui è che questa funzione è staticamente compilata e ottimizzata per ogni nuovo array dimensionale che usi, quindi non si compila più del necessario ma ti darà la velocità C / Fortran. Alla fine questo è simile all'utilizzo dei modelli C ++, ma è un linguaggio di livello superiore con molti strumenti per renderlo più semplice (abbastanza facile che questo sarebbe un bel problema di compiti a casa per uno studente).

Un'altra lingua che è buona per questo è un Lisp come Common Lisp. È facile da usare poiché come Julia ti dà l'AST compilato con molti strumenti di introspezione integrati, ma a differenza di Julia non lo compilerà automaticamente (nella maggior parte delle distribuzioni).


1

Sono nella stessa barca (Fortran). Una volta che ho i miei elementi 1D, 2D, 3D e 4D (faccio geometria proiettiva) creo gli stessi operatori per ogni tipo e quindi scrivo la mia logica con equazioni di alto livello che chiariscono cosa sta succedendo. Non è così lento come si potrebbe pensare di avere loop separati per ogni operazione e molta copia di memoria. Ho lasciato che il compilatore / processore eseguisse le ottimizzazioni.

Per esempio

interface operator (.x.)
    module procedure cross_product_1x2
    module procedure cross_product_2x1
    module procedure cross_product_2x2
    module procedure cross_product_3x3
end interface 

subroutine cross_product_1x2(a,b,c)
    real(dp), intent(in) :: a(1), b(2)
    real(dp), intent(out) :: c(2)

    c = [ -a(1)*b(2), a(1)*b(1) ]
end subroutine

subroutine cross_product_2x1(a,b,c)
    real(dp), intent(in) :: a(2), b(1)
    real(dp), intent(out) :: c(2)

    c = [ a(2)*b(1), -a(1)*b(1) ]
end subroutine

subroutine cross_product_2x2(a,b,c)
    real(dp), intent(in) :: a(2), b(2)
    real(dp), intent(out) :: c(1)

    c = [ a(1)*b(2)-a(2)*b(1) ]
end subroutine

subroutine cross_product_3x3(a,b,c)
    real(dp), intent(in) :: a(3), b(3)
    real(dp), intent(out) :: c(3)

    c = [a(2)*b(3)-a(3)*b(2), a(3)*b(1)-a(1)*b(3), a(1)*b(2)-a(2)*b(1)]
end subroutine

Da usare in equazioni come

m = e .x. (r .x. g)  ! m = e×(r×g)

dove ee re gpuò avere qualsiasi dimensionalità che abbia un senso matematico.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.