Offline Phase: GEIM algorithm

Aim of the tutorial: this notebook shows how to perform the offline phase of the GEIM algorithm, i.e. how to use this method to generate the magic functions and the associared magic sensors from a parametric dataset.

The only observable variable is assumed to be the temperature: the GEIM class is only able to handle scalar variables at the moment.


To execute this notebook it is necessary to have the snapshots stored in Snapshots folder, placed in this directory (otherwise modify path_FOM variable).

[1]:
import numpy as np
import pickle
import os
from matplotlib import cm

from mpi4py import MPI
from dolfinx.io import gmshio
import gmsh
from dolfinx.fem import Function, FunctionSpace

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

Let us generate the mesh for importing OpenFOAM dataset into dolfinx

[2]:
mesh_comm = MPI.COMM_WORLD
model_rank = 0

# Initialize the gmsh module
gmsh.initialize()

# Load the .geo file
gmsh.merge('cavity.geo')
gmsh.model.geo.synchronize()

# Set algorithm (adaptive = 1, Frontal-Delaunay = 6)
gmsh.option.setNumber("Mesh.Algorithm", 6)
gdim = 2

# Linear Finite Element
gmsh.model.mesh.generate(gdim)
gmsh.model.mesh.optimize("Netgen")

# Import into dolfinx
model_rank = 0
domain, ct, ft = gmshio.model_to_mesh(gmsh.model, MPI.COMM_WORLD, model_rank, gdim = gdim )
gmsh.finalize()

tdim = domain.topology.dim
fdim = tdim - 1
domain.topology.create_connectivity(fdim, tdim)
Info    : Reading 'cavity.geo'...
Info    : Done reading 'cavity.geo'
Info    : Meshing 1D...
Info    : [  0%] Meshing curve 1 (Line)
Info    : [ 30%] Meshing curve 2 (Line)
Info    : [ 60%] Meshing curve 3 (Line)
Info    : [ 80%] Meshing curve 4 (Line)
Info    : Done meshing 1D (Wall 0.00111864s, CPU 0.000849s)
Info    : Meshing 2D...
Info    : Meshing surface 1 (Transfinite)
Info    : Done meshing 2D (Wall 0.0187267s, CPU 0.007379s)
Info    : 16384 nodes 32770 elements
Info    : Optimizing mesh (Netgen)...
Info    : Done optimizing mesh (Wall 2.643e-06s, CPU 3e-06s)

Let us define the functional space onto which the OpenFOAM data have been projected; then, the snapshots can be imported.

Even if only a single variable (the temperature is analysed), var_names is defined as a list of strings, to help generalise the notebook.

[3]:
path_FOM = './Snapshots/'

# The temperature is the only one that can be measured
var_names = ['norm_T']
tex_var_names = ['T']

fun_spaces = [FunctionSpace(domain, ('Lagrange', 1))]

train_snaps = dict()

for field_i, field in enumerate(var_names):

    path_snap = path_FOM+'TrainSet_'+field
    train_snaps[field] = ImportH5(fun_spaces[field_i], path_snap, field)[0]

GEIM algorithm

Generation of the magic functions/sensors

The GEIM is used to generated through a greedy process a set of basis functions and basis sensors for the data assimilation process.

Sensors are mathematically modelled as linear continuous functionals \(v_m(\cdot)\), defined as \begin{equation*} v_m(u(\mathbf{x});\,\mathbf{x}_m,\,s)=\int_\Omega u(\mathbf{x})\cdot \mathcal{K}\left(\|\mathbf{x}-\mathbf{x}_m\|_2, s\right)\,d\Omega \end{equation*} given \(\mathbf{x}_m\) the centre of mass of the functional and \(s\) its point spread. The current version adopts Gaussian kernels \begin{equation*} \mathcal{K}\left(\|\mathbf{x}-\mathbf{x}_m\|_2, s\right) = \frac{e^{\frac{-\|\mathbf{x}-\mathbf{x}_m\|_2^2}{2s^2}}}{\displaystyle\int_\Omega e^{\frac{-\|\mathbf{x}-\mathbf{x}_m\|_2^2}{2s^2}}\,d\Omega} \end{equation*} such that \(\|\mathcal{K}\left(\|\mathbf{x}-\mathbf{x}_m\|_2, s\right)\|_{L^1(\Omega)}=1\), this models the measurement process of scalar fields.

[4]:
from pyforce.offline.geim import GEIM

geim_data = dict()
train_GEIMcoeff = dict()
Mmax = 20

# This parameter serves as input to select only some cells in the mesh
sam_every = 5

s = 0.01

train_abs_err = np.zeros((Mmax, len(var_names)))
train_rel_err = np.zeros((Mmax, len(var_names)))

for field_i, field in enumerate(var_names):
    geim_data[field] = GEIM(domain, fun_spaces[field_i], field_i, s=s)
    tmp = geim_data[field].offline(train_snaps[field], Mmax, sampleEvery = sam_every, verbose = True)

    train_abs_err[:, field_i] = tmp[0].flatten()
    train_rel_err[:, field_i] = tmp[1].flatten()
    train_GEIMcoeff[field] = tmp[2]
Generating sensors (sampled every 5 cells): 3277.000 / 3277.00 - 0.008 s/it
  Iteration 020 | Abs Err: 6.48e-04 | Rel Err: 9.91e-04

Let us store the magic functions, the magic sensors and the training coefficients for later use.

[5]:
path_offline = './Offline_results/'

if not os.path.exists(path_offline+'/BasisSensors/'):
    os.makedirs(path_offline+'/BasisSensors/')
if not os.path.exists(path_offline+'/BasisFunctions/'):
    os.makedirs(path_offline+'/BasisFunctions/')

pickle.dump(train_GEIMcoeff, open(path_offline+'coeffs.geim', 'wb'))

for field in var_names:
    store(domain, geim_data[field].magic_fun,  'GEIM_' +field, path_offline+'/BasisFunctions/basisGEIM_' + field)
    store(domain, geim_data[field].magic_sens, 'GEIM_' +field, path_offline+'/BasisSensors/sensorsGEIM_' + field)

Post-process: plotting magic functions and sensors

The magic functions and sensors are plotted using pyvista.

[8]:
from contour_plotting import plot_modes

plot_modes(geim_data[var_names[0]].magic_fun, tex_var_names[0],
           shape=[2,2],
           title='Magic Function', zoom=1, subfig_size=[900,900],
           colormap=cm.jet)
../../_images/Tutorials_03_BuoyantCavity_OF6_02b_offline_GEIM_12_0.png

Let us also plot the sensors

[15]:
from contour_plotting import plot_geim_sensors

plot_geim_sensors(geim_data['norm_T'], 15, tex_var_names[0], resolution=[900,900])
../../_images/Tutorials_03_BuoyantCavity_OF6_02b_offline_GEIM_14_0.png