Christophe Prud'homme -- University of Strasbourg
Framework to solve problems based on ODE and PDE
C++17 (WIP C++20) with a Python layer using Pybind11
Seamless parallelism with default communicator
DevOps: CI/CD (docker + ubuntu/debian packaging)
Tests: Hundreds of tests run in sequential and parallel C++ and Python
Usage: Research, R&D, Teaching, Services
auto Vh = Pch<4>( mesh, markedelements(mesh, expr("<...>")) );
auto u = Vh->element(), v = Vh->element( g, "g" );
auto l = form1( _test = Vh );
l = integrate( _range = elements( support( Vh ) ),
_expr = f * id( v ) );
l += integrate( _range = markedfaces( support( Vh ), "Robin" ), _expr = -r_2 * id( v ) );
l += integrate( _range = markedfaces( support( Vh ), "Neumann" ), _expr = -un * id( v ) );
auto a = form2( _trial = Vh, _test = Vh );
a = integrate( _range = elements( support( Vh ) ),
_expr = inner( k * gradt( u ), grad( v ) ) );
a += integrate( _range = markedfaces( support( Vh ), "Robin" ), _expr = r_1*idt(u)*id(v));
a += on( _range = markedfaces( support(Vh), "Dirichlet" ), _rhs=l, _element=u, _expr = g );
a.solve( _rhs = l, _solution = u );
A large range of numerical methods to solve PDEs: cG, dG, hdG, rb/mor, …
0D+t, 1D(+t), 2D(+t), 3D(+t)
DSEL for Galerkin methods in C++
// Named Arguments
auto Xh=Pch<1>(_mesh=mesh); (1)
auto u = Xh->element();
// expression handling
auto e3 = expr( "2*x*u*v:x:u:v" ); (2)
auto e3v = expr( e3, symbolExpr( "u", dxv( u ) ), symbolExpr( "v", Nx() ) );
// remeshing in seq and //
auto rm = remesh(_mesh=mesh,_params=<remeshing specs in json>); (3)
// Fast marching : compute distance to range
auto distToBoundary = distanceToRange( _space=Vh, _range=boundaryfaces( mesh ) ); (4)
auto distToBoundaryNarrowBand = (5)
distanceToRange( _space=Vh, _range=boundaryfaces( mesh ),
_max_distance=3.*mesh->hAverage() );
1 | Expressivity: using our own named arguments library |
2 | Expressions handling: based on Ginac, handles automatic differentiation, JIT compilation |
3 | Remeshing in seq and parallel |
4 | Versatile levelset/fast marching framework |
5 | Narrow band FMM |
auto mesh = loadMesh<Mesh<Simplex<3,1>>();
auto Vh = Pch<2>( mesh ); (1)
auto u = Vh->element( inner(P()) ); (2)
auto e1 = expr( "u*u:u"); (3)
auto e2 = expr( "2*e1:e1");
auto e3base = expr( "3*e2:e2");
// handle composition
auto e3 = expr( e3base, symbolExpr("e1",e1), symbolExpr("e2",e2), symbolExpr("u",inner(P()) )); (4)
auto grad_e3 = grad<3>( e3 ); (5)
auto diff_e3_x_exact = 3*4*inner(P())*2*Px();
auto diff_e3_y_exact = 3*4*inner(P())*2*Py();
auto diff_e3_z_exact = 3*4*inner(P())*2*Pz();
auto grad_e3_exact = trans(vec(diff_e3_x_exact,diff_e3_y_exact,diff_e3_z_exact));
// compute error (machine error expected)
double error_grad_e3 = normL2(_range=elements(mesh),_expr= grad_e3 - grad_e3_exact ); (6)
1 | create \(P^2_{c,h}\) |
2 | create an element of Vh such that \(u(x)=x^T x\) |
3 | create expressions |
4 | define symbols in expressions |
5 | differentiate the expressions |
6 | compute \(L^2\) error norm |
import feelpp
from feelpp.operators import *
mesh= feelpp.load(m, mesh_name, 0.1)
Xh = feelpp.functionSpace(mesh=mesh)
v=Xh.element()
v.on(range=feelpp.elements(mesh), expr=feelpp.expr("1"))
e_meas = mesh.measure()
M=mass(test=Xh,trial=Xh,range=feelpp.elements(mesh))
assert(abs(M.energy(v,v)-e_meas)<1e-10)
S=stiffness(test=Xh,trial=Xh,range=feelpp.elements(mesh))
assert(S.energy(v,v)<1e-10)
v.on(range=feelpp.elements(mesh), expr=feelpp.expr("x+y:x:y"))
assert(abs(S.energy(v,v)-2*e_meas)<1e-10)
WIP: DSEL for Galerkin methods in Python like in C++
WIP: MOR integration in complex workflows
Parallel execution of toolboxes
Advanced parametric studies including UQ (see MS57)
Reinforcement learning for micro-swimming (see MS96)
Ensemble kalman filter in applications with sensors
import feelpp
from feelpp.toolboxes.core import *
from feelpp.toolboxes.fluid import *
from feelpp.toolboxes.heat import *
def simulate(toolbox, export=True, buildModelAlgebraicFactory=True,data=None):
toolbox.init(buildModelAlgebraicFactory)
#toolbox.printAndSaveInfo()
if toolbox.isStationary():
toolbox.solve()
if export:
toolbox.exportResults()
else:
if not toolbox.doRestart():
toolbox.exportResults(toolbox.timeInitial())
toolbox.startTimeStep()
while not toolbox.timeStepBase().isFinished():
if feelpp.Environment.isMasterRank():
print("time simulation: {}s/{}s with step: {}".format(toolbox.time(),toolbox.timeFinal(),toolbox.timeStep()))
toolbox.solve()
if export:
toolbox.exportResults()
toolbox.updateTimeStep()
return not toolbox.checkResults()
ht = heat(dim=3,order=1)
ok=simulate(ht)
meas = ht.postProcessMeasures().values()
df=pd.DataFrame([meas])
# do something with the pandas dataframe
....
cfd=fluid(dim=3,order=2)
ok=simulate(cfd)
....
Models and physical properties
Parameters
Mesh
Material properties
Boundary/Initial conditions
Post-processing
Checks (testsuite,benchmarks,…)
Design High Field Magnets up to \(37T\)
Automated generation linked to material database using templating
Full design simulation toolchain generation (2DAxi and 3D)
{ "Name": "turbulence-plane-channel",
"ShortName": "turbulence-plane-channel",
"Models": {
"fluid": {
"equations": "Navier-Stokes",
"turbulence": {
"enable": 1,
"model": "Spalart-Allmaras"}}},
"Parameters": {
"umax": 15.6,
"tfix": 0.01,
"chi": "t<tfix:t:tfix",
"umax_inlet": "umax:umax",
"H": 0.0635,
"Re": 32000},
"Materials": {
"Omega": {
"rho": "1",
"mu": "(rho*umax*(H/2))/Re:rho:umax:H:Re"}},
"BoundaryConditions": {
"velocity": {
"Dirichlet": {
"Gamma4": {
"expr": "{ umax_inlet*(2*fluid_dist2wall/H)^(1/7),0 }:umax_inlet:fluid_dist2wall:H",
"turbulence_bc": "inlet"},
"walls": {
"markers": ["Gamma1","Gamma3"],
"expr": "{0,0}",
"turbulence_bc": "wall"}}},
"pressure": {
"Dirichlet": {
"Gamma2": {
"expr": "1.2" }}},
"AAfluid": {
"outlet": {
"outlet": {
"expr": "0"}}}},
"PostProcess": {
"Exports": {
"fields": ["velocity","pressure"],
"expr": {
"dist2wall": "fluid_dist2wall:fluid_dist2wall",
"mu_t": "materials_mu_t:materials_mu_t",
"curl_U_magnitude": "fluid_curl_U_magnitude:fluid_curl_U_magnitude",
"SA_nu": "fluid_turbulence_SA_nu:fluid_turbulence_SA_nu" }}}}
Compute quantities based on expressions
Export data/results to visualisation software
Statistics : mean, max, min, integrals
Norms (w/ range support) : \(\|\cdot\|_{L^2}, \|\cdot\|_{H^1}, \|\nabla(\cdot)\|_{L^2}\)
Evaluation at points, cut lines and cut planes
Specific toolbox outputs: eg. flow rates, stresses, fluxes…
"PostProcess": { "heat" {
"Exports": {
"fields":["temperature","pid"]
},
"Measures": {
"Normal-Heat-Flux": {
"%1%": {
"markers":"%1%",
"direction":"outward",//"inward",
"index1":["Interior_wall","Exterior_wall"] } },
"Statistics": {
"temperature_%1%": {
"type":["min","max"],
"field":"temperature",
"markers":"%1%",
"index1":["Interior_wall","Exterior_wall"] }}
},
"tolerance":1e-1
}
}
Core: Mesh adaptation (event based)
Core: Repartitioning
Fluid: Rigid and Elastic moving bodies
All Toolboxes: Plugin system in C++ and Python
Time discretisation: parareal with possibility to accelerate with MOR
Scenarii: setup complex workflows in C++ (already doable in Python)
Find scalar or vector fields \(u_i,i=1...,N\) such that
Coefficients can depend on the unknowns \(u_j,j=1 \ldots N\).
Automatic differentiation built on top of Feel++ expression handling
Various schemes based on Newton or Picard
"Models": {
"cfpdes":{ "equations":["equation1","equation2"] },
"equation1":{
"setup":{
"unknown":{"basis":"Pch1","name":"prey","symbol":"u"},
"coefficients":{
"c":"1", // diffusion
"a":"-( (1-equation1_u) - equation2_v/(equation1_u+thealpha) ):thealpha:equation1_u:equation2_v", // life and death
"d":"1" // time derivative
} } },
"equation2":{
"setup":{
"unknown":{ "basis":"Pch1", "name":"predator", "symbol":"v" },
"coefficients":{
"c":"thedelta:thedelta", // diffusion
"a":"-( (thebeta*equation1_u)/(equation1_u+thealpha) - thegamma ):thebeta:thealpha:thegamma:equation1_u", // life and death
"d":"1"// time derivative
} } } }
...
"Models":
{
"cfpdes":{ "equations":["equation1","equation2"] },
"equation1":{
"setup":{
"unknown":{"basis":"Pch1","name":"c","symbol":"c"},
"coefficients":{
"d": "1",
"gamma": "{-equation2_grad_mu_0,-equation2_grad_mu_1,-equation2_grad_mu_2}"
}}},
"equation2":{
"setup":{
"unknown":{"basis":"Pch1","name":"mu","symbol":"mu"},
"coefficients":{
"gamma":"{lambda*equation1_grad_c_0,lambda*equation1_grad_c_1, lambda*equation1_grad_c_2}",
"a":"1",
"f": "equation1_c^2*(equation1_c^2 - 1)"
} } }
}
"Models":{
"cfpdes":{ "equations":"magnetic" },
"magnetic":{
"common":{"setup":{"unknown":{
"basis":"Pch1",
"name":"Atheta","
symbol":"Atheta"
}}},
"models":[{
"name":"magnetic_Conductor",
"materials":"Conductor",
"setup":{"coefficients":{
"c":"x/mu:x:mu",
"a":"1/mu/x:mu:x",
"d":"materials_Conductor_sigma*x
:materials_Conductor_sigma:x"}}
},{
"name":"magnetic_Air",
"materials":"Air",
"setup":{"coefficients":{
"c":"x/mu:x:mu",
"a":"1/mu/x:mu:x"}}}
]
}
}
...
\(p\) is the potential
\(\mathbf{j}\) is the flux
\(\boldsymbol{u}: \Omega \rightarrow \mathbb{R}^{3}\) displacement
\(\underline{\boldsymbol{\epsilon}}(\boldsymbol{u}):=\frac{1}{2}\left(\nabla \boldsymbol{u}+(\nabla \boldsymbol{u})^{\top}\right)\) strain tensor
\(\underline{\boldsymbol{\sigma}}: \Omega \rightarrow S\) stress tensor where \(S\) is the set of all symmetric matrices in \(\mathbb{R}^{d\times d}\)
Provides optimal approximation of both primal and flux/stress variables \(p/\boldsymbol{u}\) and \(\mathbf{j}/\underline{\boldsymbol{\sigma}}\) respectively;
Requires less globally coupled degrees of freedom than DG methods of comparable accuracy;
Allows local element-by-element postprocessing to obtain new approximations with enhanced accuracy and conservation properties
Some Applications
Electrostatic
Heat transfer
Flow in porous media
Elasticity and Poro-Elasticity
A HDG method for elliptic problems with integral boundary condition: Theory and Applications, Silvia Bertoluzza,Giovanna Guidoboni,Romain Hild,Daniele Prada,Christophe Prud’homme,Riccardo Sacco,Lorenzo Sala,Marcela Szopos, 2021 submitted
\(\mathcal{T}_h\) the collection of elements \(K\) such that \(\Omega = \bigcup_{K\in \mathcal{T}_h} K\).
\(h:= max_{K \in \mathcal{T}_h} h_K\), \(\partial K\) the boundary of \(K\) with its measure \(|F|\)
\(\mathbf{n}_{\partial K}\) is the associated unit outward normal vector.
The skeleton of \(\mathcal{T}_h\) is the collection of all the faces of \(\mathcal{T}_h\) into the set \(\mathcal{F}_h\).
\(\mathcal{F}_h = \mathcal{F}_h^\Gamma \cup \mathcal{F}_h^0, \; \mathcal{F}_h^\Gamma = \mathcal{F}_h^D \cup \mathcal{F}_h^N \cup \mathcal{F}_h^{ibc}\)
\(V_h = \Pi_{K \in \mathcal{T}_h} V(K), \qquad V(K) = \left[ P_k (K) \right]^n\)
\(W_h = \Pi_{K\in\mathcal{T}_h} W(K), \qquad W(K) = \left[ P_k (K) \right]\)
\(\widetilde M_h = \{ \mu \in L^2(\mathcal{F}_h): \mu\rvert_F \in P_k(F) \; \forall F \in \mathcal{F}_h \setminus \mathcal{F}_h^{ibc} \},\)
\(M^*_h = \{ \mu \in C^0(\mathcal{F}^{ibc}_h): \mu\rvert_F \in P_0(F) \; \forall F \in \mathcal{F}_h^{ibc} \} \cong \mathbb{R}\)
\(M_h=\widetilde M_h \oplus M^*_h\)
Discrete formulation Find \(\boldsymbol{j}_h \in V_h, \; p_h \in W_h\) and \(\hat{p}_h \in M_h\) such that:
auto mesh=loadMesh(_mesh=new Mesh<Simplex<3>>); (1)
auto complement_integral_bdy = complement(faces(mesh), (2)
[&mesh]( auto const& e ) {
if ( e.hasMarker() &&
e.marker().matches(mesh->markerName("Ibc*") ) )
return true;
return false;
});
auto face_mesh = createSubmesh( mesh, complement_integral_bdy); (3)
auto ibc_mesh = createSubmesh( mesh, markedfaces(mesh,"Ibc*")); (4)
1 | load mesh |
2 | build set of non ibc facets |
3 | \(\mathcal{F}_h^{ibc}\) and |
4 | \(\mathcal{F}_h\setminus\mathcal{F}_h^{ibc}\). |
Vh_ptr_t Vh = Pdhv<OrderP>( mesh); (1)
Wh_ptr_t Wh = Pdh<OrderP>( mesh );
Mh_ptr_t Mh = Pdh<OrderP>( face_mesh );
// only one degree of freedom
Ch_ptr_t Ch = Pch<0>(ibc_mesh );
// $n$ IBC
auto ibcSpaces = product( nb_ibc, Ch); (2)
auto Xh = product( Vh, Wh, Mh. ibcSpaces ); (3)
1 | create the spaces \(V_h,W_h,\tilde{M}_h\) and \(M_h^*\). |
2 | handle arbirary number of IBCs |
3 | initialize spaces and product space |
auto a = blockform2( Xh )
auto rhs = blockform1( Xh );
. . .
// Assembling the right hand side
rhs(1_c) += integrate(_range=elements(mesh),_expr=-f*id(w));
. . .
// Assembling the main matrix
a(0_c,0_c) += integrate(_range=elements(mesh),
_expr=(trans(lambda*idt(u))*id(v)) );
. . .
//$\langle \hat{p}_h\rvert_{\tilde{M}_h}, \boldsymbol{v}_h^K \cdot {\boldsymbol{n}}_{\partial K}\rangle$ $$
a(0_c,2_c) += integrate(_range=internalfaces(mesh),
_expr=( idt(phat)*(leftface(trans(id(v))*N())+
rightface(trans(id(v))*N()))));
a( 3_c, 0_c, i, 0 ) +=
integrate( _range=markedfaces(mesh,"Ibc"),
_expr=(trans(idt(u))*N()) * id(nu) );
auto U = Xh.element();
a.solve(_solution=U, _rhs=rhs, _name="hdg");
auto up = U(0_c);
auto pp = U(1_c);
auto phat = U(2_c);
auto ip = U(3_c,0);
// postprocessing
auto Whp = Pdh<OrderP+1>( mesh );
auto pps = product( Whp );
auto PP = pps.element();
auto ppp = PP(0_c);
auto b = blockform2( pps, solve::strategy::local, backend() );
b( 0_c, 0_c ) = integrate( _range=elements(mesh), _expr=inner(gradt(ppp),grad(ppp)));
auto ell = blockform1( pps, solve::strategy::local, backend() );
ell(0_c) = integrate( _range=elements(mesh), _expr=-lambda*grad(ppp)*idv(up));
b.solve( _solution=PP, _rhs=ell, _name="sc.post", _local=true);
ppp=PP(0_c);
ppp += -ppp.ewiseMean(P0dh)+pp.ewiseMean(P0dh);
Similar to CFPDEs, except that only one equation for now
time dependence
Other terms in the PDEs
non-linear coefficients
IBCs
arbitrary number
Coupling with 0D+t models using FMU
time splitting approach to avoid iterating
Extended to PoroElasticity
WIP: HHO support
WIP: Order reduction (RB and NiRB)
Future: merge with CFPDEs
Develop Feel++ as a service
Streamline complex workflows for advanced usage/studies
data assimilation,
machine learning,
UQ on complex models
…
Go Digital twins
Building energy modeling
High field magnets
Eye