Некоторое время назад я искал описание операций, процессов, происходящих в библиотеке численного моделирования OpenFOAM. Нашел много абстрактных описаний работы метода конечных объёмов, классических разностных схем, различных физических уравнений. Мне же хотелось узнать более детально — откуда в таком-то выходном файле на такой-то итерации получились эти значения, какие выражения стоят за теми или иными параметрами в файлах настроек fvSchemes, fvSolution?
Для тех, кому это тоже интересно — эта статья. Те, кто хорошо знаком с OpenFOAM или с методами в нём реализованными — пишите о найденных ошибках и неточностях в личку.
На хабре уже была пара статей про OpenFOAM:
OpenFOAM на практике
OpenFOAM с точки зрения программиста-физика
Поэтому не буду останавливаться на том, что это «открытая (GPL) платформа для численнного моделирования, предназначенная для моделирования, связанного с решением уравнений в частных производных методом конечных объёмов, и широко используемая для решения задач механики сплошных сред».
Сегодня я на простом примере опишу операции, которые происходят в ходе расчёта в OpenFOAM.
Итак, дана геометрия — куб со стороной 1 метр:

Перед нами стоит задача смоделировать поток-распространение некоторого скалярного поля (температура, количество вещества), который задаётся следующим уравнением переноса (1) внутри объема тела.
(1)
,
где
скалярная величина, например, выражает температуру [K] или концентрацию некоторого вещества, а
выражает перенос вещества, массовый поток [кг/с].
Это уравнение, например, используют для моделирования распространения тепла
,
где k — теплопроводность, а
— температура [K].
Граничные условия задачи следующие: есть грань-вход, грань-выход, остальные грани — гладкие стенки.

Наша сетка будет очень простая — делим куб на 5 равных ячеек вдоль оси Z.

Метод конечных объёмов предусматривает, что (1) в интегральной форме (2) будет выполняться для каждого конечного объёма
.
(2)
,
где
— геометрический центр конечного объёма.
Упростим, преобразуем первое слагаемое выражения (2) следующим образом:
(2.1) (HJ-3.12)*



Как видно — мы приняли, что скалярная величина изменяется внутри конечного объема линейно и значение величины
в некоторой точке
внутри конечного объёма можно вычислить как:
(2.2)
,
где
Для упрощения второго слагаемого выражения (2) используем обобщённую теорему Гаусса-Остроградского: интеграл от дивергенции векторного поля
по объёму
, равен потоку вектора через поверхность
, ограничивающую данный объём. На человеческом языке «сумма всех потоков в/из конечного объема равна сумме потоков через грани этого конечного объема»:
(2.3)
,
где
замкнутая поверхность, ограничивающая объём
,
— вектор, направленный по нормали от объёма
.
Учитывая то, что конечный объём ограничен набором плоских граней, можно выражение (2.3) преобразовать к сумме интегралов по поверхности:
(2.4) (HJ-3.13)
,
где
выражает значение переменной в центре грани,
— вектор площади, выходит из центра грани, направлен в сторону от ячейки (локально), в сторону от ячейки с меньшим индексом к ячейке с большим индексом (глобально).
Чтобы не хранить одни и те же параметры вектора
два раза, т.к. очевидно, что у двух соседних ячеек вектор-нормали к грани между ячейками, направленный в сторону От центра ячейки будет различаться только направлением-знаком. Поэтому было создано owner-neighbour отношение между гранью и ячейкой. Если вектор площади
(глобальный, положительное направление от ячейки с меньшим индексом к ячейке с большим индексом) указывает ОТ центра
ячейки такое отношение между ячейкой и вектором
, а точнее между ячейкой и гранью, обозначается owner). В случае если этот вектор
указывает внутрь рассматриваемой ячейки, то отношение neighbour. Направление влияет на знак величины (+ для owner и — для neighbour) и это важно при суммировании см. далее.

(HJ-3.16)

Значение
в центре грани вычисляется через значения
в центрах прилегающих ячеек — способ такого выражения носит название разностной схемы. В OpenFOAM тип разностной схемы задается в файле /system/fvSchemes:
Gauss — означает, что выбрана центральная разностная схема;
linear — означает, что интерполяция с центров ячеек на центры граней будет происходить линейно.

Допустим, что наша скалярная величина изменяется внутри конечного объема от центра к граням линейно. Тогда аппроксимированное в центре грани значение будет вычисляться согласно формуле:

Где веса
и
рассчитываются как

Где
— объемы ячеек.
Для случаев скошенных ячеек существуют более сложные формулы расчета весов аппроксимации.
Таким образом, значения phi_f в центрах граней ячеек вычисляются на основе значений в центрах ячеек. Значения градиентов grad(phi) вычисляются на основе значений phi_f.
И весь этот алгоритм может быть представлен в виде следующего псевдокода.

Учитывая (2.1) и (2.4) выражение (2) принимает вид:
(3)

Согласно методу конечных объёмов проводится дискретизация по времени и выражение (3) записывается как:
(4)

Проинтегрируем (4):
(4.1)

Разделим левую и правую часть на
:
(5)

Теперь мы можем получить систему линейных уравнений для каждого конечного объёма
.
Ниже представлена нумерация узлов сетки, которую мы будем использовать.

Нумерация узлов-центров ячеек (50, 51 — центры граничных граней):

Нумерация узлов-центров граней:

Объемы элементов:

Коэффициенты интерполяции, необходимые для вычисления значений на гранях ячеек. Индекс «e» обозначает «правая грань ячейки». Правая относительно вида, как на рисунке «Нумерация узлов-центров ячеек»:

Для P = 0.
Выражение (5), описывающее поведение величины

будет преобразовано в систему линейных алгебраических уравнений, каждое из которых вида:

или, согласно индексам точек на гранях

А еще все потоки в/из ячейки могут быть выражены в виде суммы

где, например,
— коэффициент линеаризации потока в точке-центре ячейки E,
— коэффициент линеаризации потока в точке-центре грани,
— нелинейная часть (например, константа).
Согласно нумерации граней выражение примет вид:

С учетом граничных условий для элемента P_0 линейное алгебраическое уравнение может быть представлено в виде

… подставим ранее полученные коэффициенты…

Поток из inlet'a направлен в ячейку, поэтому имеет отрицательный знак.

Так как у нас в управляющем выражении присутствует кроме диффузионного еще и временной член, но конечное уравнение выглядит как


Для P = 1.

Для P = 4.

Система линейных алгебраических уравнений (СЛАУ) может быть представлена в матричном виде как
,
где
Далее полученная СЛАУ решается решателем, указанным в fvSchemes.
И в итоге получается вектор значений
на основе которого получаются значения для вектора



Затем вектор
подставляется в СЛАУ
и происходит новая итерация расчёта вектора
.
И так до тех пор, пока невязка не достигнет требуемых пределов.
* Некоторые уравнения в этой статье взяты из диссертации Ясака Хрвое (HJ — номер уравнения) и если кому-то захочется прочитать про них подробнее (http://powerlab.fsb.hr/ped/kturbo/OpenFOAM/docs/HrvojeJasakPhD.pdf)
Скачать файлы задачи можно здесь:
github.com/j-avdeev/case
Файлы решателя:
github.com/j-avdeev/matrHyper1Foam
В качестве бонуса — видео, как распространяется концентрация
.
Для тех, кому это тоже интересно — эта статья. Те, кто хорошо знаком с OpenFOAM или с методами в нём реализованными — пишите о найденных ошибках и неточностях в личку.
На хабре уже была пара статей про OpenFOAM:
OpenFOAM на практике
OpenFOAM с точки зрения программиста-физика
Поэтому не буду останавливаться на том, что это «открытая (GPL) платформа для численнного моделирования, предназначенная для моделирования, связанного с решением уравнений в частных производных методом конечных объёмов, и широко используемая для решения задач механики сплошных сред».
Сегодня я на простом примере опишу операции, которые происходят в ходе расчёта в OpenFOAM.
Итак, дана геометрия — куб со стороной 1 метр:

Перед нами стоит задача смоделировать поток-распространение некоторого скалярного поля (температура, количество вещества), который задаётся следующим уравнением переноса (1) внутри объема тела.
(1)

где


Это уравнение, например, используют для моделирования распространения тепла

где k — теплопроводность, а

Оператор дивергенции на самом деле это
оператор
.
Напомню, что существует оператор набла (оператор Гамильтона), который записывается следующим образом:
,
где i, j, k — единичные векторы.
Если скалярно умножить оператор набла на векторную величину, то мы получим дивергенцию данного вектора:

«С точки зрения физики, дивергенция векторного поля является показателем того, в какой степени данная точка пространства является источником или стоком этого поля»
Если умножить оператор набла на скаляр, получается градиент этого скаляра:

Градиент показывает увеличение или уменьшение по какому-либо направлению величины скаляра
.

Напомню, что существует оператор набла (оператор Гамильтона), который записывается следующим образом:

где i, j, k — единичные векторы.
Если скалярно умножить оператор набла на векторную величину, то мы получим дивергенцию данного вектора:

«С точки зрения физики, дивергенция векторного поля является показателем того, в какой степени данная точка пространства является источником или стоком этого поля»
Если умножить оператор набла на скаляр, получается градиент этого скаляра:

Градиент показывает увеличение или уменьшение по какому-либо направлению величины скаляра

Граничные условия задачи следующие: есть грань-вход, грань-выход, остальные грани — гладкие стенки.

Разбиение объема куба на конечные объемы
Наша сетка будет очень простая — делим куб на 5 равных ячеек вдоль оси Z.

Много формул
Метод конечных объёмов предусматривает, что (1) в интегральной форме (2) будет выполняться для каждого конечного объёма

(2)

где

Центр конечного объема

Упростим, преобразуем первое слагаемое выражения (2) следующим образом:
(2.1) (HJ-3.12)*



Как видно — мы приняли, что скалярная величина изменяется внутри конечного объема линейно и значение величины


(2.2)

где

Для упрощения второго слагаемого выражения (2) используем обобщённую теорему Гаусса-Остроградского: интеграл от дивергенции векторного поля



(2.3)

где




Вектор S

Учитывая то, что конечный объём ограничен набором плоских граней, можно выражение (2.3) преобразовать к сумме интегралов по поверхности:
(2.4) (HJ-3.13)

где


Еще немного про вектор S
Чтобы не хранить одни и те же параметры вектора






(HJ-3.16)

Про разностные схемы
Значение


divSchemes
{
default none;
div(phi,psi) Gauss linear;
}
Gauss — означает, что выбрана центральная разностная схема;
linear — означает, что интерполяция с центров ячеек на центры граней будет происходить линейно.

Допустим, что наша скалярная величина изменяется внутри конечного объема от центра к граням линейно. Тогда аппроксимированное в центре грани значение будет вычисляться согласно формуле:

Где веса



Где

Для случаев скошенных ячеек существуют более сложные формулы расчета весов аппроксимации.
Таким образом, значения phi_f в центрах граней ячеек вычисляются на основе значений в центрах ячеек. Значения градиентов grad(phi) вычисляются на основе значений phi_f.
И весь этот алгоритм может быть представлен в виде следующего псевдокода.
1. Объявляем массив градиентов конечных объемов, инициализируем его нулями
2. Пробегаемся по всем внутренним граням (которые не граничные)
> Вычисляем flux_f = phi_f*S_f. Значения phi_f вычисляем на основе значений phi в центах ячеек
> Добавляем flux_f к градиенту элемента-owner и -flux_f к градиенту элемента-neighbour
3. Пробегаемся по всем граничным граням
> Вычисляем flux_f = phi_f*S_f
> Добавляем flux_f к градиенту элементу-owner (neighbour-элементов у граничных граней нет)
4. Пробегаемся по всем элементам
> Делим получившуюся сумму-градиент на объем элемента
Дискретизация по времени

Учитывая (2.1) и (2.4) выражение (2) принимает вид:
(3)

Согласно методу конечных объёмов проводится дискретизация по времени и выражение (3) записывается как:
(4)

Проинтегрируем (4):
(4.1)

Разделим левую и правую часть на

(5)

Данные для матрицы дискретизации
Теперь мы можем получить систему линейных уравнений для каждого конечного объёма

Ниже представлена нумерация узлов сетки, которую мы будем использовать.

Координаты узлов хранятся в /constant/polyMesh/points
24
(
(0 0 0)
(1 0 0)
(0 1 0)
(1 1 0)
(0 0 0.2)
(1 0 0.2)
(0 1 0.2)
(1 1 0.2)
(0 0 0.4)
(1 0 0.4)
(0 1 0.4)
(1 1 0.4)
(0 0 0.6)
(1 0 0.6)
(0 1 0.6)
(1 1 0.6)
(0 0 0.8)
(1 0 0.8)
(0 1 0.8)
(1 1 0.8)
(0 0 1)
(1 0 1)
(0 1 1)
(1 1 1)
)
Нумерация узлов-центров ячеек (50, 51 — центры граничных граней):

Нумерация узлов-центров граней:

Объемы элементов:

Коэффициенты интерполяции, необходимые для вычисления значений на гранях ячеек. Индекс «e» обозначает «правая грань ячейки». Правая относительно вида, как на рисунке «Нумерация узлов-центров ячеек»:

Формирование матрицы дискретизации
Для P = 0.
Выражение (5), описывающее поведение величины

будет преобразовано в систему линейных алгебраических уравнений, каждое из которых вида:

или, согласно индексам точек на гранях

А еще все потоки в/из ячейки могут быть выражены в виде суммы

где, например,



Согласно нумерации граней выражение примет вид:

С учетом граничных условий для элемента P_0 линейное алгебраическое уравнение может быть представлено в виде

… подставим ранее полученные коэффициенты…

Поток из inlet'a направлен в ячейку, поэтому имеет отрицательный знак.

Так как у нас в управляющем выражении присутствует кроме диффузионного еще и временной член, но конечное уравнение выглядит как


Для P = 1.

Для P = 4.

Система линейных алгебраических уравнений (СЛАУ) может быть представлена в матричном виде как

где
=== A(i,j) ===
40.5 0.5 0 0 0
-0.5 40 0.5 0 0
0 -0.5 40 0.5 0
0 0 -0.5 40 0.5
0 0 0 -0.5 40.5
=== b(i,j) ===
1
0
0
0
0
Далее полученная СЛАУ решается решателем, указанным в fvSchemes.
И в итоге получается вектор значений

psi = dimensions [0 0 0 0 0 0 0];
internalField nonuniform List<scalar> 5(0.0246875 0.000308546 3.85622e-06 4.81954e-08 5.95005e-10);
на основе которого получаются значения для вектора




Затем вектор



И так до тех пор, пока невязка не достигнет требуемых пределов.
Ссылки
* Некоторые уравнения в этой статье взяты из диссертации Ясака Хрвое (HJ — номер уравнения) и если кому-то захочется прочитать про них подробнее (http://powerlab.fsb.hr/ped/kturbo/OpenFOAM/docs/HrvojeJasakPhD.pdf)
Скачать файлы задачи можно здесь:
github.com/j-avdeev/case
Файлы решателя:
github.com/j-avdeev/matrHyper1Foam
В качестве бонуса — видео, как распространяется концентрация
