• Non ci sono risultati.

3.2 Descrizione delle classi

3.2.4 La classe BacktrackingOptimizer

La classe BacktrackingOptimizer implementa un minimizzatore che utilizza il metodo del gradiente con backtracking (si veda l’algoritmo 4 per

Figura 3.4: Diagramma UML delle relazioni tra le classi che implementano l’ottimizzatore con backtracking

i dettagli). Come si vede dal diagramma UML riportato in figura 3.4, es- sa deriva dalla classe astratta AbstractOptimizer che definisce l’interfaccia comune a tutti i possibili ottimizzatori. Tale classe contiene solamente il me- todo apply() (metodo pure virtual che dovrà essere ridefinito dalle classi de- rivate che implementano ottimizzatori concreti) e la variabile parameters_, che contiene l’insieme delle impostazioni che possono essere settate dall’u- tente. L’essenza della classe è tutta nel metodo apply(), la cui firma è:

1 virtual void apply

2 (DCP::CompositeDifferentialProblem& problem,

3 const DCP::AbstractObjectiveFunctional& objectiveFunctional,

4 dolfin::Function& initialGuess,

5 const std::function

6 <

7 void (DCP::CompositeDifferentialProblem&,

8 const dolfin::GenericFunction&)

9 >& updater,

10 const std::function

11 <

12 void (dolfin::Function&, const dolfin::Function&) 13 >& searchDirectionComputer);

Data l’importanza di tale funzione, analizziamo i parametri in ingresso uno ad uno:

problem definisce l’equazione di stato e l’equazione aggiunta (con possibili

interdipendenze tra coefficienti e soluzioni delle due, come visto nella sezione 3.2.1) del problema di controllo ottimo in esame

objectiveFunctional definisce il funzionale obiettivo del problema di con-

trollo ottimo considerato. Si noti che spesso tale funzionale obiet- tivo dipende dalla soluzione del problema primale o del problema aggiunto. Per implementare questa dipendenza, si può usare una VariableExpression, come visto nella sezione 3.2.3.

initialGuess è il valore iniziale della variabile di controllo nell’algoritmo

iterativo di minimizzazione. All’uscita dalla funzione conterrà il valore finale del controllo, ossia il punto di minimo approssimato calcolato dall’algoritmo.

updater è la variabile che si occupa di aggiornare il valore della funzione

di controllo all’interno di problem. All’interno del metodo apply() infatti, nel loop di minimizzazione, la variabile di controllo (conte- nuta in initialGuess) cambia valore ad ogni iterazione. È quin- di necessario che il coefficiente di controllo all’interno della variabile problem (sia esso il valore della forzante o di una condizione al bor- do) cambi per rispecchiare il cambiamento avvenuto sulla variabile initialGuess. Si noti che la variabile updater è definita tramite un function wrapper : questo significa che alla funzione apply() si può passare come updater un qualunque callable object (funzioni, funto- ri, lambda expressions). Come è facilmente intuibile dai tipi forniti, le due variabili in ingresso alla variabile updater saranno rispetti- vamente il problema da aggiornare e il nuovo valore della variabi- le di controllo. Alcuni tipi di updaters sono forniti con la libreria DCP e si possono trovare nella stessa directory in cui si trova il file BacktrackingOptimizer.cpp. Essi sono definiti come funtori, ossia classi che implementano il call operator (cioè il metodo operator()()) e possono essere usati dall’utente nel caso in cui siano adatti al pro- blema di controllo che si sta implementando. Le classi che conten- gono tali updaters sono chiamate DirichletControlValueUpdater, DistributedControlValueUpdater e NeumannControlValueUpdater. È però possibile definire un proprio callable object da passare alla fun- zione apply() come updater. La presenza di tale funzionalità è in alcuni casi ridondante, ma si è scelto di utilizzare questo approccio per la generalità che consente.

searchDirectionComputer è la variabile che si occupa di calcolare la di- rezione di ricerca ad ogni iterazione. Come per la variabile updater, anche searchDirectionComputer è un function wrapper, che pren- de in ingresso due dolfin::Function: la prima conterrà, all’uscita dalla funzione, la direzione di ricerca all’iterazione corrente; la se- conda contiene invece il gradiente del funzionale all’iterazione corren- te, necessario a calcolare la direzione di ricerca. Nell’overriding del metodo apply() della classe BacktrackingOptimizer, la variabile

searchDirectionComputer ha valore di default pari al metodo sta- tico BacktrackingOptimizer::gradientSearchDirection(), conte- nuto nella classe stessa, che calcola la direzione di discesa attraverso la formula

dk= −∇J (gk) .

In alcuni casi, come visto in sezione 2.3.4, questo non è sufficiente e sarà necessario definire una funzione di calcolo della direzione di discesa da passare in ingresso al metodo apply().

È interessante notare anche il ruolo della variabile dotProductComputer_ appartenente alla classe BacktrackingOptimizer. Tale variabile contiene un puntatore a una dolfin::Form che implementa il calcolo del prodotto scalare tra due oggetti dello spazio a cui appartiene la variabile di controllo. Si noti infatti che nel metodo apply() è necessario calcolare prodotti sca- lari e norme (che sono prodotti scalari di una quantità per se stessa). Ad esempio, è richiesto il calcolo della norma del gradiente e dell’incremento del controllo (per il test di convergenza) e il calcolo del prodotto scalare tra gradiente e direzione di discesa (per il test di verità della condizione di sufficient decrease). Tutte le variabili di cui è necessario calcolare il prodotto scalare appartengono però allo stesso spazio funzionale! È per- tanto sufficiente definire una sola dolfin::Form e cambiarne i coefficienti per calcolare tutti i prodotti scalari necessari. La funzione apply() può contare su un certo numero di forme disponibili per il calcolo del prodotto scalare, definite nel file dotproduct.h (compilato attraverso FFC a partire dal file dotproduct.ufl), ma esse non possono ovviamente esaurire tut- ti i casi possibili. Nel caso in cui sia necessario utilizzare una definizione di prodotto scalare diversa da quelle disponibili, basterà implementarla in un file .ufl, compilare tale file con FFC, definire un oggetto del tipo im- plementato nel file .h risultante dalla compilazione e chiamare il metodo setDotProductComputer(), passando un puntatore a tale oggetto come ar- gomento in ingresso, per far sì che il metodo apply() utilizzi il prodotto scalare definito dall’utente.

Risultati numerici

Presentiamo ora alcuni risultati numerici per il problema di controllo esposto in sezione 2.3. Tali risultati sono stati ottenuti utilizzando la libreria DCP presentata nel capitolo 3.

Come già detto, in questo capitolo non arriveremo ad applicare il pro- blema di controllo in esame al caso che ha dato il via a questo lavoro di tesi. Tratteremo invece un caso semplificato, che contiene comunque tutte le principali criticità legate a questo tipo di problema di controllo e che potrà poi essere usato come punto di partenza in lavori successivi per provare a trovare una soluzione nel caso del problema esposto nel capitolo 1.

4.1

Il caso 2D

Trattiamo in questa prima sezione un semplice caso bidimensionale in modo da poter studiare nel dettaglio il ruolo dei diversi parametri del pro- blema di controllo. I tempi di calcolo necessari per la soluzione delle diverse istanze del problema stesso risultano infatti essere piuttosto ridotti in questo caso.

Documenti correlati