-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathtrunc.qmd
More file actions
2263 lines (2024 loc) · 80.6 KB
/
Copy pathtrunc.qmd
File metadata and controls
2263 lines (2024 loc) · 80.6 KB
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
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
Truncation error analysis provides a widely applicable framework for
analyzing the accuracy of finite difference schemes. This type of
analysis can also be used for finite element and finite volume methods
if the discrete equations are written in finite difference form. The
result of the analysis is an asymptotic estimate of the error in the
scheme on the form $Ch^r$, where $h$ is a discretization parameter
($\Delta t$, $\Delta x$, etc.), $r$ is a number, known as the convergence
rate, and $C$ is a constant, typically dependent on the
derivatives of the exact solution.
Knowing $r$ gives understanding of the accuracy of the scheme. But
maybe even more important, a powerful verification method for computer
codes is to check that the empirically observed convergence rates in
experiments coincide with the theoretical value of $r$ found from
truncation error analysis.
The analysis
can be carried out by hand, by symbolic software, and also
numerically. All three methods will be illustrated.
From examining the symbolic expressions of the truncation error
we can add correction terms to the differential equations in order
to increase the numerical accuracy.
In general, the term truncation error refers to the discrepancy that
arises from performing a finite number of steps to approximate a
process with infinitely many steps. The term is used in a number
of contexts, including truncation of infinite series, finite
precision arithmetic, finite differences, and differential equations.
We shall be concerned with computing truncation errors arising in
finite difference formulas and in finite difference discretizations
of differential equations.
## Abstract problem setting
Consider an abstract differential equation
$$
\mathcal{L}(u)=0,
$$
where $\mathcal{L}(u)$ is some formula involving the unknown $u$ and
its derivatives. One example is $\mathcal{L}(u)=u'(t)+a(t)u(t)-b(t)$, where
$a$ and $b$ are constants or functions of time.
We can discretize the differential equation and obtain a corresponding
discrete model, here written as
$$
\mathcal{L}_{\Delta}(u) =0\tp
$$
The solution $u$ of this equation is the *numerical solution*.
To distinguish the
numerical solution from the exact solution of the differential
equation problem,
we denote the latter by $\uex$ and write the
differential equation and its discrete counterpart as
\begin{align*}
\mathcal{L}(\uex)&=0,\\
\mathcal{L}_\Delta (u)&=0\tp
\end{align*}
Initial and/or boundary conditions can usually be left out of the truncation
error analysis and are omitted in the following.
The numerical solution $u$ is, in a finite difference method, computed
at a collection of mesh points. The discrete equations represented
by the abstract equation $\mathcal{L}_\Delta (u)=0$ are usually
algebraic equations involving $u$ at some
neighboring mesh points.
## Error measures
A key issue is how accurate the numerical solution is.
The ultimate way of addressing this issue would be to compute
the error $\uex - u$ at the mesh points. This is usually extremely demanding.
In very simplified problem settings we may, however, manage to
derive formulas for the numerical solution $u$, and
therefore closed form expressions
for the error $\uex - u$. Such special cases can provide
considerable insight regarding accuracy and stability, but
the results are established for special problems.
The error $\uex -u$ can be computed empirically in special cases where
we know $\uex$. Such cases can be constructed by the method of
manufactured solutions, where we choose some exact solution $\uex = v$
and fit a source term $f$ in the governing differential equation
$\mathcal{L}(\uex)=f$ such that $\uex=v$ is a solution (i.e.,
$f=\mathcal{L}(v)$). Assuming an error model of the form $Ch^r$,
where $h$ is the discretization parameter, such as $\Delta t$ or
$\Delta x$, one can estimate the convergence rate $r$. This is a
widely applicable procedure, but the validity of the results is,
strictly speaking, tied to the chosen test problems.
Another error measure arises by asking to what extent the exact solution
$\uex$ fits the discrete equations. Clearly, $\uex$ is in general
not a solution of $\mathcal{L}_\Delta(u)=0$, but we can define
the residual
$$
R = \mathcal{L}_\Delta(\uex),
$$
and investigate how close $R$ is to zero. A small $R$ means
intuitively that the discrete equations are close to the
differential equation, and then we are tempted to think that
$u^n$ must also be close to $\uex(t_n)$.
The residual $R$ is known as the truncation error of the finite
difference scheme $\mathcal{L}_\Delta(u)=0$. It appears that the
truncation error is relatively straightforward to compute by hand or
symbolic software *without specializing the differential equation
and the discrete model to a special case*. The resulting $R$ is found
as a power series in the discretization parameters. The leading-order
terms in the series provide an asymptotic measure of the accuracy of
the numerical solution method (as the discretization parameters
tend to zero). An advantage of truncation error analysis, compared to
empirical estimation of convergence rates, or detailed analysis
of a special problem with a mathematical expression for the numerical
solution, is that the truncation error analysis reveals the
accuracy of the various building blocks in the numerical method and
how each building block impacts the overall accuracy. The analysis
can therefore be used to detect building blocks with lower accuracy
than the others.
Knowing the truncation error or other error measures is important for
verification of programs by empirically establishing convergence
rates. The forthcoming text will provide many examples on how to
compute truncation errors for finite difference discretizations of
ODEs and PDEs.
## Truncation errors in finite difference formulas {#sec-trunc-finite-differences}
The accuracy of a finite difference formula is a fundamental issue
when discretizing differential equations. We shall first go through a
particular example in detail and thereafter list the truncation error
in the most common finite difference approximation formulas.
## Example: The backward difference for $u'(t)$ {#sec-trunc-fd-backward}
Consider a backward
finite difference approximation of the first-order derivative $u'$:
$$
\lbrack D_t^- u\rbrack^n = \frac{u^{n} - u^{n-1}}{\Delta t} \approx u'(t_n) \tp
$$ {#eq-trunc-fd-bw1}
Here, $u^n$ means the value of some function $u(t)$ at a point $t_n$, and
$[D_t^-u]^n$ is the *discrete derivative* of $u(t)$ at
$t=t_n$. The discrete derivative computed by a finite difference
is, in general, not exactly equal to the derivative $u'(t_n)$. The error in
the approximation is
$$
R^n = [D^-_tu]^n - u'(t_n)\tp
$$ {#eq-trunc-fd-bw3}
The common way of calculating $R^n$ is to
1. expand $u(t)$ in a Taylor series around the point where the
derivative is evaluated, here $t_n$,
1. insert this Taylor series in (@eq-trunc-fd-bw3),
and
1. collect terms that cancel and simplify the expression.
The result is an expression for $R^n$ in terms of a power series in
$\Delta t$. The error $R^n$ is commonly referred to as the *truncation
error* of the finite difference formula.
The Taylor series formula often found in calculus books takes the form
$$
f(x+h) = \sum_{i=0}^\infty \frac{1}{i!}\frac{d^if}{dx^i}(x)h^i\tp
$$
In our application,
we expand the Taylor series around the point where the finite difference
formula approximates the derivative. The Taylor series of $u^n$ at $t_n$
is simply $u(t_n)$, while the Taylor series of $u^{n-1}$ at $t_n$ must
employ the general formula,
\begin{align*}
u(t_{n-1}) = u(t-\Delta t) &= \sum_{i=0}^\infty \frac{1}{i!}\frac{d^iu}{dt^i}(t_n)(-\Delta t)^i\\
& = u(t_n) - u'(t_n)\Delta t + {\half}u''(t_n)\Delta t^2
+ \Oof{\Delta t^3},
\end{align*}
where $\Oof{\Delta t^3}$ means a power-series in $\Delta t$ where
the lowest power is $\Delta t^3$. We assume that $\Delta t$ is small such that
$\Delta t^p \gg \Delta t^q$ if $p$ is smaller than $q$.
The details of higher-order terms
in $\Delta t$ are therefore not of much interest.
Inserting the Taylor series above in the right-hand side of
(@eq-trunc-fd-bw3) gives rise to some algebra:
\begin{align*}
[D_t^-u]^n - u'(t_n) &= \frac{u(t_n) - u(t_{n-1})}{\Delta t} - u'(t_n)\\
&= \frac{u(t_n) - (u(t_n) - u'(t_n)\Delta t + {\half}u''(t_n)\Delta t^2 + \Oof{\Delta t^3} )}{\Delta t} - u'(t_n)\\
&= -{\half}u''(t_n)\Delta t + \Oof{\Delta t^2} ),
\end{align*}
which is, according to
(@eq-trunc-fd-bw3), the truncation error:
$$
R^n = - {\half}u''(t_n)\Delta t + \Oof{\Delta t^2} ) \tp
$$
The dominating term for small $\Delta t$ is $-{\half}u''(t_n)\Delta t$,
which is proportional to $\Delta t$, and we say that the truncation error
is of *first order* in $\Delta t$.
## Example: The forward difference for $u'(t)$ {#sec-trunc-fd-forward}
We can analyze the approximation error in the forward difference
$$
u'(t_n) \approx [D_t^+ u]^n = \frac{u^{n+1}-u^n}{\Delta t},
$$
by writing
$$
R^n = [D_t^+ u]^n - u'(t_n),
$$
and expanding $u^{n+1}$ in a Taylor series around $t_n$,
$$
u(t_{n+1}) = u(t_n) + u'(t_n)\Delta t +
{\half}u''(t_n)\Delta t^2 + \Oof{\Delta t^3} \tp
$$
The result becomes
$$
R = {\half}u''(t_n)\Delta t +
\Oof{\Delta t^2},
$$
showing that also the forward difference is of first order.
## Example: The central difference for $u'(t)$ {#sec-trunc-fd-central}
For the central difference approximation,
$$
u'(t_n)\approx [ D_tu]^n, \quad [D_tu]^n =
\frac{u^{n+\half} - u^{n-\half}}{\Delta t},
$$
we write
$$
R^n = [ D_tu]^n - u'(t_n),
$$
and expand $u(t_{n+\half})$ and
$u(t_{n-\half}$ in Taylor series around the point $t_n$ where
the derivative is evaluated. We have
\begin{align*}
u(t_{n+\half}) = &u(t_n) + u'(t_n)\half\Delta t +
{\half}u''(t_n)(\half\Delta t)^2 + \\
& \frac{1}{6}u'''(t_n) (\half\Delta t)^3
+ \frac{1}{24}u''''(t_n) (\half\Delta t)^4 + \\
& \frac{1}{120}u''''(t_n) (\half\Delta t)^5 + \Oof{\Delta t^6},\\
u(t_{n-\half}) = &u(t_n) - u'(t_n)\half\Delta t +
{\half}u''(t_n)(\half\Delta t)^2 - \\
& \frac{1}{6}u'''(t_n) (\half\Delta t)^3
+ \frac{1}{24}u''''(t_n) (\half\Delta t)^4 - \\
& \frac{1}{120}u'''''(t_n) (\half\Delta t)^5 + \Oof{\Delta t^6}
\end{align*} \tp
Now,
$$
u(t_{n+\half}) - u(t_{n-\half}) = u'(t_n)\Delta t + \frac{1}{24}u'''(t_n) \Delta t^3 + \frac{1}{960}u'''''(t_n) \Delta t^5 + \Oof{\Delta t^7} \tp
$$
By collecting terms in $[D_t u]^n - u'(t_n)$ we find the truncation error
to be
$$
R^n = \frac{1}{24}u'''(t_n)\Delta t^2 + \Oof{\Delta t^4},
$$
with only even powers of $\Delta t$. Since $R\sim \Delta t^2$ we say
the centered difference is of *second order* in $\Delta t$.
## Overview of leading-order error terms in finite difference formulas {#sec-trunc-table}
Here we list the leading-order terms of the truncation errors
associated with several common finite difference formulas for the
first and second derivatives.
$$
\begin{split}
\lbrack D_tu \rbrack^n &= \frac{u^{n+\half} - u^{n-\half}}{\Delta t} = u'(t_n) + R^n,\\
R^n &= \frac{1}{24}u'''(t_n)\Delta t^2 + \Oof{\Delta t^4}
\end{split}
$$ {#eq-trunc-table-fd1-center2-eq}
$$
\begin{split}
\lbrack D_{2t}u \rbrack^n &= \frac{u^{n+1} - u^{n-1}}{2\Delta t} = u'(t_n) + R^n,\\
R^n &= \frac{1}{6}u'''(t_n)\Delta t^2 + \Oof{\Delta t^4}
\end{split}
$$ {#eq-trunc-table-fd2-center-eq}
$$
\begin{split}
\lbrack D_t^-u \rbrack^n &= \frac{u^{n} - u^{n-1}}{\Delta t} = u'(t_n) + R^n,\\
R^n &= -{\half}u''(t_n)\Delta t + \Oof{\Delta t^2}
\end{split}
$$ {#eq-trunc-table-fd1-bw-eq}
$$
\begin{split}
\lbrack D_t^+u \rbrack^n &= \frac{u^{n+1} - u^{n}}{\Delta t} = u'(t_n) + R^n,\\
R^n &= {\half}u''(t_n)\Delta t + \Oof{\Delta t^2}
\end{split}
$$ {#eq-trunc-table-fd1-fw-eq}
$$
\begin{split}
[\bar D_tu]^{n+\theta} &= \frac{u^{n+1} - u^{n}}{\Delta t} = u'(t_{n+\theta}) + R^{n+\theta},\\
R^{n+\theta} &= \half(1-2\theta)u''(t_{n+\theta})\Delta t -
\frac{1}{6}((1 - \theta)^3 - \theta^3)u'''(t_{n+\theta})\Delta t^2 +
\Oof{\Delta t^3}
\end{split}
$$ {#eq-trunc-table-fd1-theta-eq}
$$
\begin{split}
\lbrack D_t^{2-}u \rbrack^n &= \frac{3u^{n} - 4u^{n-1} + u^{n-2}}{2\Delta t} = u'(t_n) + R^n,\\
R^n &= -\frac{1}{3}u'''(t_n)\Delta t^2 + \Oof{\Delta t^3}
\end{split}
$$ {#eq-trunc-table-fd1-bw2-eq}
$$
\begin{split}
\lbrack D_tD_t u \rbrack^n &= \frac{u^{n+1} - 2u^{n} + u^{n-1}}{\Delta t^2} = u''(t_n) + R^n,\\
R^n &= \frac{1}{12}u''''(t_n)\Delta t^2 + \Oof{\Delta t^4}
\end{split}
$$ {#eq-trunc-table-fd1-center-eq}
It will also be convenient to have the truncation errors for various
means or averages. The weighted arithmetic mean leads to
$$
\begin{split}
[\overline{u}^{t,\theta}]^{n+\theta}
& = \theta u^{n+1} + (1-\theta)u^n =
u(t_{n+\theta}) + R^{n+\theta},
\\
R^{n+\theta} &= {\half}u''(t_{n+\theta})\Delta t^2\theta (1-\theta) +
\Oof{\Delta t^3}\tp
\end{split}
$$ {#eq-trunc-table-avg-theta-eq}
The standard arithmetic mean follows from this formula when
$\theta=\half$. Expressed at point $t_n$ we get
$$
\begin{split}
[\overline{u}^{t}]^{n} &= \half(u^{n-\half} + u^{n+\half})
= u(t_n) + R^{n},
\\
R^{n} &= \frac{1}{8}u''(t_{n})\Delta t^2 + \frac{1}{384}u''''(t_n)\Delta t^4
+ \Oof{\Delta t^6}\tp
\end{split}
$$ {#eq-trunc-table-avg-arith-eq}
The geometric mean also has an error $\Oof{\Delta t^2}$:
$$
\begin{split}
[\overline{u^2}^{t,g}]^{n} &= u^{n-\half}u^{n+\half} = (u^n)^2 + R^n,
\\
R^n &= - \frac{1}{4}u'(t_n)^2\Delta t^2 + \frac{1}{4}u(t_n)u''(t_n)\Delta t^2
+ \Oof{\Delta t^4}\tp
\end{split}
$$ {#eq-trunc-table-avg-geom-eq}
The harmonic mean is also second-order accurate:
$$
\begin{split}
[\overline{u}^{t,h}]^{n} &= u^n = \frac{2}{\frac{1}{u^{n-\half}} + \frac{1}{u^{n+\half}}}
+ R^{n+\half},
\\
R^n &= - \frac{u'(t_n)^2}{4u(t_n)}\Delta t^2 + \frac{1}{8}u''(t_n)\Delta t^2\tp
\end{split}
$$ {#eq-trunc-table-avg-harm-eq}
## Software for computing truncation errors {#sec-trunc-sympy}
We can use `sympy` to aid calculations with Taylor series.
The derivatives can be defined as symbols, say `D3f` for the
3rd derivative of some function $f$. A truncated Taylor series
can then be written as `f + D1f*h + D2f*h**2/2`. The following
class takes some symbol `f` for the function in question
and makes a list of symbols for the derivatives. The
`__call__` method computes the symbolic form of the series
truncated at `num_terms` terms.
```python
import sympy as sym
class TaylorSeries:
"""Class for symbolic Taylor series."""
def __init__(self, f, num_terms=4):
self.f = f
self.N = num_terms
self.df = [f]
for i in range(1, self.N + 1):
self.df.append(sym.Symbol("D%d%s" % (i, f.name)))
def __call__(self, h):
"""Return the truncated Taylor series at x+h."""
terms = self.f
for i in range(1, self.N + 1):
terms += sym.Rational(1, sym.factorial(i)) * self.df[i] * h**i
return terms
```
We may, for example, use this class to compute the truncation error
of the Forward Euler finite difference formula:
```python
>>> from truncation_errors import TaylorSeries
>>> from sympy import *
>>> u, dt = symbols('u dt')
>>> u_Taylor = TaylorSeries(u, 4)
>>> u_Taylor(dt)
D1u*dt + D2u*dt**2/2 + D3u*dt**3/6 + D4u*dt**4/24 + u
>>> FE = (u_Taylor(dt) - u)/dt
>>> FE
(D1u*dt + D2u*dt**2/2 + D3u*dt**3/6 + D4u*dt**4/24)/dt
>>> simplify(FE)
D1u + D2u*dt/2 + D3u*dt**2/6 + D4u*dt**3/24
```
The truncation error consists of the terms after the first one ($u'$).
The module file [`trunc/truncation_errors.py`](https://github.com/devitocodes/devito_book/tree/main/src/trunc/truncation_errors.py) contains another class `DiffOp` with symbolic expressions for
most of the truncation errors listed in the previous section.
For example:
```python
>>> from truncation_errors import DiffOp
>>> from sympy import *
>>> u = Symbol('u')
>>> diffop = DiffOp(u, independent_variable='t')
>>> diffop['geometric_mean']
-D1u**2*dt**2/4 - D1u*D3u*dt**4/48 + D2u**2*dt**4/64 + ...
>>> diffop['Dtm']
D1u + D2u*dt/2 + D3u*dt**2/6 + D4u*dt**3/24
>>> >>> diffop.operator_names()
['geometric_mean', 'harmonic_mean', 'Dtm', 'D2t', 'DtDt',
'weighted_arithmetic_mean', 'Dtp', 'Dt']
```
The indexing of `diffop` applies names that correspond to the operators:
`Dtp` for $D^+_t$, `Dtm` for $D_t^-$, `Dt` for $D_t$, `D2t` for
$D_{2t}$, `DtDt` for $D_tD_t$.
## Truncation errors in exponential decay ODE {#sec-trunc-decay}
We shall now compute the truncation error of a finite difference
scheme for a differential equation.
Our first problem involves the following
linear ODE that models exponential decay,
$$
u'(t)=-au(t)\tp
$$ {#eq-trunc-decay-ode}
## Forward Euler scheme {#sec-trunc-decay-FE}
We begin with the Forward Euler scheme for discretizing (@eq-trunc-decay-ode):
$$
\lbrack D_t^+ u = -au \rbrack^n \tp
$$ {#eq-trunc-decay-FE-scheme}
The idea behind the truncation error computation is to insert
the exact solution $\uex$ of the differential equation problem
(@eq-trunc-decay-ode)
in the discrete equations (@eq-trunc-decay-FE-scheme) and find the residual
that arises because $\uex$ does not solve the discrete equations.
Instead, $\uex$ solves the discrete equations with a residual $R^n$:
$$
[D_t^+ \uex + a\uex = R]^n \tp
$$ {#eq-trunc-decay-FE-uex}
From @eq-trunc-table-fd1-fw-eq it follows that
$$
[D_t^+ \uex]^n = \uex'(t_n) +
\half\uex''(t_n)\Delta t + \Oof{\Delta t^2},
$$
which inserted in (@eq-trunc-decay-FE-uex) results in
$$
\uex'(t_n) +
\half\uex''(t_n)\Delta t + \Oof{\Delta t^2}
+ a\uex(t_n) = R^n \tp
$$
Now, $\uex'(t_n) + a\uex^n = 0$ since $\uex$ solves the differential equation.
The remaining terms constitute the residual:
$$
R^n = \half\uex''(t_n)\Delta t + \Oof{\Delta t^2} \tp
$$ {#eq-trunc-decay-FE-R}
This is the truncation error $R^n$ of the Forward Euler scheme.
Because $R^n$ is proportional to $\Delta t$, we say that
the Forward Euler scheme is of first order in $\Delta t$.
However, the truncation error
is just one error measure, and it is not equal to the true error
$\uex^n - u^n$. For this simple model problem we can compute
a range of different error measures for the Forward Euler scheme,
including the true error $\uex^n - u^n$, and all of them
have dominating terms proportional to $\Delta t$.
## Crank-Nicolson scheme {#sec-trunc-decay-CN}
For the Crank-Nicolson scheme,
$$
[D_t u = -au]^{n+\half},
$$ {#eq-trunc-decay-CN-scheme}
we compute the truncation error by inserting the exact solution of
the ODE and adding a residual $R$,
$$
[D_t \uex + a\overline{\uex}^{t} = R]^{n+\half} \tp
$$ {#eq-trunc-decay-CN-scheme-R}
The term $[D_t\uex]^{n+\half}$ is easily computed
from @eq-trunc-table-fd1-center-eq
by replacing $n$
with $n+{\half}$ in the formula,
$$
\lbrack D_t\uex\rbrack^{n+\half} = \uex'(t_{n+\half}) +
\frac{1}{24}\uex'''(t_{n+\half})\Delta t^2 + \Oof{\Delta t^4}\tp
$$
The arithmetic mean is related to $u(t_{n+\half})$ by
@eq-trunc-table-avg-arith-eq so
$$
[a\overline{\uex}^{t}]^{n+\half}
= \uex(t_{n+\half}) + \frac{1}{8}\uex''(t_{n})\Delta t^2 +
+ \Oof{\Delta t^4}\tp
$$
Inserting these expressions in (@eq-trunc-decay-CN-scheme-R) and
observing that $\uex'(t_{n+\half}) +a\uex^{n+\half} = 0$, because
$\uex(t)$ solves the ODE $u'(t)=-au(t)$ at any point $t$,
we find that
$$
R^{n+\half} = \left(
\frac{1}{24}\uex'''(t_{n+\half}) + \frac{1}{8}\uex''(t_{n})
\right)\Delta t^2 + \Oof{\Delta t^4}
$$
Here, the truncation error is of second order because the leading
term in $R$ is proportional to $\Delta t^2$.
At this point it is wise to redo some of the computations above
to establish the truncation error of the Backward Euler scheme,
see Exercise @sec-trunc-exer-decay-BE.
## The $\theta$-rule {#sec-trunc-decay-theta}
We may also compute the truncation error of the $\theta$-rule,
$$
[\bar D_t u = -a\overline{u}^{t,\theta}]^{n+\theta} \tp
$$
Our computational task is to find $R^{n+\theta}$ in
$$
[\bar D_t \uex + a\overline{\uex}^{t,\theta} = R]^{n+\theta} \tp
$$
From @eq-trunc-table-fd1-theta-eq and
@eq-trunc-table-avg-theta-eq we get
expressions for the terms with $\uex$.
Using that $\uex'(t_{n+\theta}) + a\uex(t_{n+\theta})=0$,
we end up with
\begin{align}
R^{n+\theta}
=
&({\half}-\theta)\uex''(t_{n+\theta})\Delta t +
\half\theta (1-\theta)\uex''(t_{n+\theta})\Delta t^2 + \nonumber\\
& \half(\theta^2 -\theta + 3)\uex'''(t_{n+\theta})\Delta t^2
+ \Oof{\Delta t^3}
\end{align}
For $\theta =\half$ the first-order term vanishes and the scheme is of
second order, while for $\theta\neq \half$ we only have a first-order scheme.
## Using symbolic software {#sec-trunc-decay-software}
The previously mentioned `truncation_error` module can be used to
automate the Taylor series expansions and the process of
collecting terms. Here is an example on possible use:
```python
from truncation_error import DiffOp
from sympy import *
def decay():
u, a = symbols('u a')
diffop = DiffOp(u, independent_variable='t',
num_terms_Taylor_series=3)
D1u = diffop.D(1) # symbol for du/dt
ODE = D1u + a*u # define ODE
FE = diffop['Dtp'] + a*u
CN = diffop['Dt' ] + a*u
BE = diffop['Dtm'] + a*u
theta = diffop['barDt'] + a*diffop['weighted_arithmetic_mean']
theta = sm.simplify(sm.expand(theta))
R = {'FE': FE-ODE, 'BE': BE-ODE, 'CN': CN-ODE,
'theta': theta-ODE}
return R
```
The returned dictionary becomes
```python
decay: {
'BE': D2u*dt/2 + D3u*dt**2/6,
'FE': -D2u*dt/2 + D3u*dt**2/6,
'CN': D3u*dt**2/24,
'theta': -D2u*a*dt**2*theta**2/2 + D2u*a*dt**2*theta/2 -
D2u*dt*theta + D2u*dt/2 + D3u*a*dt**3*theta**3/3 -
D3u*a*dt**3*theta**2/2 + D3u*a*dt**3*theta/6 +
D3u*dt**2*theta**2/2 - D3u*dt**2*theta/2 + D3u*dt**2/6,
}
```
The results are in correspondence with our hand-derived expressions.
## Empirical verification of the truncation error {#sec-trunc-decay-estimate-R}
The task of this section is to demonstrate how we can compute
the truncation error $R$ numerically. For example, the truncation
error of the Forward Euler scheme applied to the decay ODE $u'=-ua$
is
$$
R^n = [D_t^+\uex + a\uex]^n \tp
$$ {#eq-trunc-decay-FE-R-comp}
If we happen to know the exact solution $\uex(t)$, we can easily evaluate
$R^n$ from the above formula.
To estimate how $R$ varies with the discretization parameter $\Delta
t$, which has been our focus in the previous mathematical derivations,
we first make the assumption that $R=C\Delta t^r$ for
appropriate constants $C$ and
$r$ and small enough $\Delta t$. The rate $r$ can be estimated from a series
of experiments where $\Delta t$ is varied. Suppose we have
$m$ experiments $(\Delta t_i, R_i)$, $i=0,\ldots,m-1$.
For two consecutive experiments $(\Delta t_{i-1}, R_{i-1})$
and $(\Delta t_i, R_i)$, a corresponding $r_{i-1}$ can be estimated by
$$
r_{i-1} = \frac{\ln (R_{i-1}/R_i)}{\ln (\Delta t_{i-1}/\Delta t_i)},
$$ {#eq-trunc-R-empir1}
for $i=1,\ldots,m-1$. Note that the truncation error $R_i$ varies
through the mesh, so (@eq-trunc-R-empir1) is to be applied
pointwise. A complicating issue is that $R_i$ and $R_{i-1}$ refer to
different meshes. Pointwise comparisons of the truncation error at a
certain point in all meshes therefore requires any
computed $R$ to be restricted to the *coarsest mesh* and that
all finer meshes contain all the points in the coarsest mesh.
Suppose we have
$N_0$ intervals in the coarsest mesh. Inserting a superscript $n$ in
(@eq-trunc-R-empir1), where $n$ counts mesh points in the coarsest
mesh, $n=0,\ldots,N_0$, leads to the formula
$$
r_{i-1}^n = \frac{\ln (R_{i-1}^n/R_i^n)}{\ln (\Delta t_{i-1}/\Delta t_i)} \tp
$$ {#eq-trunc-R-empir2}
Experiments are most conveniently defined by $N_0$ and a number of
refinements $m$. Suppose each mesh has twice as many cells $N_i$ as the previous
one:
$$
N_i = 2^iN_0,\quad \Delta t_i = TN_i^{-1},
$$
where $[0,T]$ is the total time interval for the computations.
Suppose the computed $R_i$ values on the mesh with $N_i$ intervals
are stored in an array `R[i]` (`R` being a list of arrays, one for
each mesh). Restricting this $R_i$ function to
the coarsest mesh means extracting every $N_i/N_0$ point and is done
as follows:
```python
stride = N[i]/N_0
R[i] = R[i][::stride]
```
The quantity `R[i][n]` now corresponds to $R_i^n$.
In addition to estimating $r$ for the pointwise values
of $R=C\Delta t^r$, we may also consider an integrated quantity
on mesh $i$,
$$
R_{I,i} = \left(\Delta t_i\sum_{n=0}^{N_i} (R_i^n)^2\right)^\half\approx \int_0^T R_i(t)dt \tp
$$
The sequence $R_{I,i}$, $i=0,\ldots,m-1$, is also expected to
behave as $C\Delta t^r$, with the same $r$ as for the pointwise quantity
$R$, as $\Delta t\rightarrow 0$.
The function below computes the $R_i$ and $R_{I,i}$ quantities, plots
them and compares with
the theoretically derived truncation error (`R_a`) if available.
```python
import numpy as np
def estimate(truncation_error, T, N_0, m, makeplot=True):
"""
Compute the truncation error in a problem with one independent
variable, using m meshes, and estimate the convergence
rate of the truncation error.
The user-supplied function truncation_error(dt, N) computes
the truncation error on a uniform mesh with N intervals of
length dt::
R, t, R_a = truncation_error(dt, N)
where R holds the truncation error at points in the array t,
and R_a are the corresponding theoretical truncation error
values (None if not available).
The truncation_error function is run on a series of meshes
with 2**i*N_0 intervals, i=0,1,...,m-1.
The values of R and R_a are restricted to the coarsest mesh.
and based on these data, the convergence rate of R (pointwise)
and time-integrated R can be estimated empirically.
"""
N = [2**i * N_0 for i in range(m)]
R_I = np.zeros(m) # time-integrated R values on various meshes
R = [None] * m # time series of R restricted to coarsest mesh
R_a = [None] * m # time series of R_a restricted to coarsest mesh
dt = np.zeros(m)
legends_R = []
legends_R_a = [] # all legends of curves
for i in range(m):
dt[i] = T / float(N[i])
R[i], t, R_a[i] = truncation_error(dt[i], N[i])
R_I[i] = np.sqrt(dt[i] * np.sum(R[i] ** 2))
if i == 0:
t_coarse = t # the coarsest mesh
stride = N[i] / N_0
R[i] = R[i][::stride] # restrict to coarsest mesh
R_a[i] = R_a[i][::stride]
if makeplot:
plt.figure(1)
plt.plot(t_coarse, R[i])
plt.yscale("log")
legends_R.append("N=%d" % N[i])
plt.figure(2)
plt.plot(t_coarse, R_a[i] - R[i])
plt.yscale("log")
legends_R_a.append("N=%d" % N[i])
if makeplot:
plt.figure(1)
plt.xlabel("time")
plt.ylabel("pointwise truncation error")
plt.legend(legends_R)
plt.savefig("R_series.png")
plt.savefig("R_series.pdf")
plt.figure(2)
plt.xlabel("time")
plt.ylabel("pointwise error in estimated truncation error")
plt.legend(legends_R_a)
plt.savefig("R_error.png")
plt.savefig("R_error.pdf")
r_R_I = convergence_rates(dt, R_I)
print("R integrated in time; r:", end=" ")
print(" ".join(["%.1f" % r for r in r_R_I]))
R = np.array(R) # two-dim. numpy array
r_R = [convergence_rates(dt, R[:, n])[-1] for n in range(len(t_coarse))]
```
The first `makeplot` block demonstrates how to build up two figures
in parallel, using `plt.figure(i)` to create and switch to figure number
`i.` Figure numbers start at 1. A logarithmic scale is used on the
$y$ axis since we expect that $R$ as a function of time (or mesh points)
is exponential. The reason is that the theoretical estimate
(@eq-trunc-decay-FE-R) contains $\uex''$, which for the present model
goes like $e^{-at}$. Taking the logarithm makes a straight line.
The code follows closely the previously
stated mathematical formulas, but the statements for computing the convergence
rates might deserve an explanation.
The generic help function `convergence_rate(h, E)` computes and returns
$r_{i-1}$, $i=1,\ldots,m-1$ from (@eq-trunc-R-empir2),
given $\Delta t_i$ in `h` and
$R_i^n$ in `E`:
```python
def convergence_rates(h, E):
from math import log
r = [log(E[i]/E[i-1])/log(h[i]/h[i-1])
for i in range(1, len(h))]
return r
```
Calling `r_R_I = convergence_rates(dt, R_I)` computes the sequence
of rates $r_0,r_1,\ldots,r_{m-2}$ for the model $R_I\sim\Delta t^r$,
while the statements
```python
R = np.array(R) # two-dim. numpy array
r_R = [convergence_rates(dt, R[:,n])[-1]
for n in range(len(t_coarse))]
```
compute the final rate $r_{m-2}$ for $R^n\sim\Delta t^r$ at each mesh
point $t_n$ in the coarsest mesh. This latter computation deserves
more explanation. Since `R[i][n]` holds the estimated
truncation error $R_i^n$ on mesh $i$, at point $t_n$ in the coarsest mesh,
`R[:,n]` picks out the sequence $R_i^n$ for $i=0,\ldots,m-1$.
The `convergence_rate` function computes the rates at $t_n$, and by
indexing `[-1]` on the returned array from `convergence_rate`,
we pick the rate $r_{m-2}$, which we believe is the best estimation since
it is based on the two finest meshes.
The `estimate` function is available in a module
[`trunc_empir.py`](https://github.com/devitocodes/devito_book/tree/main/src/trunc/trunc_empir.py).
Let us apply this function to estimate the truncation
error of the Forward Euler scheme. We need a function `decay_FE(dt, N)`
that can compute (@eq-trunc-decay-FE-R-comp) at the
points in a mesh with time step `dt` and `N` intervals:
```python
import numpy as np
import trunc_empir
def decay_FE(dt, N):
dt = float(dt)
t = np.linspace(0, N * dt, N + 1)
u_e = I * np.exp(-a * t) # exact solution, I and a are global
u = u_e # naming convention when writing up the scheme
R = np.zeros(N)
for n in range(0, N):
R[n] = (u[n + 1] - u[n]) / dt + a * u[n]
R_a = 0.5 * I * (-a) ** 2 * np.exp(-a * t) * dt
return R, t[:-1], R_a[:-1]
if __name__ == "__main__":
I = 1
a = 2 # global variables needed in decay_FE
trunc_empir.estimate(decay_FE, T=2.5, N_0=6, m=4, makeplot=True)
```
The estimated rates for the integrated truncation error $R_I$ become
1.1, 1.0, and 1.0 for this sequence of four meshes. All the rates
for $R^n$, computed as `r_R`, are also very close to 1 at all mesh points.
The agreement between the theoretical formula (@eq-trunc-decay-FE-R)
and the computed quantity (ref(@eq-trunc-decay-FE-R-comp)) is
very good, as illustrated in
Figures @fig-trunc-fig-FE-rates and @fig-trunc-fig-FE-error.
The program [`trunc_decay_FE.py`](https://github.com/devitocodes/devito_book/tree/main/src/trunc/trunc_decay_FE.py)
was used to perform the simulations and it can easily be modified to
test other schemes (see also Exercise @sec-trunc-exer-decay-estimate).
{#fig-trunc-fig-FE-rates width="400px"}
{#fig-trunc-fig-FE-error width="400px"}
## Increasing the accuracy by adding correction terms {#sec-trunc-decay-corr}
Now we ask the question: can we add terms in the differential equation
that can help increase the order of the truncation error? To be precise,
let us revisit the Forward Euler scheme for $u'=-au$, insert the
exact solution $\uex$, include a residual $R$, but also include
new terms $C$:
$$
\lbrack D_t^+ \uex + a\uex = C + R \rbrack^n\tp
$$ {#eq-trunc-decay-FE-corr}
Inserting the Taylor expansions for $[D_t^+\uex]^n$ and keeping
terms up to 3rd order in $\Delta t$ gives the equation
$$
\half\uex''(t_n)\Delta t - \frac{1}{6}\uex'''(t_n)\Delta t^2
+ \frac{1}{24}\uex''''(t_n)\Delta t^3
+ \Oof{\Delta t^4} = C^n + R^n\tp
$$
Can we find $C^n$ such that $R^n$ is $\Oof{\Delta t^2}$?
Yes, by setting
$$
C^n = \half\uex''(t_n)\Delta t,
$$
we manage to cancel the first-order term and
$$
R^n = \frac{1}{6}\uex'''(t_n)\Delta t^2 + \Oof{\Delta t^3}\tp
$$
The correction term $C^n$ introduces $\half\Delta t u''$
in the discrete equation, and we have to get rid of the derivative
$u''$. One idea is to approximate $u''$ by a second-order accurate finite
difference formula, $u''\approx (u^{n+1}-2u^n+u^{n-1})/\Delta t^2$,
but this introduces an additional time level
with $u^{n-1}$. Another approach is to rewrite $u''$ in terms of $u'$
or $u$ using the ODE:
$$
u'=-au\quad\Rightarrow\quad u''=-au' = -a(-au)= a^2u\tp
$$
This means that we can simply set
$C^n = {\half}a^2\Delta t u^n$. We can then either
solve the discrete equation
$$
[D_t^+ u = -au + {\half}a^2\Delta t u]^n,
$$ {#eq-trunc-decay-corr-FE-discrete}
or we can equivalently discretize the perturbed ODE
$$
u' = -\hat au ,\quad \hat a = a(1 - {\half}a\Delta t),
$$ {#eq-trunc-decay-corr-FE-ODE}
by a Forward Euler method. That is, we replace the original coefficient
$a$ by the perturbed coefficient $\hat a$. Observe that
$\hat a\rightarrow a$ as $\Delta t\rightarrow 0$.
The Forward Euler method applied to (@eq-trunc-decay-corr-FE-ODE)
results in
$$
[D_t^+ u = -a(1 - {\half}a\Delta t)u]^n\tp
$$
We can control our computations and verify that the truncation error
of the scheme above is indeed $\Oof{\Delta t^2}$.
Another way of revealing the fact that the perturbed ODE leads
to a more accurate solution is to look at the amplification factor.
Our scheme can be written as
$$
u^{n+1} = Au^n,\quad A = 1-\hat a\Delta t = 1 - p + {\half}p^2,\quad p=a\Delta t,
$$
The amplification factor $A$ as a function of $p=a\Delta t$ is seen to be
the first three terms of the Taylor series for the exact amplification
factor $e^{-p}$. The Forward Euler scheme for $u=-au$ gives only the
first two terms $1-p$ of the Taylor series for $e^{-p}$. That is,
using $\hat a$ increases the order of the accuracy in the amplification factor.
Instead of replacing $u''$ by $a^2u$, we use the relation
$u''=-au'$ and add a term $-{\half}a\Delta t u'$
in the ODE:
$$
u'=-au - {\half}a\Delta t u'\quad\Rightarrow\quad
\left( 1 + {\half}a\Delta t\right) u' = -au\tp
$$
Using a Forward Euler method results in
$$
\left( 1 + {\half}a\Delta t\right)\frac{u^{n+1}-u^n}{\Delta t}
= -au^n,
$$
which after some algebra can be written as
$$
u^{n+1} = \frac{1 - {\half}a\Delta t}{1+{\half}a\Delta t}u^n\tp
$$
This is the same formula as the one arising from a Crank-Nicolson
scheme applied to $u'=-au$!
It is now recommended to do Exercise @sec-trunc-exer-decay-corr-BE and
repeat the above steps to see what kind of correction term is needed
in the Backward Euler scheme to make it second order.
The Crank-Nicolson scheme is a bit more challenging to analyze, but
the ideas and techniques are the same. The discrete equation reads
$$
[D_t u = -au ]^{n+\half},
$$
and the truncation error is defined through
$$
[D_t \uex + a\overline{\uex}^{t} = C + R]^{n+\half},
$$
where we have added a correction term. We need to Taylor expand both
the discrete derivative and the arithmetic mean with aid of
@eq-trunc-table-fd1-center-eq and
@eq-trunc-table-avg-arith-eq, respectively.
The result is
$$
\frac{1}{24}\uex'''(t_{n+\half})\Delta t^2 + \Oof{\Delta t^4}
+ \frac{a}{8}\uex''(t_{n+\half})\Delta t^2 + \Oof{\Delta t^4} = C^{n+\half} + R^{n+\half}\tp
$$
The goal now is to make $C^{n+\half}$ cancel the $\Delta t^2$ terms:
$$
C^{n+\half} =
\frac{1}{24}\uex'''(t_{n+\half})\Delta t^2
+ \frac{a}{8}\uex''(t_{n})\Delta t^2\tp
$$
Using $u'=-au$, we have that $u''=a^2u$, and we find that $u'''=-a^3u$.
We can therefore solve the perturbed ODE problem
$$
u' = -\hat a u,\quad \hat a = a(1 - \frac{1}{12}a^2\Delta t^2),
$$
by the Crank-Nicolson scheme and obtain a method that is of fourth
order in $\Delta t$. Exercise @sec-trunc-exer-decay-corr-verify
encourages you to implement these correction terms and calculate
empirical convergence rates to verify that higher-order accuracy
is indeed obtained in real computations.
## Extension to variable coefficients
Let us address the decay ODE with variable coefficients,
$$
u'(t) = -a(t)u(t) + b(t),
$$
discretized by the Forward Euler scheme,
$$
[D_t^+ u = -au + b]^n \tp
$$
The truncation error $R$ is as always found by inserting the exact
solution $\uex(t)$ in the discrete scheme:
$$
[D_t^+ \uex + a\uex - b = R]^n \tp
$$
Using @eq-trunc-table-fd1-fw-eq,
$$
\uex'(t_n) - \half\uex''(t_n)\Delta t + \Oof{\Delta t^2}
+ a(t_n)\uex(t_n) - b(t_n) = R^n \tp
$$
Because of the ODE,
$$
\uex'(t_n) + a(t_n)\uex(t_n) - b(t_n) =0,
$$
we are left with the result
$$
R^n = -\half\uex''(t_n)\Delta t + \Oof{\Delta t^2} \tp
$$ {#eq-trunc-decay-vc-R}
We see that the variable coefficients do not pose any additional difficulties
in this case. Exercise @sec-trunc-exer-decay-varcoeff-CN takes the
analysis above one step further to the Crank-Nicolson scheme.
## Exact solutions of the finite difference equations
Having a mathematical expression for the numerical solution is very
valuable in program verification, since we then know the exact numbers
that the program should produce. Looking at the various
formulas for the truncation errors in
@eq-trunc-table-fd1-center-eq and
@eq-trunc-table-avg-harm-eq in
Section @sec-trunc-table, we see that all but two of
the $R$ expressions contain a second or higher order derivative
of $\uex$. The exceptions are the geometric and harmonic
means where the truncation
error involves $\uex'$ and even $\uex$ in case of the harmonic mean.
So, apart from these two means,
choosing $\uex$ to be a linear function of
$t$, $\uex = ct+d$ for constants $c$ and $d$, will make
the truncation error vanish since $\uex''=0$. Consequently,
the truncation error of a finite difference scheme will be zero