File: diffusion.tex

package info (click to toggle)
python-escript 5.6-10
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 144,304 kB
  • sloc: python: 592,074; cpp: 136,909; ansic: 18,675; javascript: 9,411; xml: 3,384; sh: 738; makefile: 207
file content (409 lines) | stat: -rw-r--r-- 18,457 bytes parent folder | download | duplicates (3)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Copyright (c) 2003-2018 by The University of Queensland
% http://www.uq.edu.au
%
% Primary Business: Queensland, Australia
% Licensed under the Apache License, version 2.0
% http://www.apache.org/licenses/LICENSE-2.0
%
% Development until 2012 by Earth Systems Science Computational Center (ESSCC)
% Development 2012-2013 by School of Earth Sciences
% Development from 2014 by Centre for Geoscience Computing (GeoComp)
%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

\section{The Diffusion Problem}
\label{DIFFUSION CHAP}

\subsection{\label{DIFFUSION OUT SEC}Outline}
In this section we discuss how to solve a time-dependent temperature
diffusion\index{diffusion equation} PDE for a given block of material.
Within the block there is a heat source which drives temperature diffusion.
On the surface, energy can radiate into the surrounding environment.
\fig{DIFFUSION FIG 1} shows the configuration.

\begin{figure}
\centerline{\includegraphics[width=\figwidth]{DiffusionDomain}}
\caption{Temperature Diffusion Problem with Circular Heat Source}
\label{DIFFUSION FIG 1}
\end{figure}

In \Sec{DIFFUSION TEMP SEC} we present the relevant model.
A time integration scheme is introduced to calculate the temperature at given time nodes $t^{(n)}$.
At each time step a Helmholtz equation\index{Helmholtz equation} must be solved. 
The implementation of a Helmholtz equation solver will be discussed in \Sec{DIFFUSION HELM SEC}. 
In Section~\ref{DIFFUSION TRANS SEC} this solver is used to build a solver for
the temperature diffusion problem.

\subsection{\label{DIFFUSION TEMP SEC}Temperature Diffusion}
The unknown temperature $T$ is a function of its location in the domain and time $t>0$.
The governing equation in the interior of the domain is given by
\begin{equation}
\rho c_p T_{,t} - (\kappa T_{,i})_{,i} = q_H
\label{DIFFUSION TEMP EQ 1}
\end{equation}
where $\rho c_p$ and $\kappa$ are given material constants.
In case of a composite material parameters depend on their location in the domain.
$q_H$ is a heat source (or sink) within the domain.
We use the Einstein summation convention\index{summation convention} as introduced in \Chap{FirstSteps}.
In our case we assume $q_H$ to be equal to a constant heat
production rate $q^{c}$ on a circle or sphere with center $x^c$ and radius $r$, and $0$ elsewhere:
\begin{equation}
q_H(x,t)=\left\{ 
\begin{array}{l l}
    q^c  & \quad\text{if $\|x-x^c\| \le r$}\\
    0    & \quad\text{else}\\
\end{array} \right.
\label{DIFFUSION TEMP EQ 1b}
\end{equation}
for all $x$ in the domain and time $t>0$.

On the surface of the domain we specify a radiation condition which prescribes
the normal component of the flux $\kappa T_{,i}$ to be proportional
to the difference of the current temperature to the surrounding temperature $T_{ref}$:
\begin{equation}
 \kappa T_{,i} n_i = \eta (T_{ref}-T) 
\label{DIFFUSION TEMP EQ 2}
\end{equation}
$\eta$ is a given material coefficient depending on the material of the block and the surrounding medium.
$n_i$ is the $i$-th component of the outer normal field\index{outer normal field} at the surface of the domain. 

To solve the time-dependent \eqn{DIFFUSION TEMP EQ 1} the initial temperature at time $t=0$ has to be given.
Here we assume that the initial temperature is the surrounding temperature:
\begin{equation}
T(x,0)=T_{ref} 
\label{DIFFUSION TEMP EQ 4}
\end{equation}
for all $x$ in the domain. Note that the initial conditions satisfy the
boundary condition defined by \eqn{DIFFUSION TEMP EQ 2}. 

The temperature is calculated at discrete time nodes $t^{(n)}$ where 
$t^{(0)}=0$ and $t^{(n)}=t^{(n-1)}+h$, where $h>0$ is the step size which is assumed to be constant.
In the following, the upper index ${(n)}$ refers to a value at time $t^{(n)}$.
The simplest and most robust scheme to approximate the time derivative of the
temperature is the backward Euler\index{backward Euler} scheme.
The backward Euler scheme is based on the Taylor expansion of $T$ at time $t^{(n)}$:
\begin{equation}
T^{(n)}\approx T^{(n-1)}+T_{,t}^{(n)}(t^{(n)}-t^{(n-1)})
=T^{(n-1)} + h \cdot T_{,t}^{(n)}
\label{DIFFUSION TEMP EQ 6}
\end{equation}
This is inserted into \eqn{DIFFUSION TEMP EQ 1}. By separating the terms at
$t^{(n)}$ and $t^{(n-1)}$ one gets for $n=1,2,3\ldots$
\begin{equation}
\frac{\rho c_p}{h} T^{(n)} - (\kappa T^{(n)}_{,i})_{,i} = q_H +  \frac{\rho c_p}{h} T^{(n-1)}
\label{DIFFUSION TEMP EQ 7}
\end{equation}
where $T^{(0)}=T_{ref}$ is taken form the initial condition given by \eqn{DIFFUSION TEMP EQ 4}.
Together with the natural boundary condition 
\begin{equation}
 \kappa T_{,i}^{(n)} n_i = \eta (T_{ref}-T^{(n)}) 
\label{DIFFUSION TEMP EQ 2222}
\end{equation}
taken from \eqn{DIFFUSION TEMP EQ 2} this forms a boundary value problem that
has to be solved for each time step. 
As a first step to implement a solver for the temperature diffusion problem we
will implement a solver for the boundary value problem that has to be solved
at each time step.

\subsection{\label{DIFFUSION HELM SEC}Helmholtz Problem}
The partial differential equation to be solved for $T^{(n)}$ has the form 
\begin{equation}
\omega T^{(n)} - (\kappa T^{(n)}_{,i})_{,i} = f
\label{DIFFUSION HELM EQ 1}
\end{equation}
and we set
\begin{equation}
\omega=\frac{\rho c_p}{h} \mbox{ and } f=q_H +\frac{\rho c_p}{h}T^{(n-1)} \;.
\label{DIFFUSION HELM EQ 1b}
\end{equation}
With $g=\eta T_{ref}$ the radiation condition defined by \eqn{DIFFUSION TEMP EQ 2222} takes the form 
\begin{equation}
    \kappa T^{(n)}_{,i} n_{i} = g - \eta T^{(n)}\text{ on $\Gamma$}
\label{DIFFUSION HELM EQ 2}
\end{equation}
The partial differential \eqn{DIFFUSION HELM EQ 1} together with boundary
conditions of \eqn{DIFFUSION HELM EQ 2} is the Helmholtz equation\index{Helmholtz equation}.

We want to use the \LinearPDE class provided by \escript to define and solve a
general linear, steady, second order PDE such as the Helmholtz equation.
For a single PDE the \LinearPDE class supports the following form:
\begin{equation}\label{LINEARPDE.SINGLE.1 TUTORIAL}
-(A_{jl} u_{,l})_{,j}+D u = Y
\end{equation}
where we show only the coefficients relevant for the problem discussed here.
For the general form of a single PDE see \eqn{LINEARPDE.SINGLE.1}.
The coefficients $A$ and $Y$ have to be specified through \Data objects in
the \Function on the PDE or objects that can be converted into such \Data objects.
$A$ is a \RankTwo and $D$ and $Y$ are scalar.
The following natural boundary conditions\index{boundary condition!natural}
are considered on $\Gamma$:
\begin{equation}\label{LINEARPDE.SINGLE.2 TUTORIAL}
n_{j}A_{jl} u_{,l}+d u= y  \;.
\end{equation}
Notice that the coefficient $A$ is the same as in the PDE~\eqn{LINEARPDE.SINGLE.1 TUTORIAL}.
The coefficients $d$ and $y$ are each a \Scalar in the \FunctionOnBoundary.
Constraints\index{constraint} for the solution prescribe the value of the
solution at certain locations in the domain. They have the form
\begin{equation}\label{LINEARPDE.SINGLE.3 TUTORIAL}
u=r \mbox{ where } q>0
\end{equation}
Both $r$ and $q$ are a \Scalar where $q$ is the characteristic function\index{characteristic function} defining where the constraint is applied.
The constraints defined by \eqn{LINEARPDE.SINGLE.3 TUTORIAL} override any
other condition set by \eqn{LINEARPDE.SINGLE.1 TUTORIAL} or \eqn{LINEARPDE.SINGLE.2 TUTORIAL}.
The \Poisson class of the \linearPDEs module, which we have already used in
\Chap{FirstSteps}, is in fact a subclass of the more general \LinearPDE class.
The \linearPDEs module provides a \Helmholtz class but we will make direct use
of the general \LinearPDE class.

By inspecting the Helmholtz equation\index{Helmholtz equation}
(\ref{DIFFUSION HELM EQ 1}) and boundary condition (\ref{DIFFUSION HELM EQ 2}),
and substituting $u$ for $T^{(n)}$, we can easily assign values to the
coefficients in the general PDE of the \LinearPDE class:
\begin{equation}\label{DIFFUSION HELM EQ 3}
\begin{array}{llllll}
A_{ij}=\kappa \delta_{ij} & D=\omega & Y=f \\
d=\eta & y= g &  \\
\end{array}
\end{equation}
$\delta_{ij}$ is the Kronecker symbol\index{Kronecker symbol} defined
by $\delta_{ij}=1$ for $i=j$ and $0$ otherwise.
Undefined coefficients are assumed to be not present.\footnote{There is a
difference in \escript for a coefficient to be not present and set to zero.
Since in the former case the coefficient is not processed, it is more efficient
to leave it undefined instead of assigning zero to it.}
In this diffusion example we do not need to define a characteristic function
$q$ because the boundary conditions we consider in \eqn{DIFFUSION HELM EQ 2}
are just the natural boundary conditions which are already defined in the
\LinearPDE class (shown in \eqn{LINEARPDE.SINGLE.2 TUTORIAL}).

The Helmholtz equation can be set up the following way\footnote{Note that this
is not a complete code. The full source code can be found in ``helmholtz.py''.}:
\begin{python}
  mypde=LinearPDE(mydomain)
  mypde.setValue(A=kappa*kronecker(mydomain),D=omega,Y=f,d=eta,y=g)
  u=mypde.getSolution()
\end{python}
where we assume that \code{mydomain} is a \Domain object and 
\code{kappa}, \code{omega}, \code{eta}, and \code{g} are given scalar values
typically \code{float} or \Data objects. The \method{setValue} method
assigns values to the coefficients of the general PDE.
The \method{getSolution} method solves the PDE and returns the solution
\code{u} of the PDE. \function{kronecker} is an \escript function returning
the Kronecker symbol.

The coefficients can be set by several calls to \method{setValue} in arbitrary
order. 
If a value is assigned to a coefficient several times, the last assigned value
is used when the solution is calculated:
\begin{python}
  mypde = LinearPDE(mydomain)
  mypde.setValue(A=kappa*kronecker(mydomain), d=eta)
  mypde.setValue(D=omega, Y=f, y=g)
  mypde.setValue(d=2*eta) # overwrites d=eta
  u=mypde.getSolution()
\end{python}
In some cases the solver of the PDE can make use of the fact that the PDE is
symmetric\index{symmetric PDE}. A PDE is called symmetric if
\begin{equation}\label{LINEARPDE.SINGLE.4  TUTORIAL}
A_{jl}=A_{lj}\;.
\end{equation}
Note that $D$ and $d$ may have any value and the right hand sides $Y$, $y$ as
well as the constraints are not relevant. The Helmholtz problem is symmetric.
The \LinearPDE class provides the method \method{checkSymmetry} to check if
the given PDE is symmetric.
\begin{python}
  mypde = LinearPDE(mydomain)
  mypde.setValue(A=kappa*kronecker(mydomain), d=eta)
  print(mypde.checkSymmetry()) # returns True
  mypde.setValue(B=kronecker(mydomain)[0])
  print(mypde.checkSymmetry()) # returns False
  mypde.setValue(C=kronecker(mydomain)[0])
  print(mypde.checkSymmetry()) # returns True
\end{python}
Unfortunately, calling \method{checkSymmetry} is very expensive and is only
recommended for testing and debugging purposes.
The \method{setSymmetryOn} method is used to declare a PDE symmetric:
\begin{python}
  mypde = LinearPDE(mydomain)
  mypde.setValue(A=kappa*kronecker(mydomain))
  mypde.setSymmetryOn()
\end{python}
Now we want to see how we solve the Helmholtz equation on a
rectangular domain of length $l_{0}=5$ and height $l_{1}=1$.
We choose a simple test solution such that we can verify the returned solution
against the exact answer. We take $T=x_{0}$ (here
$q_H = 0$) and then calculate right hand side terms $f$ and $g$
such that the test solution becomes the solution of the problem.
If we assume $\kappa$ is constant, an easy calculation shows that we
have to choose $f=\omega \cdot x_{0}$.
On the boundary we get $\kappa n_{i} u_{,i}=\kappa n_{0}$, so we set $g=\kappa n_{0}+\eta x_{0}$.
The following script \file{helmholtz.py}\index{scripts!\file{helmholtz.py}}
which is available in the \ExampleDirectory implements this test problem using
the \finley PDE solver:
\begin{python}
  from esys.escript import *
  from esys.escript.linearPDEs import LinearPDE
  from esys.finley import Rectangle
  from esys.weipa import saveVTK
  # set some parameters
  kappa=1.
  omega=0.1
  eta=10.
  # generate domain
  mydomain = Rectangle(l0=5., l1=1., n0=50, n1=10)
  # open PDE and set coefficients
  mypde=LinearPDE(mydomain)
  mypde.setSymmetryOn()
  n=mydomain.getNormal()
  x=mydomain.getX()
  mypde.setValue(A=kappa*kronecker(mydomain), D=omega,Y=omega*x[0], \
                 d=eta, y=kappa*n[0]+eta*x[0])
  # calculate error of the PDE solution
  u=mypde.getSolution()
  print("error is ",Lsup(u-x[0]))
  saveVTK("x0.vtu", sol=u)
\end{python}
To visualize the solution `x0.vtu' you can use the command
\begin{verbatim}
mayavi2 -d x0.vtu -m Surface
\end{verbatim}
and it is easy to see that the solution $T=x_{0}$ is calculated.
 
The script is similar to the script \file{poisson.py} discussed in \Chap{FirstSteps}.
\code{mydomain.getNormal()} returns the outer normal field on the surface of the domain.
The function \function{Lsup} is imported by the \code{from esys.escript import *}
statement and returns the maximum absolute value of its argument.
The error shown by the print statement should be in the order of $10^{-7}$.
As piecewise bi-linear interpolation is used by \finley to approximate the
solution, and our solution is a linear function of the spatial coordinates,
one might expect that the error would be zero or in the order of machine
precision (typically $\approx 10^{-15}$).
However most PDE packages use an iterative solver which is terminated when a
given tolerance has been reached. The default tolerance is $10^{-8}$.
This value can be altered by using the \method{setTolerance} method of the
\LinearPDE class.

\subsection{The Transition Problem}
\label{DIFFUSION TRANS SEC}
Now we are ready to solve the original time-dependent problem. The main 
part of the script is the loop over time $t$ which takes the following form:
\begin{python}
  t=0
  T=Tref
  mypde=LinearPDE(mydomain)
  mypde.setValue(A=kappa*kronecker(mydomain), D=rhocp/h, d=eta, y=eta*Tref)
  while t<t_end:
        mypde.setValue(Y=q+rhocp/h*T)
        T=mypde.getSolution()
        t+=h
\end{python}
\var{kappa}, \var{rhocp}, \var{eta} and \var{Tref} are input parameters of the model.
\var{q} is the heat source in the domain and \var{h} is the time step size.
The variable \var{T} holds the current temperature.
It is used to calculate the right hand side coefficient \var{f} in the
Helmholtz \eqn{DIFFUSION HELM EQ 1}.
The statement \code{T=mypde.getSolution()} overwrites \var{T} with the
temperature of the new time step $\var{t}+\var{h}$.
To get this iterative process going we need to specify the initial temperature
distribution, which is equal to $T_{ref}$.
The \LinearPDE object \var{mypde} and the coefficients that do not change over
time are set up before the loop is entered.
In each time step only the coefficient $Y$ is reset as it depends on the
temperature of the previous time step.
This allows the PDE solver to reuse information from previous time steps as
much as possible.

The heat source $q_H$ which is defined in \eqn{DIFFUSION TEMP EQ 1b}
is \var{qc} in an area defined as a circle of radius \var{r} and center
\var{xc}, and zero outside this circle. \var{q0} is a fixed constant.
The following script defines $q_H$ as desired:
\begin{python}
  from esys.escript import length,whereNegative
  xc=[0.02, 0.002]
  r=0.001
  x=mydomain.getX()
  qH=q0*whereNegative(length(x-xc)-r)
\end{python}
\var{x} is an \class{esys.escript.Data} object which contains locations in the \Domain \var{mydomain}.
The \function{length()} function (also from the \escript module) returns the 
Euclidean norm:
\begin{equation}\label{DIFFUSION HELM EQ 3aba}
\|y\|=\sqrt{
y_{i}
y_{i}
} = \function{esys.escript.length}(y)
\end{equation}
So \code{length(x-xc)} calculates the distances of the location \var{x} to the
center of the circle \var{xc} where the heat source is acting.
Note that the coordinates of \var{xc} are defined as a list of floating point numbers.
It is automatically converted into a \Data class object before being
subtracted from \var{x}.
The function \function{whereNegative} applied to \code{length(x-xc)-r} returns
a \Data object which is equal to one where the object is negative (inside the
circle) and zero elsewhere.
After multiplication with \var{qc} we get a function with the desired property
of having value \var{qc} inside the circle and zero elsewhere.

Now we can put the components together to create the script \file{diffusion.py}
which is available in the \ExampleDirectory\index{scripts!\file{diffusion.py}}:
\begin{python}
  from esys.escript import *
  from esys.escript.linearPDEs import LinearPDE
  from esys.finley import Rectangle
  from esys.weipa import saveVTK
  #... set some parameters ...
  xc=[0.02, 0.002]
  r=0.001
  qc=50.e6
  Tref=0.
  rhocp=2.6e6
  eta=75.
  kappa=240.
  tend=5.
  # ... time, time step size and counter ...
  t=0
  h=0.1
  i=0
  #... generate domain ...
  mydomain = Rectangle(l0=0.05, l1=0.01, n0=250, n1=50)
  #... open PDE ...
  mypde=LinearPDE(mydomain)
  mypde.setSymmetryOn()
  mypde.setValue(A=kappa*kronecker(mydomain), D=rhocp/h, d=eta, y=eta*Tref)
  # ... set heat source: ....
  x=mydomain.getX()
  qH=qc*whereNegative(length(x-xc)-r)
  # ... set initial temperature ....
  T=Tref
  # ... start iteration:
  while t<tend:
        i+=1
        t+=h
        print("time step:",t)
        mypde.setValue(Y=qH+rhocp/h*T)
        T=mypde.getSolution()
        saveVTK("T.%d.vtu"%i, temp=T)
\end{python}
The script will create the files \file{T.1.vtu}, \file{T.2.vtu}, $\ldots$,
\file{T.50.vtu} in the directory where the script has been started.
The files contain the temperature distributions at time steps $1, 2, i,
\ldots, 50$ in the \VTK file format.

\begin{figure}
\centerline{\includegraphics[width=\figwidth]{DiffusionRes1}}
\centerline{\includegraphics[width=\figwidth]{DiffusionRes16}}
\centerline{\includegraphics[width=\figwidth]{DiffusionRes32}}
\centerline{\includegraphics[width=\figwidth]{DiffusionRes48}}
\caption{Results of the Temperature Diffusion Problem for Time Steps 1, 16, 32
         and 48 (top to bottom)}
\label{DIFFUSION FIG 2}
\end{figure}
\fig{DIFFUSION FIG 2} shows the result for some selected time steps.
An easy way to visualize the results is the command
\begin{verbatim}
mayavi2 -d T.1.vtu -m Surface
\end{verbatim}
Use the \emph{Configure Data} window in \mayavi to move forward and backward in time.