Quali sono i vantaggi e gli svantaggi inerenti all'utilizzo delle classi per incapsulare algoritmi numerici?


13

Molti algoritmi utilizzati nell'informatica scientifica hanno una struttura intrinseca diversa rispetto agli algoritmi comunemente considerati nelle forme meno ingegneristiche di ingegneria del software. In particolare, i singoli algoritmi matematici tendono ad essere altamente complessi, spesso coinvolgono centinaia o migliaia di righe di codice, tuttavia non coinvolgono alcuno stato (cioè non agiscono su una struttura di dati complessa) e possono spesso essere ridotti in termini programmatici interfaccia - a una singola funzione che agisce su un array (o due).

Ciò suggerisce che una funzione, e non una classe, è l'interfaccia naturale per la maggior parte degli algoritmi riscontrati nell'informatica scientifica. Tuttavia, questa argomentazione offre poche informazioni su come gestire l'implementazione di algoritmi complessi e multiparte.

Mentre l'approccio tradizionale è stato semplicemente quello di avere una funzione che chiama un numero di altre funzioni, passando gli argomenti pertinenti lungo la strada, OOP offre un approccio diverso, in cui gli algoritmi possono essere incapsulati come classi. Per chiarezza, incapsulando un algoritmo in una classe, intendo creare una classe in cui gli input dell'algoritmo vengono immessi nel costruttore della classe e quindi viene chiamato un metodo pubblico per richiamare effettivamente l'algoritmo. Una simile implementazione di multigrid in psuedocode C ++ potrebbe apparire come:

class multigrid {
    private:
        x_, b_
        [grid structure]

        restrict(...)
        interpolate(...)
        relax(...)
    public:
        multigrid(x,b) : x_(x), b_(b) { }
        run()
}

multigrid::run() {
     [call restrict, interpolate, relax, etc.]
}

La mia domanda è quindi la seguente: quali sono i vantaggi e gli svantaggi di questo tipo di pratica rispetto a un approccio più tradizionale senza lezioni? Ci sono problemi di estensibilità o manutenibilità? Per essere chiari, non ho intenzione di sollecitare l'opinione, ma piuttosto di comprendere meglio gli effetti a valle (cioè quelli che potrebbero non sorgere fino a quando una base di codice non diventerà abbastanza grande) dell'adozione di tale pratica di codifica.


2
È sempre un brutto segno quando il nome della tua classe è un aggettivo piuttosto che un sostantivo.
David Ketcheson,

3
Una classe potrebbe fungere da spazio dei nomi senza stato per l'organizzazione di funzioni al fine di gestire la complessità, ma esistono altri modi per gestire la complessità nei linguaggi che forniscono classi. (Mi vengono in mente gli
spazi dei

@GeoffOxberry Non posso dire se si tratta di un utilizzo buono o cattivo - motivo per cui lo sto chiedendo in primo luogo - ma le classi, a differenza di spazi dei nomi o moduli, possono anche gestire lo "stato temporaneo", ad esempio la gerarchia della griglia in multigrid, che viene scartato al completamento dell'algoritmo.
Ben

Risposte:


13

Dopo aver eseguito software numerico per 15 anni, posso affermare in modo inequivocabile quanto segue:

  • L'incapsulamento è importante. Non si desidera passare puntatori ai dati (come si suggerisce) poiché espone lo schema di archiviazione dei dati. Se si espone lo schema di archiviazione, non è possibile modificarlo nuovamente poiché si accederà ai dati in tutto l'intero programma. L'unico modo per evitarlo è incapsulare i dati in variabili membro private di una classe e lasciare agire solo le funzioni membro. Se leggo la tua domanda, pensi a una funzione che calcola gli autovalori di una matrice come apolidi, prendendo un puntatore alle voci della matrice come argomento e restituendo gli autovalori in qualche modo. Penso che questo sia il modo sbagliato di pensarci. A mio avviso, questa funzione dovrebbe essere una funzione membro "const" di una classe, non perché cambia la matrice, ma perché è una che opera con i dati.

  • La maggior parte dei linguaggi di programmazione OO consente di avere funzioni di membri privati. Questo è il tuo modo di suddividere un algoritmo di grandi dimensioni in uno più piccolo. Ad esempio, le varie funzioni di supporto necessarie per il calcolo degli autovalori funzionano ancora sulla matrice, e quindi sarebbero naturalmente funzioni di membri privati ​​di una classe di matrici.

  • Rispetto a molti altri sistemi software, può essere vero che le gerarchie di classi sono spesso meno importanti di, per esempio, nelle interfacce utente grafiche. Esistono certamente posti nel software numerico in cui sono importanti: Jed delinea l'una nell'altra risposta a questo thread, vale a dire i molti modi in cui uno può rappresentare una matrice (o, più in generale, un operatore lineare su uno spazio vettoriale di dimensioni finite). PETSc lo fa in modo molto coerente, con funzioni virtuali per tutte le operazioni che agiscono su matrici (non lo chiamano "funzioni virtuali", ma è così). Esistono altre aree in codici a elementi finiti tipici in cui si utilizza questo principio di progettazione del software OO. Quelli che vengono in mente sono i molti tipi di formule di quadratura e i molti tipi di elementi finiti, tutti i quali sono naturalmente rappresentati come un'unica interfaccia / molte implementazioni. Anche le descrizioni delle leggi materiali rientrerebbero in questo gruppo. Ma può essere vero che questo è tutto e che il resto di un codice ad elementi finiti non usa l'ereditarietà pervasivamente come si potrebbe usare, diciamo, nelle GUI.

Da solo questi tre punti, dovrebbe essere chiaro che la programmazione orientata agli oggetti è sicuramente applicabile anche ai codici numerici e che sarebbe sciocco ignorare i numerosi vantaggi di questo stile. Può essere vero che BLAS / LAPACK non usano questo paradigma (e che neanche la solita interfaccia esposta da MATLAB) ma mi azzarderei a pensare che ogni software numerico di successo scritto negli ultimi 10 anni sia, in effetti, orientato agli oggetti.


16

L'incapsulamento e il nascondimento dei dati sono estremamente importanti per le biblioteche estensibili nell'informatica scientifica. Considera matrici e solutori lineari come due esempi. Un utente deve solo sapere che un operatore è lineare, ma può avere una struttura interna come la scarsità, un kernel, una rappresentazione gerarchica, un prodotto tensore o un complemento di Schur. In tutti i casi, i metodi di Krylov non dipendono dai dettagli dell'operatore, ma dipendono solo dall'azione della MatMultfunzione (e forse dalla sua aggiunta). Allo stesso modo, l'utente di un'interfaccia del risolutore lineare (ad esempio un risolutore non lineare) si preoccupa solo che il problema lineare sia risolto e non dovrebbe essere necessario o non desidera specificare l'algoritmo utilizzato. In effetti, specificare tali cose impedirebbe la capacità del risolutore non lineare (o altra interfaccia esterna).

Le interfacce sono buone. A seconda di un'implementazione è male. Che si ottenga questo risultato utilizzando classi C ++, oggetti C, caratteri tipografici Haskell o altre funzionalità del linguaggio è irrilevante. La capacità, la solidità e l'estensibilità di un'interfaccia è ciò che conta nelle biblioteche scientifiche.


8

Le classi dovrebbero essere utilizzate solo se la struttura del codice è gerarchica. Dato che stai citando Algorithms, la loro struttura naturale è un diagramma di flusso, non una gerarchia di oggetti.

Nel caso di OpenFOAM, la parte algoritmica è implementata in termini di operatori generici (div, grad, curl, ecc.) Che sono sostanzialmente funzioni astratte che operano su diversi tipi di tensori, utilizzando diversi tipi di schemi numerici. Questa parte del codice è sostanzialmente costruita da molti algoritmi generici che operano su classi. Ciò consente al client di scrivere qualcosa del tipo:

solve(ddt(U) + div(phi, U)  == rho*g + ...);

Gerarchie come modelli di trasporto, modelli di turbolenza, schemi di differenziazione, schemi di gradiente, condizioni al contorno, ecc. Sono implementate in termini di classi C ++ (di nuovo generiche sulle quantità del tensore).

Ho notato una struttura simile nella libreria CGAL, in cui i vari algoritmi sono raggruppati insieme come gruppi di oggetti funzione raggruppati con informazioni geometriche per formare kernel geometrici (classi), ma questo viene fatto di nuovo per separare le operazioni dalla geometria (rimozione del punto da una faccia, da un tipo di dati punto).

Struttura gerarchica ==> classi

Algoritmi procedurali, diagramma di flusso ==>


5

Anche se questa è una vecchia domanda, penso che valga la pena menzionare la particolare soluzione di Julia . Ciò che questo linguaggio fa è "OOP senza classe": i costrutti principali sono tipi, ovvero oggetti di dati compositi affini a structs in C, su cui viene definita una relazione di ereditarietà. I tipi non hanno "funzioni membro", ma ogni funzione ha una firma del tipo e accetta i sottotipi. Per esempio, si potrebbe avere un abstract Matrixtipo e sottotipi DenseMatrix, SparseMatrixe hanno un metodo generico do_something(a::Matrix, b::Matrix)con specializzazione do_something(a::SparseMatrix, b::SparseMatrix). Il dispacciamento multiplo viene utilizzato per selezionare la versione più appropriata da chiamare.

Questo approccio è più potente di OOP basato sulla classe, che equivale a dispacciamento basato sull'ereditarietà solo sul primo argomento, se si adotta la convenzione che "un metodo è una funzione con thiscome primo parametro" (comune, ad esempio in Python). Qualche forma di invio multiplo può essere emulata, diciamo, in C ++, ma con contorsioni considerevoli .

La distinzione principale è che i metodi non appartengono alle classi, ma esistono come entità separate e l'ereditarietà può avvenire su tutti i parametri.

Alcuni riferimenti:

http://docs.julialang.org/en/release-0.4/manual/methods/

http://assoc.tumblr.com/post/71454527084/cool-things-you-can-do-in-julia

https://thenewphalls.wordpress.com/2014/03/06/understanding-object-oriented-programming-in-julia-inheritance-part-2/


1

Due vantaggi dell'approccio OO potrebbero essere:

  • βαcalculate_alpha()αcalculate_beta()calculate_alpha()α

  • calculate_f()f(X,y,z)zset_z()zcalculate_f()z

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.