Документация Engee
Notebook

Реализация конечно-разностной схемы для решения уравнения Лапласа

В этом примере мы решим следующее уравнение Лапласа

$$\begin{align*} - \Delta u &= 1, \quad x \in \Omega \\ u &= 0, \quad x \in \partial \Omega. \end{align*}$$

используя 5-точечны конечно-разностную схему решения.

Создание сетки

Положим, нас интересует решение задачи на некоторой конкретной сетке, заданной в форме L-образной группы точек на плоскости.

In [ ]:
gr()
Out[0]:
Plots.GRBackend()

Сперва мы создадим простую матрицу из нулей, затем заполним нужные области единицами, добавив также "окантовку" размером в 1 элемент для задания граничных условий Дирихле. Функция spy из набора графиков gr() хорошо подходит для визуализации областей, где значения элементов матриц ненулевые.

In [ ]:
10÷2
Out[0]:
5
In [ ]:
function lshape(N)
    #N = round(Int, N/2) * 2   # убедимся, что входной параметр – четный
    L = zeros(Int, N, N)       # создаем массив из чисел типа Int со значением 0
    
    # заполним нужную область единицами
    L[2:N÷2, 2:N-1] .= 1
    L[2:N-1,   N÷2+1:N-1] .= 1
    # ÷ обозначает целочисленное деление
    # (! julia не позволяет индексировать векторы при помощи чисел типа Float)

    # Вместо единиц, подставим номера значений в матрице
    items = findall( !iszero, L )
    L[items] = 1:length(items)

    return L

end

G = lshape( 32 );
spy( G .> 0, markersize=3, markercolor=1 )
Out[0]:

Если нам будет нужно пронумеровать все эти точки, как это делает функция numgrid платформы MATLAB, воспользуемся следующим кодом:

In [ ]:
g = lshape( 10 ) .> 10
Out[0]:
10×10 BitMatrix:
 0  0  0  0  0  0  0  0  0  0
 0  0  0  0  1  1  1  1  1  0
 0  0  0  0  1  1  1  1  1  0
 0  0  0  1  1  1  1  1  1  0
 0  0  0  1  1  1  1  1  1  0
 0  0  0  0  0  1  1  1  1  0
 0  0  0  0  0  1  1  1  1  0
 0  0  0  0  0  1  1  1  1  0
 0  0  0  0  0  1  1  1  1  0
 0  0  0  0  0  0  0  0  0  0
In [ ]:
g = lshape( 10 )
Out[0]:
10×10 Matrix{Int64}:
 0  0  0   0   0   0   0   0   0  0
 0  1  5   9  13  17  25  33  41  0
 0  2  6  10  14  18  26  34  42  0
 0  3  7  11  15  19  27  35  43  0
 0  4  8  12  16  20  28  36  44  0
 0  0  0   0   0  21  29  37  45  0
 0  0  0   0   0  22  30  38  46  0
 0  0  0   0   0  23  31  39  47  0
 0  0  0   0   0  24  32  40  48  0
 0  0  0   0   0   0   0   0   0  0

Можно увидеть, какие номера элементов входной матрицы должны участвовать в вычислении каких результатов. При 5-элеметной схеме, в вычислениях элемента 1 участвуют элементы 1, 2 и 5 (соседние элементы).

Оператор Лапласа

Теперь загрузим функцию, которая задает оператор Лапласа на произвольной сетке, используя для вычислений 4-связную область.

In [ ]:
using SparseArrays, LinearAlgebra
include( "delsq.jl" ); # Создание лапласиана

Воспользуемся функцией spy чтобы лучше увидеть отдельные маркеры.

In [ ]:
L = lshape(9);
G = delsq(L);
spy( G .!= 0, colorbar=false, markersize=3, markercolor=1 )
Out[0]:

Хотя и стандартный вывод для разреженных матриц уже довольно хорошо дает увидеть области разных значений.

In [ ]:
delsq( lshape(9) )
Out[0]:
37×37 SparseMatrixCSC{Float64, Int64} with 157 stored entries:
⎡⡻⢎⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎤
⎢⠈⠪⡛⣬⡢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠈⠪⡱⣮⡀⠀⠢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠈⠻⣦⠀⠈⠢⡀⠀⠀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠈⠢⡀⠀⠻⣦⡀⠈⠢⡀⠀⠀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠈⠢⡀⠈⠻⢆⡀⠈⠢⡀⠀⠀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠈⠻⣦⡀⠈⠢⡀⠀⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠈⠛⣤⡀⠈⠂⎥
⎢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠢⡀⠈⠻⣦⡀⎥
⎣⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠈⠁⎦

И для матрицы 32*32 (выведем часть этой матрицы):

In [ ]:
G = lshape( 32 );
D = delsq( G );
spy( D .!= 0, colorbar=false, markersize=2, markershpe=:square, markercolor=1 )
Out[0]:

Сколько точек всего в этой функции (грубо говоря, сколько расчетов она задает):

In [ ]:
N = sum( G[:] .> 0 )
Out[0]:
675

Решаем PDE с граничным условием

Теперь мы готовы решить приведенное выше дифференциальное уравнение с граничным условием Дирихле (1 если точка внутри области, 0 если она на границе). Julia легко позволяет решать линейные системы уравнений с коэффициентами в виде разреженных матриц (при помощи библиотеки UMFPack – той же, что и MATLAB).

In [ ]:
N = 60                   # Зададим правую часть уравнения
L = lshape(N)
A = -delsq(L)
b = ones( size(A,1) ) / N^2

u = A \ b;          # Решаем систему линейных уравнений

Выведем контурный график этого решения:

In [ ]:
U = zeros( size(L) )
U[findall(!iszero, L)] = u

contour( U, levels=8, xlimits=(0,N), ylimits=(0,N), 
         yflip=true, c=cgrad(:turbo, rev=true),
         cbar=false, aspect_ratio=:equal )
Out[0]:

Теперь выведем решение в качестве трехмерной сетки:

In [ ]:
wireframe( U[1:3:end,1:3:end],
           zflip=true, camera=(240,30),
           fmt=:png, aspect_ratio=:equal )
Out[0]:
No description has been provided for this image

Заключение

Мы решили несложное уравнение на Г-образной расчетной сетке, которую задали при помощи написанной нами функции. Кроме того, поскольку мы использовали разреженные матрицы, наша задача должна решаться быстрее и занимать меньше памяти, чем при использовании плотных (обычных) матриц, таким образом мы, теоретически, получаем возможность решать многократно более крупные задачи.