16

When FiniteElement method is used, the differential equations will first be transformed to certain standard form (named as formal PDE in recent FEM document), and it turns out to be critical to check what the standard form is when analyzing various issues related to FEM. Here are some examples:

Position of discontinuous coefficient influences the solution of PDE

How to input Robin boundary conditions for nonstandard Laplace equation?

Sign of conservative convection coefficient in a formal (Inactive) PDE

Stress analysis in axisymmetric bodies

The coefficient of formal PDE is available from PDECoefficientData, but its output is just hard to read. For example, with

{state} = NDSolve`ProcessEquations[
            With[{u = u[x, y]}, {-2 D[u, y, y] - 3 D[u, x, x] == 1, 
                                 DirichletCondition[u == 0, True]}], 
            u, {x, 0, 1}, {y, 0, 1}];

data = state["FiniteElementData"]["PDECoefficientData"];

data["All"] (* {{{{1}}, {{{{0}, {0}}}}}, {{{{{3, 0}, {0, 2}}}}, {{{{0}, {0}}}}, {{{{0, 0}}}}, {{0}}}, {{{0}}}, {{{0}}}} *)

at hand, can you tell what's what? Can you label $d$, $c$, $\alpha$, etc. in the formal PDE

$$d\frac{\partial }{\partial t}u+\nabla \cdot (-c \nabla u-\alpha u+\gamma ) +\beta \cdot \nabla u+ a u -f=0$$

with corresponding values, without doubt?

Can we have a function that shows the formal PDE of FiniteElement in a easy-to-read way? A possible (but not necessary of course) input-output in my mind:

showFormalPDE@With[{u = u[x, y]}, -2 D[u, y, y] - 3 D[u, x, x] == 1]
(* -1 + Inactive[Div][(-{{3, 0}, {0, 2}}.Inactive[Grad][u[x, y], {x, y}]), {x, y}] == 0 *)
xzczd
  • 65,995
  • 9
  • 163
  • 468

3 Answers3

15

Coincidence had it that I needed a code to reconstruct the inactive PDE that has been parsed for a customer a few weeks back. I have then added this function to the kernel and it will be available in 12.2.

The details of the operators and their specification can be found in the documentation and @andre already added links to that documentation.

In versions after 12.2 you can use:

NDSolve`FEM`GetInactivePDE 

Else, here is the code to get the inactive PDE from the NDSolve state data:

Needs["NDSolve`FEM`"]
zeroCoefficientQ[c_] := Union[N[Flatten[c]]] === {0.}
ClearAll[GetInactivePDE]
GetInactivePDE[pdec_PDECoefficientData, vd_] := 
 Module[{lif, sif, dif, mif, hasTimeQ, tvar, vars, depVars, neqn, 
   nspace, dep, load, dload, diff, cconv, conv, react, 
   pde},
  {lif, sif, dif, mif} = pdec["All"];

tvar = NDSolve`SolutionDataComponent[vd, "Time"]; If[tvar === None || tvar === {}, hasTimeQ = False; tvar = Sequence[];, hasTimeQ = True;];

vars = NDSolveSolutionDataComponent[vd, "Space"]; depVars = NDSolveSolutionDataComponent[vd, "DependentVariables"]; neqn = Length[depVars]; nspace = Length[vars]; dep = (# @@ Join[{tvar}, vars]) & /@ depVars;

{load, dload} = lif; {diff, cconv, conv, react} = sif;

load = load[[All, 1]]; dload = dload[[All, 1, All, 1]]; conv = conv[[All, All, 1, All]]; cconv = cconv[[All, All, All, 1]];

pde = If[hasTimeQ, mif[[1]].D[dep, {tvar, 2}] + dif[[1]].D[dep, tvar], ConstantArray[0, {Length[dep]}]];

If[! zeroCoefficientQ[diff], pde += (Plus @@@ Table[Inactive[ Div][-diff[[r, c]].Inactive[Grad][dep[[c]], vars], vars], {r, neqn}, {c, neqn}]);];

If[! zeroCoefficientQ[cconv], pde += (Plus @@@ Table[Inactive[Div][-cconv[[r, c]]*dep[[c]], vars], {r, neqn}, {c, neqn}]);];

If[! zeroCoefficientQ[dload], pde += (Inactive[Div][#, vars] & /@ dload);];

If[! zeroCoefficientQ[conv], pde += (Plus @@@ Table[conv[[r, c]].Inactive[Grad][dep[[c]], vars], {r, neqn}, {c, neqn}]);];

pde += react.dep;

pde -= load;

pde ]

GetInactivePDE[state_NDSolve`StateData] := Module[{femd = state["FiniteElementData"], vd = state["VariableData"], pdec}, pdec = femd["PDECoefficientData"]; GetInactivePDE[pdec, vd]]

Here is an example of it's usage:

op = -x D[u[x, y], {x, 2}] - D[u[x, y], {y, 2}] - 1;
{state} = 
  NDSolve`ProcessEquations[{op == 0, 
    DirichletCondition[u[x, y] == 0, True]}, 
   u, {x, y} ∈ Disk[]
   ];

pde = GetInactivePDE[state]; pde // InputForm

{-1 + {1, 0} . Inactive[Grad][u[x, y], {x, y}] + Inactive[Div][-{{x, 0}, {0, 1}} . Inactive[Grad][u[x, y], {x, y}], {x, y}]}

Note, how the x in front of the D got pulled into the Div - Grad and how that is compensated by a convection component. See for example FEMDocumentation/tutorial/FiniteElementBestPractice#588198981 that explains this behavior.

xzczd
  • 65,995
  • 9
  • 163
  • 468
user21
  • 39,710
  • 8
  • 110
  • 167
  • 1
    Side note: NDSolve\FEM`GetInactivePDE ` is built-in in v12.2. (There's no separate document page for it though. ) – xzczd Apr 29 '21 at 06:21
  • 1
    I take the liberty to add a line making GetInactivePDE in this post handle NDSolve\StateData`, feel free to roll back if you don't like it :) . – xzczd Apr 30 '21 at 13:25
  • @user21 Looking at the inactive form of the pde: The Div-part defines the NeumannValue (-{{x, 0}, {0, 1}} . Grad [U[x, y], {x, y}]) . Is this conclusion correct? Thanks! – Ulrich Neumann May 16 '22 at 14:08
  • @UlrichNeumann, yes, whatever is in the Div part defines NeumannValue. – user21 May 17 '22 at 11:22
  • @user21 Thank you, in this question Understanding NeumannValue I 'm trying, in vain, to verify this result. Please read my question. – Ulrich Neumann May 18 '22 at 09:47
  • Just notice an improper behavior of GetInactivePDE: c = IdentityMatrix[2]; α = {x, y}; γ = {y, x^2}; ipde = Inactive[Div][Inactive[Plus][-c . Inactive[Grad][u[x, y], {x, y}], Inactive[Times][-α, u[x, y]], γ], {x, y}] == 0; {state} = NDSolve`ProcessEquations[{ipde, DirichletCondition[u[x, y] == 0, True]}, u, {x, y} ∈ Disk[]]; NDSolve`FEM`GetInactivePDE@state, the $(-c ∇u-α u+γ)$ term should be placed in a single $∇·$. – xzczd Jul 28 '22 at 05:12
  • @xzczd, I'll have a look once I'm back from vacation. – user21 Jul 28 '22 at 12:14
  • @xzczd, I had a look. The fact that GetInactivePDE returns a divergence for each term is not wrong per se. No matter how I implement this, there will always be forms where the output is mathematically equivalent but different from what was given on input. I don't think this is a major problem. Do you disagree? – user21 Aug 15 '22 at 10:56
  • Well, I disagree. Though the PDEs are mathematically equivalent, the corresponding NeumannValues are different, and one of the most important usage of GetInactivePDE (in my view) is helping one to decide the correct NeumannValue. – xzczd Aug 15 '22 at 11:05
  • @xzczd, there should not be any difference. – user21 Aug 15 '22 at 11:09
  • @xzczd, this should be equivalent: Inactive[ Div][-c . Inactive[Grad][u[x], {x}] - \[Alpha]* u[x] + \[Gamma], {x}] == Inactive[Div][-c . Inactive[Grad][u[x], {x}], {x}] + Inactive[Div][-\[Alpha]*u[x], {x}] + Inactive[Div][\[Gamma], {x}] == NeumannValue – user21 Aug 15 '22 at 11:14
  • Yeah, but when Times is Inactived, the situation is different, I elaborate the issue in this answer: https://mathematica.stackexchange.com/a/271385/1871 – xzczd Aug 15 '22 at 11:19
  • OK, thanks I'll look at that a bit later. – user21 Aug 15 '22 at 11:22
  • Oops, I see, it's indeed not wrong. (It's $\nabla \color{orange}{\cdot} (-\alpha u)$ v.s. $\beta \color{orange}{\cdot} \nabla u$. ) But still, I'd argue if $−c∇u−αu+γ$ is in a single $\nabla \cdot$, the output will be much easier to follow. – xzczd Aug 15 '22 at 11:41
  • @xzczd, yeah, would be nice but it's not easy to get and I'd rather spend my time fixing more serious issues. Thanks for checking this again and also for reporting it. – user21 Aug 16 '22 at 07:17
10

I don't know if you are aware that this is documented in details.

The problem is that the informations are dispatched over the documentation of PDECoefficentData and InitializePDECoefficients.

your code :

{state} = 
  NDSolve`ProcessEquations[
   With[{u = u[x, y]}, {-2 D[u, y, y] - 3 D[u, x, x] == 1, 
     DirichletCondition[u == 0, True]}], u, {x, 0, 1}, {y, 0, 1}];

data = state["FiniteElementData"]["PDECoefficientData"];

data["All"] ({{{{1}},{{{{0},{0}}}}},{{{{{3,0},{0,2}}}},{{{{0},{0}}}},{{{{0,0}}}},
{{0}}},{{{0}}},{{{0}}}}
)

The PDECoefficentData documentation explains this :

data["ConvectionCoefficients"]
data["DampingCoefficients"]
data["MassCoefficients"]
data["LoadCoefficients"]
(* etc ... *) 

{{{{0, 0}}}}

{{0}}

{{0}}

{{1}}

InitializePDECoefficients documentation :

enter image description here

The DampingCoefficients and MassCoefficients are explained beyond.

xzczd
  • 65,995
  • 9
  • 163
  • 468
andre314
  • 18,474
  • 1
  • 36
  • 69
  • Oh, I didn't notice the Details section of InitializePDECoefficients… but I think it'll be good to have a function that can generate the formal PDE automatically, checking the table in the document is still a bit painful. – xzczd Jul 11 '20 at 08:09
  • 1
    It took me a while to notice this too (I don't rememder exactly, but it was after having read the tutorials). – andre314 Jul 11 '20 at 08:16
5

The problem has been resolved by user21. I'd like to add a wrapper for NDSolve`FEM`GetInactivePDE to make it easier to use:

ClearAll[checkFormalPDE]

toeq[sys_] := Replace[Flatten@{sys}, expr : Except[_Equal | _DirichletCondition | _PeriodicBoundaryCondition] :> expr == 0, {1}];

SetAttributes[checkFormalPDE, HoldAll];

With[{pe = NDSolveProcessEquations, gip = NDSolveFEM`GetInactivePDE}, checkFormalPDE[(head : NDSolve | NDSolveValue | NDEigenvalues | NDEigensystem)[sys_, depend_, domain__, n___Integer, rest : OptionsPattern[]]] := Module[{state = Sow@First@pe[sys // toeq, depend, domain, rest]}, state // gip]];

Usage

Simply wrap it on NDSolve/NDSolveValue/NDEigenvalues/NDEigensystem:

checkFormalPDE@
 NDSolve[With[{u = u[x, y]}, {-2 D[u, y, y] - 3 D[u, x, x] == 1, 
    DirichletCondition[u == 0, True]}], u, {x, 0, 1}, {y, 0, 1}]
(* {-1 + Inactive[Div][(-{{3, 0}, {0, 2}} . Inactive[Grad][u[x, y], {x, y}]), {x, y}]} *)
xzczd
  • 65,995
  • 9
  • 163
  • 468