Offline Phase: SGREEDY algorithm for sensor placement

Aim of the tutorial: this notebook shows how to use the SGREEDY algorithm for sensor placement starting from a reduced basis, e.g., the POD modes.


To execute this notebook it is necessary to have the POD modes stored in Offline_results/BasisFunctions folder, placed in this directory (otherwise modify path_off variable).

[1]:
import numpy as np
import os
from IPython.display import clear_output
import pickle

from dolfinx.fem import FunctionSpace

from pyforce.tools.write_read import ImportH5, StoreFunctionsList
from pyforce.tools.functions_list import FunctionsList

path_off ='./Offline_results/'

The geometry is imported from “ANL11A2_octave.geo”, generated with GMSH. Then, the mesh is created with the gmsh module.

[2]:
from neutronics import create_anl11a2_mesh

domain, _, _ = create_anl11a2_mesh(use_msh=True, save_mesh=False)

fuel1_marker    = 1
fuel2_marker    = 2
fuel_rod_marker = 3
refl_marker     = 4

void_marker     = 10
sym_marker      = 20

clear_output()

Let us import the POD modes, using the ImportH5 function from the pyforce package.

[3]:
# Defining the functional space
V = FunctionSpace(domain, ("Lagrange", 1))

# Define the variables to load
var_names = [
             'phi_1',
             'phi_2'
             ]

tex_var_names = [
                 r'\phi_1',
                 r'\phi_2'
                 ]

bf = dict()

for field_i in range(len(var_names)):

    bf[var_names[field_i]] = ImportH5(V, path_off+'/BasisFunctions/basisPOD_'+var_names[field_i], 'POD_'+var_names[field_i])[0]

SGREEDY for POD basis

The SGREEDY algorithm, Maday et al. (2014), aims at maximising the inf-sup constant \(\beta_{N,M}\): roughly speaking, this constant measures how the sensors can include unmodelled physics with respect to the one embedded in the reduced basis.

The overall algorithm is summarised in the following figure from Riva et al. (2024)

SGREEDY

As for the GEIM method, three different values of the point spread will be considered: 0.1, 1 and 2.5. The SGREEDY method is implemented in the pyforce package, and it is called by SGREEDY: the class must be initialised with the domain, the basis functions (the POD modes in this case), the functional space in which the snapshots live, the name of the variable and the value of the point spread.

[4]:
from pyforce.offline.sensors import SGREEDY

s = 2.
sgreedy_pod = dict()

for field_i in range(len(var_names)):
    sgreedy_pod[var_names[field_i]] = [SGREEDY(domain, bf[var_names[field_i]], V, var_names[field_i], s)]*2

Since the snapshots belong to \(H^1\subset L^2\), the Riesz representation of a functional is sought in this space, endowed with the inner product \begin{equation*} \left(u,v\right)_{H^1}=\int_\Omega \nabla u\cdot \nabla v\,d\Omega + \int_\Omega u\cdot v\,d\Omega \end{equation*} The results will be compared with the approximation properties of the case with the Riesz representation in \(L^2\), whose inner product is \begin{equation*} \left(u,v\right)_{L^2} = \int_\Omega u\cdot v\,d\Omega \end{equation*}

The SGREEDY algorithm is called by the method generate from the SGREEDY class: it requires the dimension of the reduced space to use, the maximum number of sensors to place and a tolerance value for the stability-loop of the algorithm. Moreover, the method can have the optional parameters verbose and sampleEvery, which allow to print the progress and telling how many cells are to be used, respectively. In the end, there is a switch option for the use of the Riesz representation in \(H^1\) or \(L^2\).

[5]:
Nmax = 10
Mmax = 20

sam_every = 4

is_H1 = [False, True]
fun_space_label = ['L2', 'H1']

for field_i in range(len(var_names)):
        for kk in range(len(is_H1)):

            print('SGREEDY for '+var_names[field_i]+' with s={:.2f}'.format(s)+' and Riesz representation in '+fun_space_label[kk])

            sgreedy_pod[var_names[field_i]][kk].generate(   Nmax, Mmax, tol = 0.2,
                                                            sampleEvery = sam_every,
                                                            verbose=False, is_H1 = is_H1[kk])

            print(' ')
SGREEDY for phi_1 with s=2.00 and Riesz representation in L2

SGREEDY for phi_1 with s=2.00 and Riesz representation in H1

SGREEDY for phi_2 with s=2.00 and Riesz representation in L2

SGREEDY for phi_2 with s=2.00 and Riesz representation in H1

We can store the sensors using the StoreFunctionsList method.

[6]:
for field_i in range(len(var_names)):
        for kk in range(len(is_H1)):
                StoreFunctionsList( domain, sgreedy_pod[var_names[field_i]][kk].basis_sens,
                                    'SGREEDYPOD_' +var_names[field_i]+'_s_{:.2e}'.format(s),
                                    path_off+'/BasisSensors/sensorsSGREEDYPOD_' + var_names[field_i]+'_s_{:.2e}_'.format(s)+fun_space_label[kk])