<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
		<id>http://e6.ijs.si/medusa/wiki/api.php?action=feedcontributions&amp;user=VagifO&amp;feedformat=atom</id>
		<title>Medusa: Coordinate Free Mehless Method implementation - User contributions [en]</title>
		<link rel="self" type="application/atom+xml" href="http://e6.ijs.si/medusa/wiki/api.php?action=feedcontributions&amp;user=VagifO&amp;feedformat=atom"/>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php/Special:Contributions/VagifO"/>
		<updated>2026-04-16T02:51:26Z</updated>
		<subtitle>User contributions</subtitle>
		<generator>MediaWiki 1.27.1</generator>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Scattering_from_an_infinite_cylinder&amp;diff=3683</id>
		<title>Scattering from an infinite cylinder</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Scattering_from_an_infinite_cylinder&amp;diff=3683"/>
				<updated>2025-10-03T15:28:50Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: Corrected expression for gamma&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Back to [[Computational electromagnetics]].&lt;br /&gt;
&lt;br /&gt;
==Case==&lt;br /&gt;
&amp;lt;math&amp;gt;&lt;br /&gt;
\newcommand{\eps}{\varepsilon}&lt;br /&gt;
\newcommand{\dpar}[2]{\frac{\partial #1}{\partial #2}}&lt;br /&gt;
\newcommand{\ddpar}[2]{\frac{\partial^2 #1}{\partial #2^2}}&lt;br /&gt;
\def\doubleunderline#1{\underline{\underline{#1}}}&amp;lt;/math&amp;gt;&lt;br /&gt;
As a simple two dimensional case let us consider a plane wave scattering on a dielectric cylinder. This case is important for two reasons, firstly, it shows how to ''encode'' the incident wave into the boundary between the cylinder and free space and thus calculate only with the scattered field outside. It shows how to use analytic expressions to represent sources with known analytical solutions, this is especially useful when dealing with point sources. This case is important for two reasons. Firstly we will see how we can describe the solution in terms of scattered fields only, by including the incident wave in the boundary conditions. Secondly the problem has a relatively simple analytical solution in terms of Hankel funitons and can be used as a benchmark case.The problem is defined in two domains, inside and outside the cylinder, we are solving the following system of two PDE that are coupled on the inside boundary&lt;br /&gt;
\begin{align}&lt;br /&gt;
\nabla^2 E_z^{int} +\eps_r \frac{\omega^2}{c_0^2}\thinspace E_z^{int} = 0 \qquad &amp;amp;\text{in} \quad D \label{eq:inner} \\&lt;br /&gt;
\nabla^2 E_z^s + \frac{\omega^2}{c_0^2}E_z^s  = 0 \qquad &amp;amp; \text{outside} \quad D&lt;br /&gt;
\label{eq:outer}&lt;br /&gt;
\end{align}&lt;br /&gt;
The field $E_z^{int}$ is the total field inside, $E_z^s$ is only the scattered part of $E_z$ Outside the cylinder. The boundary conditions are&lt;br /&gt;
\begin{equation}&lt;br /&gt;
E_z^{int} - E_z^{s} =E_z^{inc}  \qquad \text{on} \quad \partial D \label{eq:BC1}, \qquad \text{and} \qquad&lt;br /&gt;
\dpar{E_z^{int}}{n} - \frac{1}{\eps_r}\dpar{E_z^{s}}{n} = \dpar{E_z^{inc}}{n}  \qquad \text{on} \quad  \partial D.&lt;br /&gt;
\end{equation}&lt;br /&gt;
The incident field $E_z^{inc}$ has an analytical form in our case a plane wave $e^{ikx}$, since it has a nice closed form we know its derivatives and the boundary conditions are nicely expressed. Around the computational domain we use a PML to simulate an infinite domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
	// Construct system&lt;br /&gt;
double l_in = epsr*std::pow(omega/c0, 2.0);&lt;br /&gt;
double l_out = std::pow(omega/c0, 2.0);&lt;br /&gt;
for (int i : cyl_dom.interior()){&lt;br /&gt;
	op_inner.lap(i) + l_in*op_inner.value(i) = 0.0;&lt;br /&gt;
}&lt;br /&gt;
for (int i : outer_inter){&lt;br /&gt;
	op_outer.lap(i) + l_out*op_outer.value(i) = 0.0;&lt;br /&gt;
}&lt;br /&gt;
for (int c = 0; c &amp;lt; interface_c_idx.size(); ++c){&lt;br /&gt;
	int i = interface_c_idx[c]; // cyl index&lt;br /&gt;
	int j = interface_o_idx[c]; // outer index&lt;br /&gt;
	Vec2d pos = cyl_dom.pos(i);&lt;br /&gt;
	double x = pos[0];&lt;br /&gt;
	double y = pos[1];&lt;br /&gt;
&lt;br /&gt;
	// calculate normal derivative of the source funciton at the i-th node&lt;br /&gt;
	// get normal, must point out of the cylinder&lt;br /&gt;
	Vec2d normal = cyl_dom.normal(i);&lt;br /&gt;
	std::complex&amp;lt;double&amp;gt; incident = std::exp(1.0_i*k0*(x*std::cos(t0)+y*std::sin(t0)));&lt;br /&gt;
	std::complex&amp;lt;double&amp;gt; din_dx = &lt;br /&gt;
            1.0_i*k0*std::cos(t0)*std::exp(1.0_i*k0*(x*std::cos(t0)+y*std::sin(t0)));&lt;br /&gt;
	std::complex&amp;lt;double&amp;gt; din_dy = &lt;br /&gt;
            1.0_i*k0*std::sin(t0)*std::exp(1.0_i*k0*(x*std::cos(t0)+y*std::sin(t0)));&lt;br /&gt;
	std::complex&amp;lt;double&amp;gt; dui_dn = normal[0]*din_dx + normal[1]*din_dy;&lt;br /&gt;
&lt;br /&gt;
	// continuity of the fields, and the incident field&lt;br /&gt;
	op_inner.value(i) + (-1)*op_outer.value(j, Nouter + i) = incident;&lt;br /&gt;
&lt;br /&gt;
	// continuity of derivatives&lt;br /&gt;
	op_outer.neumann(j, outer_dom.normal(j)) &lt;br /&gt;
              + (1/epsr)*op_inner.neumann(i, cyl_dom.normal(i), j - Nouter)&lt;br /&gt;
			= dui_dn; // todo, change this from Neumanns to derivatives!&lt;br /&gt;
}&lt;br /&gt;
// SC-PML - where either x or y is constant&lt;br /&gt;
for (int i : PML){&lt;br /&gt;
	1.0/(sw[i]*sw[i])*op_outer.lap(i) + l_out*op_outer.value(i) +&lt;br /&gt;
	((-1.0)/(sw[i]*sw[i]*sw[i])*exop.d1(sw, 0, i)*op_outer.der1(i, 0)&lt;br /&gt;
		+(-1.0)/(sw[i]*sw[i]*sw[i])*exop.d1(sw, 1, i)*op_outer.der1(i, 1)) = 0.0;&lt;br /&gt;
}&lt;br /&gt;
for (int i: outer_bnd){&lt;br /&gt;
	op_outer.value(i) = 0.0;&lt;br /&gt;
}&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
==Analytical solution==&lt;br /&gt;
The solution to this problem is sought in terms of sums of cylindrical harmonics. The incident wave is a plane wave $\b E_i = \hat e_z E_0 e^{-ikx}$ and can be decomposed into Bessel functions of the first kind as&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\mathbf{E}_{i}=\widehat{z} E_{0} e^{-i k_{0} x}= \widehat{z} E_{0} \sum_{n=-\infty}^{\infty} i^{-n} J_{n}\left(k_{0} \rho\right) e^{i n \phi}.&lt;br /&gt;
\end{equation}&lt;br /&gt;
The scattered field is a sum of Hankel functions so it satisfies the Sommerfeld radiation condition&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\mathbf{E}^{s}=\widehat{z} E_{0} \sum_{n=-\infty}^{\infty} i^{-n} a_{n} H_{n}^{(2)}\left(k_{0} \rho\right) e^{i n \phi}, \quad \rho \geq a&lt;br /&gt;
\end{equation}&lt;br /&gt;
and finally the total field is again a sum ob Bessel functions of the first kind&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\mathbf{E}^{t}=\widehat{z} E_{0} \sum_{n=-\infty}^{\infty} i^{-n} b_{n} J_{n}\left(k_{1} \rho\right) e^{i n \phi}, \quad \rho \leq a.&lt;br /&gt;
\end{equation}&lt;br /&gt;
The boundary conditions for the field and its derivative on the boundary between the cylinder and free space surrounding it give two expressions&lt;br /&gt;
\begin{equation}&lt;br /&gt;
J_{n}\left(k_{0} a\right)+a_{n} H_{n}^{(2)}\left(k_{0} a\right)=b_{n} J_{n}\left(k_{1} a\right)&lt;br /&gt;
\end{equation}&lt;br /&gt;
and&lt;br /&gt;
\begin{equation}&lt;br /&gt;
k_0 J^{\prime}_{n}\left(k_{0} a\right)+ k_0 a_{n} H_{n}^{\prime(2)}\left(k_{0} a\right)=k_1 b_{n} J^{\prime}_{n}\left(k_{1} a\right).&lt;br /&gt;
\end{equation}&lt;br /&gt;
Which give the coefficients $a_n$ and $b_n$ in the above expansions as&lt;br /&gt;
\begin{equation}&lt;br /&gt;
a_{n}=-\frac{J_{n}\left(k_{0} a\right)-\Gamma_{h} J_{n}^{\prime}\left(k_{0} a\right)}{H_{n}^{(2)}\left(k_{0} a\right)-\Gamma_{h} H_{n}^{(2)^{\prime}}\left(k_{0} a\right)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
where&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\Gamma_{h}=\frac{k_{0}}{k_{1}} \frac{J_{n}\left(k_{1} a\right)}{J_{n}^{\prime}\left(k_{1} a\right)}&lt;br /&gt;
\end{equation}&lt;br /&gt;
and &lt;br /&gt;
\begin{equation}&lt;br /&gt;
b_n = \frac{J_n(k_0a)}{J_n(k_1a)} + a_n \frac{H_n^{(2)}(k_0a)}{J_n(k_1a)}.&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
==Results==&lt;br /&gt;
[[File:FDFD_2D_dielcyl_ReEn_1.png|400px]][[File:FDFD_2D_dielcyl_ImEn_1.png|400px]]&lt;br /&gt;
&lt;br /&gt;
[[File:FDFD_2D_dielcyl_ReEn_2.png|400px]][[File:FDFD_2D_dielcyl_ImEn_2.png|400px]]&lt;br /&gt;
&lt;br /&gt;
[[File:FDFD_2D_dielcyl_ReEn_3.png|400px]][[File:FDFD_2D_dielcyl_ImEn_3.png|400px]]&lt;br /&gt;
&lt;br /&gt;
[[File:FDFD_2D_dielcyl_ReEn_4.png|400px]][[File:FDFD_2D_dielcyl_ImEn_4.png|400px]]&lt;br /&gt;
&lt;br /&gt;
[[File:FDFD_2D_dielcyl_ReEn_5.png|400px]][[File:FDFD_2D_dielcyl_ImEn_5.png|400px]]&lt;br /&gt;
&lt;br /&gt;
[[File:FDFD_2D_benchmark_times_basic.png|800px]]&lt;br /&gt;
&lt;br /&gt;
[[File:FDFD_2D_dx_conv2.png|800px]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3681</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3681"/>
				<updated>2024-08-26T16:11:19Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: Added link to repository&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity vector field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version on our [https://gitlab.com/e62Lab/medusa/-/tree/dev/examples?ref_type=heads repository]). First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest $n$ nodes of each node which are called stencil nodes or support nodes or simply the stencil.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(n);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use the RBF-FD method with polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
&lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we set the initial condition to &lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, t) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile: $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*nu*a*k*std::sin(k*discretization.pos(i,0))/(b+a*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation interior&lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*nu) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (5) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For the simulation bellow we used the following parameters: $\nu = 0.05, l = 1, a = 4, b = 4.1, k = 2\pi$, $n = 13$, $m = 4$, $dx = 2\cdot 10^{-3}$, $dt = 10^{-6}$.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$ and analyze the convergence with increasing number of discretization nodes $N$. &lt;br /&gt;
&lt;br /&gt;
After some initial oscillation the maximum error decreases approximately as $\left(\frac{1}{N}\right)^k$, which is proportional to $dx^k$, where $k$ is the fit parameter for the function: $\log_{10}e_\infty = -k\log_{10}N + c$ for every $m$. But eventually the error plateaus because for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
Find the full version of the code in our repository. We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1, fill it with nodes and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(dr);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes, excluding boundary nodes from supports of boundary nodes for better stability of neumann BC&lt;br /&gt;
    discretization.findSupport(FindClosest(n).forNodes(discretization.interior()));&lt;br /&gt;
    discretization.findSupport(FindClosest(n).forNodes(discretization.boundary()).searchAmong(discretization.interior()).forceSelf(true));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0,\\&lt;br /&gt;
\frac{\partial u}{\partial y}\bigg|_{y = 0} = \frac{\partial u}{\partial y}\bigg|_{y = 1} = \frac{\partial v}{\partial x}\bigg|_{x = 0} = \frac{\partial v}{\partial x}\bigg|_{x = 1} = 0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2; // update velocity vector&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials. For the simulation bellow we used parameters $\nu = 1$, $n$ = 45, $m = 4$, $dr = 0.02$, $dt = 10^{-5}$. The animations below show the velocities in both directions and the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation monomial order. The rate at which the maximum error decreases is close to $\left(\frac{1}{\sqrt{N}}\right)^{k}$ as shown on the graph bellow. The distance between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$ if the node distribution is uniform. For finer discretizations and bigger $m$ the error becomes approximately constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $m = 4$, $dr = 0.02$ giving us $N = 753$ and vary the time step size. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3674</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3674"/>
				<updated>2024-04-23T14:55:23Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Solution in 1D */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity vector field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository). First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest $n$ nodes of each node which are called stencil nodes or support nodes or simply the stencil.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(n);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use the RBF-FD method with polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
&lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we set the initial condition to &lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, t) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile: $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*nu*a*k*std::sin(k*discretization.pos(i,0))/(b+a*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation interior&lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*nu) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (5) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For the simulation bellow we used the following parameters: $\nu = 0.05, l = 1, a = 4, b = 4.1, k = 2\pi$, $n = 13$, $m = 4$, $dx = 2\cdot 10^{-3}$, $dt = 10^{-6}$.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$ and analyze the convergence with increasing number of discretization nodes $N$. &lt;br /&gt;
&lt;br /&gt;
After some initial oscillation the maximum error decreases approximately as $\left(\frac{1}{N}\right)^k$, which is proportional to $dx^k$, where $k$ is the fit parameter for the function: $\log_{10}e_\infty = -k\log_{10}N + c$ for every $m$. But eventually the error plateaus because for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
Find the full version of the code in our repository. We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1, fill it with nodes and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(dr);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes, excluding boundary nodes from supports of boundary nodes for better stability of neumann BC&lt;br /&gt;
    discretization.findSupport(FindClosest(n).forNodes(discretization.interior()));&lt;br /&gt;
    discretization.findSupport(FindClosest(n).forNodes(discretization.boundary()).searchAmong(discretization.interior()).forceSelf(true));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0,\\&lt;br /&gt;
\frac{\partial u}{\partial y}\bigg|_{y = 0} = \frac{\partial u}{\partial y}\bigg|_{y = 1} = \frac{\partial v}{\partial x}\bigg|_{x = 0} = \frac{\partial v}{\partial x}\bigg|_{x = 1} = 0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2; // update velocity vector&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials. For the simulation bellow we used parameters $\nu = 1$, $n$ = 45, $m = 4$, $dr = 0.02$, $dt = 10^{-5}$. The animations below show the velocities in both directions and the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation monomial order. The rate at which the maximum error decreases is close to $\left(\frac{1}{\sqrt{N}}\right)^{k}$ as shown on the graph bellow. The distance between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$ if the node distribution is uniform. For finer discretizations and bigger $m$ the error becomes approximately constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $m = 4$, $dr = 0.02$ giving us $N = 753$ and vary the time step size. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3673</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3673"/>
				<updated>2024-04-23T14:38:37Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: Boundary conditions for 2D&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity vector field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository). First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest $n$ nodes of each node which are called stencil nodes or support nodes or simply the stencil.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(n);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use the RBF-FD method with polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
&lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we set the initial condition to &lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, t) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile: $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::sin(k*discretization.pos(i,0))/(b+a*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation interior&lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (5) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For the simulation bellow we used the following parameters: $\nu = 0.05, l = 1, a = 4, b = 4.1, k = 2\pi$, $n = 13$, $m = 4$, $dx = 2\cdot 10^{-3}$, $dt = 10^{-6}$.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$ and analyze the convergence with increasing number of discretization nodes $N$. &lt;br /&gt;
&lt;br /&gt;
After some initial oscillation the maximum error decreases approximately as $\left(\frac{1}{N}\right)^k$, which is proportional to $dx^k$, where $k$ is the fit parameter for the function: $\log_{10}e_\infty = -k\log_{10}N + c$ for every $m$. But eventually the error plateaus because for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
Find the full version of the code in our repository. We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1, fill it with nodes and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(dr);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes, excluding boundary nodes from supports of boundary nodes for better stability of neumann BC&lt;br /&gt;
    discretization.findSupport(FindClosest(n).forNodes(discretization.interior()));&lt;br /&gt;
    discretization.findSupport(FindClosest(n).forNodes(discretization.boundary()).searchAmong(discretization.interior()).forceSelf(true));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0,\\&lt;br /&gt;
\frac{\partial u}{\partial y}\bigg|_{y = 0} = \frac{\partial u}{\partial y}\bigg|_{y = 1} = \frac{\partial v}{\partial x}\bigg|_{x = 0} = \frac{\partial v}{\partial x}\bigg|_{x = 1} = 0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2; // update velocity vector&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials. For the simulation bellow we used parameters $\nu = 1$, $n$ = 45, $m = 4$, $dr = 0.02$, $dt = 10^{-5}$. The animations below show the velocities in both directions and the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation monomial order. The rate at which the maximum error decreases is close to $\left(\frac{1}{\sqrt{N}}\right)^{k}$ as shown on the graph bellow. The distance between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$ if the node distribution is uniform. For finer discretizations and bigger $m$ the error becomes approximately constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $m = 4$, $dr = 0.02$ giving us $N = 753$ and vary the time step size. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3670</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3670"/>
				<updated>2024-03-18T15:26:46Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Convergence */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity vector field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository). First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest $n$ nodes of each node which are called stencil nodes or support nodes or simply the stencil.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(n);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use the RBF-FD method with polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
&lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we set the initial condition to &lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, t) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile: $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::sin(k*discretization.pos(i,0))/(b+a*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation interior&lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (5) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For the simulation bellow we used the following parameters: $\nu = 0.05, l = 1, a = 4, b = 4.1, k = 2\pi$, $n = 13$, $m = 4$, $dx = 2\cdot 10^{-3}$, $dt = 10^{-6}$.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$ and analyze the convergence with increasing number of discretization nodes $N$. &lt;br /&gt;
&lt;br /&gt;
After some initial oscillation the maximum error decreases approximately as $\left(\frac{1}{N}\right)^k$, which is proportional to $dx^k$, where $k$ is the fit parameter for the function: $\log_{10}e_\infty = -k\log_{10}N + c$ for every $m$. But eventually the error plateaus because for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
Find the full version of the code in our repository. We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1, fill it with nodes and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(dr);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(45));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2; // update velocity vector&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials. For the simulation bellow we used parameters $\nu = 1$, $n$ = 45, $m = 4$, $dr = 0.02$, $dt = 10^{-5}$. The animations below show the velocities in both directions and the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation monomial order. The rate at which the maximum error decreases is close to $\left(\frac{1}{\sqrt{N}}\right)^{k}$ as shown on the graph bellow. The distance between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$ if the node distribution is uniform. For finer discretizations and bigger $m$ the error becomes approximately constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $m = 4$, $dr = 0.02$ giving us $N = 753$ and vary the time step size. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3669</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3669"/>
				<updated>2024-03-18T15:25:38Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity vector field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository). First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest $n$ nodes of each node which are called stencil nodes or support nodes or simply the stencil.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(n);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use the RBF-FD method with polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
&lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we set the initial condition to &lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, t) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile: $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::sin(k*discretization.pos(i,0))/(b+a*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation interior&lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (5) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For the simulation bellow we used the following parameters: $\nu = 0.05, l = 1, a = 4, b = 4.1, k = 2\pi$, $n = 13$, $m = 4$, $dx = 2\cdot 10^{-3}$, $dt = 10^{-6}$.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$ and analyze the convergence with increasing number of discretization nodes $N$. &lt;br /&gt;
&lt;br /&gt;
After some initial oscillation the maximum error decreases approximately as $\left(\frac{1}{N}\right)^k$, which is proportional to $dx^k$, where $k$ is the fit parameter for the function: $\log_{10}e_\infty = k\log_{10}N + c$ for every $m$. But eventually the error plateaus because for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
Find the full version of the code in our repository. We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1, fill it with nodes and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(dr);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(45));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2; // update velocity vector&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials. For the simulation bellow we used parameters $\nu = 1$, $n$ = 45, $m = 4$, $dr = 0.02$, $dt = 10^{-5}$. The animations below show the velocities in both directions and the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation monomial order. The rate at which the maximum error decreases is close to $\left(\frac{1}{\sqrt{N}}\right)^{k}$ as shown on the graph bellow. The distance between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$ if the node distribution is uniform. For finer discretizations and bigger $m$ the error becomes approximately constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $m = 4$, $dr = 0.02$ giving us $N = 753$ and vary the time step size. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3668</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3668"/>
				<updated>2024-03-15T15:09:18Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity vector field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository). First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest n nodes of each node which are called stencil nodes or support nodes or simply the stencil.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(n);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use the RBF-FD method with polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
&lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Next we set the initial condition to $$u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},$$ where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile: $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::sin(k*discretization.pos(i,0))/(b+a*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation interior&lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (4) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For the simulation bellow we used the following parameters: $\nu = 0.05, l = 1, a = 4, b = 4.1, k = 2\pi$, n = 13, $m = 4$, $dx = 2\cdot 10^{-3}$, $dt = 10^{-6}$.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$ and analyze the convergence with increasing number of discretization nodes $N$. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the maximum error decreases approximately as $(\frac{1}{N})^k$, which is proportional to $dx^k$, where $k$ is the fit parameter for the function: $\log_{10}e_\infty = k\log_{10}N + c$ for every $m$. But eventualy the error plateaus because for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
Find the full version of the code in our repository. We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1, fill it with nodes and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(dr);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(45));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2; // update velocity vector&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials. For the simulation bellow we used parameters $\nu = 1$, n = 45, $m = 4$, $dr = 0.02$, $dt = 10^{-5}$. The animations below show the velocities in both directions and the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation monomial order. The rate at which the maximum error decreases is close to $(\frac{1}{\sqrt{N}})^{k}$ as shown on the graph bellow. The distance between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$ if the node distribution is uniform. For finer discretizations and bigger $m$ the error becomes approxmately constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $m = 4$, $dr = 0.02$ giving us $N = 753$ and vary the time step size. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3667</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3667"/>
				<updated>2024-03-15T15:09:02Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3666</id>
		<title>File:Burgers2D error step.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3666"/>
				<updated>2024-03-15T15:08:42Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D error step.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3665</id>
		<title>File:Burgers1D time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3665"/>
				<updated>2024-03-15T15:04:59Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers1D time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Error_step6.png&amp;diff=3664</id>
		<title>File:Error step6.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Error_step6.png&amp;diff=3664"/>
				<updated>2024-03-15T15:04:26Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Error step6.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Error_step6.png&amp;diff=3663</id>
		<title>File:Error step6.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Error_step6.png&amp;diff=3663"/>
				<updated>2024-03-15T14:49:33Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Error step6.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_animation.mp4&amp;diff=3662</id>
		<title>File:Burgers animation.mp4</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_animation.mp4&amp;diff=3662"/>
				<updated>2024-03-15T14:28:44Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers animation.mp4&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Animation of 1D burgers equation solution&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3661</id>
		<title>File:Burgers1D time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3661"/>
				<updated>2024-03-15T13:59:27Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers1D time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3644</id>
		<title>File:Burgers1D time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3644"/>
				<updated>2024-03-08T10:26:15Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers1D time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Error_step6.png&amp;diff=3643</id>
		<title>File:Error step6.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Error_step6.png&amp;diff=3643"/>
				<updated>2024-03-08T10:25:33Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Error step6.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3642</id>
		<title>File:Burgers2D error step.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3642"/>
				<updated>2024-03-08T10:24:32Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D error step.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3641</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3641"/>
				<updated>2024-03-08T10:21:11Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3640</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3640"/>
				<updated>2024-03-08T10:17:18Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3639</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3639"/>
				<updated>2024-03-08T10:15:41Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3638</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3638"/>
				<updated>2024-03-08T10:14:48Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3637</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3637"/>
				<updated>2024-03-08T10:14:35Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3636</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3636"/>
				<updated>2024-03-08T10:14:08Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D time error.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3635</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3635"/>
				<updated>2024-03-07T15:16:50Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Convergence */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},$$ where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (4) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For our simulation we used the following parameters: $\nu = 1, l = 1, a = 4, b = 4, k = \frac{4\pi}{2l}$. As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with monomials of up to order $m = 4$. For stencils of minimal size - $m+1$, the method effectively becomes regular Finite Difference method (FD), which is true in our case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$, stencil size $n = m+1$, reducing our method to FD, and analyze the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $dr^k$, where $k$ is the fit parameter for every $m$ and $dr$ is the distance between consecutive nodes. But eventualy it becomes approximately constant. The reason is that for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(45));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials for our approximation engine. For the simulation we used $\nu = 1$. The animations below show the velocities in both directions as well as the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation polinomial order and analyze the convergence with increasing node density. The distance $dr$ between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$. The rate at which the error decreases is close to $dr^{-m}$ as show on the graph bellow. For finer discretizations and bigger $m$ the error becomes constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $N = 753$ and $m = 4$ and change the time step. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3634</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3634"/>
				<updated>2024-03-07T15:15:01Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Solution in 1D */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions (RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},$$ where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators (op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector u[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (4) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For our simulation we used the following parameters: $\nu = 1, l = 1, a = 4, b = 4, k = \frac{4\pi}{2l}$. As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with monomials of up to order $m = 4$. For stencils of minimal size - $m+1$, the method effectively becomes regular Finite Difference method (FD), which is true in our case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$, stencil size $n = m+1$ reducing our method to FD and analyze the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $dr^k$, where $k$ is the fit parameter for every $m$ and $dr$ is the distance between consecutive nodes. But eventualy it becomes approximately constant. The reason is that for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(45));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials for our approximation engine. For the simulation we used $\nu = 1$. The animations below show the velocities in both directions as well as the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation polinomial order and analyze the convergence with increasing node density. The distance $dr$ between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$. The rate at which the error decreases is close to $dr^{-m}$ as show on the graph bellow. For finer discretizations and bigger $m$ the error becomes constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $N = 753$ and $m = 4$ and change the time step. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3633</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3633"/>
				<updated>2024-03-07T15:11:33Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field and $\nu$ is the dynamic viscosity. To solve the equation we will linearize the advection term. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximation of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code (find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with length 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt; approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd u = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights (also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x) = \frac{2\nu ak\sin(kx)}{b + a\cos(kx)},$$ where $a, b, k$ are constants and $\nu$ is viscosity. We chose this function because it has an analytical solution&amp;lt;ref name=&amp;quot;Burgers' equation&amp;quot;&amp;gt;A. Salih, Burgers' equation. Indian Institute of Space Science and Technology, Thiruvananthapuram, 2016.&amp;lt;/ref&amp;gt;&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        u(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); &lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);      &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (u[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = u[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. The analytical solution is defined on an infinite domain, but we see from equation (4) that $u = 0$ for $kx = z\pi, z \in \mathbb{Z}$. So we will be able to enforce Dirichlet boundary conditions on a finite domain, but this will limit our choice of $k$ to $\frac{z\pi}{l}, z \in \mathbb{Z}$, where $l$ is the length of the domain.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update u for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed();&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver;&lt;br /&gt;
    solver.compute(M); &lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd u2 = solver.solve(rhs);&lt;br /&gt;
    u = u2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;u&amp;quot;, u);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For our simulation we used the following parameters: $\nu = 1, l = 1, a = 4, b = 4, k = \frac{4\pi}{2l}$. As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with monomials of up to order $m = 4$. For stencils of minimal size - $m+1$, the method effectively becomes regular Finite Difference method (FD), which is true in our case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$, vary the monomial augmentation order $m \in \{2, 4, 6, 8\}$, stencil size $n = m+1$ reducing our method to FD and analyze the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $dr^k$, where $k$ is the fit parameter for every $m$ and $dr$ is the distance between consecutive nodes. But eventualy it becomes approximately constant. The reason is that for finer spatial discretizations we are bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 respectively and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
where $u$ and $v$ are velocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find 45 closest nodes of each node.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (1, 1));&lt;br /&gt;
double step = 0.05;&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(45));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
The approximation engine is created and shape functions are computed in the same way as in the 1D example, except the template parameter is &amp;lt;code&amp;gt;Vec2d&amp;lt;/code&amp;gt; instead of &amp;lt;code&amp;gt;Vec1d&amp;lt;/code&amp;gt;. We will be simulating the following initial and boundary conditions:&lt;br /&gt;
&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matrices Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matrices. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); &lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  &lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ augmented with monomials for our approximation engine. For the simulation we used $\nu = 1$. The animations below show the velocities in both directions as well as the magnitude of the velocity.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix the time step to $dt = 10^{-6}$ and change the augmentation polinomial order and analyze the convergence with increasing node density. The distance $dr$ between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$. The rate at which the error decreases is close to $dr^{-m}$ as show on the graph bellow. For finer discretizations and bigger $m$ the error becomes constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $N = 753$ and $m = 4$ and change the time step. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3632</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3632"/>
				<updated>2024-03-06T17:15:40Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximantion of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundary conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1,\\&lt;br /&gt;
l &amp;amp;= 1,\\&lt;br /&gt;
a &amp;amp;= 4,\\&lt;br /&gt;
b &amp;amp;= 4,\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l},\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3,\\&lt;br /&gt;
n &amp;amp;= 5,\\&lt;br /&gt;
m &amp;amp;= 4.&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px|center]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$ and $h$ is the distance between consecutive nodes. But eventualy it becomes constant value. The reason is that for finer spatial discretizations we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers1D_time_err.png|400px|center]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
where $u$ and $v$ are ve;ocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find n closest nodes of each node.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (a, a));&lt;br /&gt;
double step = std::stod(argv[1]);&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(n));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We will be simulating the following initial and boundary conditions:&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matricies Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matricies. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = t_0 + tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  // set empty vector for rhs&lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
For all of the simulation we used $\nu = 1$. The animations below show the velocities in both directions as well as the magnitude of the velocity.&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
==Convergence==&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the same method as in the 1D case. We fix the time step to $dt = 10^{-6}$ and change the augmentation polinomial order and analize the convergence with increasing node density. The distance $dr$ between neighbouring nodes in 2D is approximately proportional to $\frac{1}{\sqrt{N}}$. The rate at which the error decreases is close to the expected $dr^{-m}$ as show on the graph bellow. For finer discretizations and bigger $m$ the error becomes constant as we are again bounded by the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_error_step.png|400px||center]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We fix $N = 753$ and $m = 4$ and change the time step. The error decreases linearily as in the 1D case.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers2D_time_error.png|400px||center]]&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3631</id>
		<title>File:Burgers1D time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers1D_time_err.png&amp;diff=3631"/>
				<updated>2024-03-06T17:13:25Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3630</id>
		<title>File:Burgers time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3630"/>
				<updated>2024-03-06T17:11:59Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3629</id>
		<title>File:Burgers2D time error.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_time_error.png&amp;diff=3629"/>
				<updated>2024-03-06T17:06:49Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3628</id>
		<title>File:Burgers time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3628"/>
				<updated>2024-03-06T17:03:34Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3627</id>
		<title>File:Burgers time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3627"/>
				<updated>2024-03-06T16:59:13Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3626</id>
		<title>File:Burgers time err.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers_time_err.png&amp;diff=3626"/>
				<updated>2024-03-06T16:57:58Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers time err.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3625</id>
		<title>File:Burgers2D error step.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3625"/>
				<updated>2024-03-06T16:25:34Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D error step.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3624</id>
		<title>File:Burgers2D error step.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3624"/>
				<updated>2024-03-06T15:47:44Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO uploaded a new version of File:Burgers2D error step.png&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3623</id>
		<title>File:Burgers2D error step.png</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_error_step.png&amp;diff=3623"/>
				<updated>2024-03-06T15:41:17Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3622</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3622"/>
				<updated>2024-03-05T17:06:20Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximantion of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundary conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1,\\&lt;br /&gt;
l &amp;amp;= 1,\\&lt;br /&gt;
a &amp;amp;= 4,\\&lt;br /&gt;
b &amp;amp;= 4,\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l},\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3,\\&lt;br /&gt;
n &amp;amp;= 5,\\&lt;br /&gt;
m &amp;amp;= 4.&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$ and $h$ is the distance between consecutive nodes. But eventualy it becomes constant value. The reason is that for finer spatial discretizations we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
where $u$ and $v$ are ve;ocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find n closest nodes of each node.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (a, a));&lt;br /&gt;
double step = std::stod(argv[1]);&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(n));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We will be simulating the following initial and boundary conditions:&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matricies Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matricies. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = t_0 + tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  // set empty vector for rhs&lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 2D=&lt;br /&gt;
For all of the simulation we used $\nu = 1$. The animations below show the velocities in both directions as well as the magnitude of the velocity.&lt;br /&gt;
[[File:Burgers2D_u.mp4|360px]] [[File:Burgers2D_v.mp4|360px]] [[File:Burgers2D_norm.mp4|360px]]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_v.mp4&amp;diff=3621</id>
		<title>File:Burgers2D v.mp4</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_v.mp4&amp;diff=3621"/>
				<updated>2024-03-05T17:03:37Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_u.mp4&amp;diff=3620</id>
		<title>File:Burgers2D u.mp4</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_u.mp4&amp;diff=3620"/>
				<updated>2024-03-05T17:03:22Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_norm.mp4&amp;diff=3619</id>
		<title>File:Burgers2D norm.mp4</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=File:Burgers2D_norm.mp4&amp;diff=3619"/>
				<updated>2024-03-05T17:02:51Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3618</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3618"/>
				<updated>2024-03-05T17:01:14Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximantion of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the apropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundary conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1,\\&lt;br /&gt;
l &amp;amp;= 1,\\&lt;br /&gt;
a &amp;amp;= 4,\\&lt;br /&gt;
b &amp;amp;= 4,\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l},\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3,\\&lt;br /&gt;
n &amp;amp;= 5,\\&lt;br /&gt;
m &amp;amp;= 4.&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$ and $h$ is the distance between consecutive nodes. But eventualy it becomes constant value. The reason is that for finer spatial discretizations we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
where $u$ and $v$ are ve;ocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find n closest nodes of each node.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (a, a));&lt;br /&gt;
double step = std::stod(argv[1]);&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
// Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(n));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We will be simulating the following initial and boundary conditions:&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matricies Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matricies. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = t_0 + tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  // set empty vector for rhs&lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results in 1D=&lt;br /&gt;
For all of the simulation we used $\nu = 1$. The animations below show the velocities in both directions as well as the magnitude of the velocity.&lt;br /&gt;
[[File:Burgers2D_u.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Medusa&amp;diff=3617</id>
		<title>Medusa</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Medusa&amp;diff=3617"/>
				<updated>2024-03-02T13:27:17Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;&amp;lt;!--__NOTITLE__--&amp;gt;&lt;br /&gt;
'''Welcome to the Medusa wiki. To visit the main website, go to [http://e6.ijs.si/medusa/ http://e6.ijs.si/medusa/].'''&lt;br /&gt;
&lt;br /&gt;
In [http://e6.ijs.si/ParallelAndDistributedSystems/ Parallel and Distributed Systems Laboratory] we are working on a C++ library that is first and foremost focused on tools for solving Partial Differential Equations by meshless methods. The basic idea is to create generic codes for tools that are needed for solving not only PDEs but many other problems, e.g. Moving Least Squares approximation, $k$-d tree, domain generation engines, etc.&lt;br /&gt;
We call this open source meshless project [http://e6.ijs.si/medusa/ Medusa: Coordinate Free Meshless Method implementation (MM)].&lt;br /&gt;
&lt;br /&gt;
Technical details about code and examples  can be found on our [http://e6.ijs.si/medusa/docs/ documentation page] and [https://gitlab.com/e62Lab/medusa Gitlab repository]. [[File:C.png|100px||link=https://gitlab.com/e62Lab/medusa|alt=Alt text|code]] [[File:doxygen.png|100px|link=http://e6.ijs.si/medusa/docs/|alt=Alt text|Documentation page]]&lt;br /&gt;
&lt;br /&gt;
This wiki site is meant for more relaxed discussions about general principles, possible and already implemented applications, preliminary analyses, etc.&lt;br /&gt;
Note, that there are many grammatical mistakes, typos, stupid sentences, etc. This wiki is meant for quick information exchange and therefore we do not invest a lot of energy into styling :).  &lt;br /&gt;
&lt;br /&gt;
== Documentation ==&lt;br /&gt;
* [https://gitlab.com/e62Lab/medusa Code on Gitlab]&lt;br /&gt;
* [[How to build | Installation and building]]&lt;br /&gt;
* [[Including this library in your project | Including this library in your project]]&lt;br /&gt;
* [[Testing | Running tests]]&lt;br /&gt;
* [http://e6.ijs.si/medusa/docs/ Technical documentation]&lt;br /&gt;
* [[Coding style | Coding style]]&lt;br /&gt;
* [[Wiki editing guide | Wiki editing and backup guide]]&lt;br /&gt;
&lt;br /&gt;
== Building blocks ==&lt;br /&gt;
Medusa is modular coordinate-free parallel implementation of a numerical framework designed, but not limited to, for solving PDEs. In this section we present main modules of the library that can be also used as a standalone tools. &lt;br /&gt;
* [[Positioning of computational nodes]] &lt;br /&gt;
* [[Relaxation of the nodal distribution]]&lt;br /&gt;
* [[Refinement of the nodal distribution]]&lt;br /&gt;
* [[k-d tree|''k''-d tree and other spatial search structures]] &lt;br /&gt;
* [[Solving system | Solving linear system - including over and underdetermined systems]]&lt;br /&gt;
* [[Weighted Least Squares (WLS)]]&lt;br /&gt;
* [[Computation of shape functions]]&lt;br /&gt;
* [[Radial basis function-generated finite differences (RBF-FD)]]&lt;br /&gt;
* [[Ghost nodes (theory)]]&lt;br /&gt;
* [[Integrators for time stepping]]&lt;br /&gt;
&lt;br /&gt;
== Examples ==&lt;br /&gt;
In this section we present exact examples. Each of the below solutions can be found also in in the repository under examples. More explanation about the physical background and solution procedure can be found in following sections.&lt;br /&gt;
* [[Philosophy of examples and how to run them]]&lt;br /&gt;
* [[Poisson's equation]]&lt;br /&gt;
* [[Heat equation]]&lt;br /&gt;
* [[Linear elasticity]]&lt;br /&gt;
* [[Complex-valued problems]]&lt;br /&gt;
* [[Coupled domains]]&lt;br /&gt;
* [[Parametric domains | Parametric domains &amp;amp;ndash; Curved surface with variable density]]&lt;br /&gt;
* [[NURBS domains | Domains modeled with non-uniform rational basis splines (NURBS)]]&lt;br /&gt;
* [[Determining the interior of the domain by oversampling the boundary]]&lt;br /&gt;
* [[Computer-aided design - Importing IGES and STEP files]]&lt;br /&gt;
* [[Realistic 3D models|Working with 3D surface mesh models ]]&lt;br /&gt;
* [[customization | Operator customization]]&lt;br /&gt;
* [[Ghost nodes]]&lt;br /&gt;
* [[Electromagnetic scattering]]&lt;br /&gt;
* [[Schrödinger equation]]&lt;br /&gt;
* [[Wave equation]]&lt;br /&gt;
* [[Cahn-Hilliard equation]]&lt;br /&gt;
* [[Fluid mechanics]]&lt;br /&gt;
* [[Solid Mechanics | Solid mechanics]]&lt;br /&gt;
&lt;br /&gt;
== Discussions / Applications ==&lt;br /&gt;
This section is meant for general discussion about the physical background of the examples, the solution procedures, various applications, etc. Note, that code snippets presented in discussion might not reflect the actual state of Medusa.  &lt;br /&gt;
* [[Convection Diffusion equation | Convection Diffusion equation]]&lt;br /&gt;
* [[Adaptivity|H-adaptivity]]&lt;br /&gt;
* [[Hp-adaptivity]]&lt;br /&gt;
* [[Solid Mechanics]]&lt;br /&gt;
** [[Point contact]]&lt;br /&gt;
** [[Hertzian contact]]&lt;br /&gt;
** [[Cantilever beam]]&lt;br /&gt;
** [[Fretting fatigue case]]&lt;br /&gt;
** [[Plasticity]]&lt;br /&gt;
* [[Fluid Mechanics]]&lt;br /&gt;
** [[Lid driven cavity]]&lt;br /&gt;
** [[Burgers' equation]]&lt;br /&gt;
** [[de Vahl Davis natural convection test]]&lt;br /&gt;
** [[Natural convection in 3D irregular domain]]&lt;br /&gt;
** [[Natural convection from heated cylinder]]&lt;br /&gt;
** [[Natural convection between concentric cylinders]]&lt;br /&gt;
** [[Non-Newtonian fluid]]&lt;br /&gt;
* [[Computational electromagnetics]]&lt;br /&gt;
** [[Triple dielectric step in 1D]]&lt;br /&gt;
** [[Scattering from an infinite cylinder]]&lt;br /&gt;
** [[Point source near an anisotropic lens]]&lt;br /&gt;
* Other applications&lt;br /&gt;
** [[Attenuation due to liquid water content in the atmosphere|Attenuation of a satellite communication]]&lt;br /&gt;
** [[Heart rate variability detection]]&lt;br /&gt;
** [[Bioheat equation]]&lt;br /&gt;
&lt;br /&gt;
== Performance analyses ==&lt;br /&gt;
* [[Execution on Intel® Xeon Phi™ co-processor]]&lt;br /&gt;
* [[1D MLSM and FDM comparison]]&lt;br /&gt;
* [[:File:tech_report.pdf|Execution overheads due to clumsy types::technical report]] [[File:pdf-file.gif]]&lt;br /&gt;
* [[Solving sparse systems]]&lt;br /&gt;
* [[Eigen paralelization]]&lt;br /&gt;
&lt;br /&gt;
== Last changes ==&lt;br /&gt;
&amp;lt;news unique=1 limit = 5&amp;gt;&lt;br /&gt;
*{{{timeanddate}}} :: {{{title}}} &lt;br /&gt;
&lt;br /&gt;
&amp;lt;/news&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Miscellaneous ==&lt;br /&gt;
* FAQ  - [[Frequently asked questions]]. &lt;br /&gt;
* [[List of wiki contributors]]&lt;br /&gt;
* List of library contributors: [http://e6.ijs.si/medusa/about#about-contributors See the official website]&lt;br /&gt;
&lt;br /&gt;
== References ==&lt;br /&gt;
&lt;br /&gt;
For all related papers including conference contributions, monographs and book chapters check https://e6.ijs.si/ParallelAndDistributedSystems/publications/&lt;br /&gt;
&lt;br /&gt;
{{Box-round|title= Selected papers |&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/153678339.pdf M. Jančič, G. Kosec; Strong form mesh‑free hp‑adaptive solution of linear elasticity problem, Engineering with computers, vol. 39, 2023 [DOI: 10.1007/s00366-023-01843-6]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/98533123.pdf M. Depolli, J. Slak, G. Kosec; Parallel domain discretization algorithm for RBF-FD and other meshless numerical methods for solving PDEs, Computers &amp;amp; Structures, 2022 [DOI: 10.1016/j.compstruc.2022.106773]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/69777155.pdf J. Slak, G. Kosec; Medusa : A C++ library for solving PDEs using strong form mesh-free methods, ACM transactions on mathematical software, vol. 47, 2021 [DOI: 10.1145/3450966]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/56730115.pdf U. Duh, G. Kosec, J. Slak; Fast variable density node generation on parametric surfaces with application to mesh-free methods, SIAM journal on scientific computing, vol. 43, 2021 [DOI: 10.1137/20M1325642]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/52715011.pdf M. Jančič, J. Slak, G. Kosec; Monomial augmentation guidelines for RBF-FD from accuracy versus computational time perspective, Journal of scientific computing, vol. 87, 2021 [DOI: 10.1007/s10915-020-01401-y]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/32782887.pdf J. Slak, G. Kosec; On generation of node distributions for meshless PDE discretizations, SIAM journal on scientific computing, vol. 41, 2019 [DOI: 10.1137/18M1231456]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/32424999.pdf G. Kosec, J. Slak, M. Depolli, R. Trobec, K. Pereira, S. Tomar, T. Jacquemin, S. Bordas, M. Wahab; Weak and strong from meshless methods for linear elastic problem under fretting contact conditions, Tribology international, vol. 138, 2019]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/32230439.pdf J. Slak, G. Kosec; Adaptive radial basis function-generated finite differences method for contact problems, International journal for numerical methods in engineering, vol. 119, 2019 [DOI: 10.1002/nme.6067]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/32388135.pdf M. Maksić, V. Djurica, A. Souvent, J. Slak, M. Depolli, G. Kosec; Cooling of overhead power lines due to the natural convection, International journal of electrical power &amp;amp; energy systems, 2019]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/31107623.pdf J. Slak, G. Kosec; Refined meshless local strong form solution of Cauchy-Navier equation on an irregular domain, Engineering analysis with boundary elements, vol. 100, 2019]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/29639719.pdf M. Depolli, G. Kosec; Assessment of differential evolution for multi-objective optimization in a natural convection problem solved by a local meshless method, Engineering optimization, 2017, vol. 49, no. 4, pp. 675-692]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/29512743.pdf G. Kosec; A local numerical solution of a fluid-flow problem on an irregular domain, Advances in engineering software, vol. 120, 2018 [DOI: 10.1016/j.advengsoft.2016.05.010]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/27912487.pdf G. Kosec, R. Trobec; Simulation of semiconductor devices with a local numerical approach, Engineering analysis with boundary elements, 2015 [DOI: 10.1016/j.enganabound.2014.07.013]]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/3218939.pdf G. Kosec, B. Šarler; Simulation of macrosegregation with mesosegregates in binary metallic casts by a meshless method, Engineering analysis with boundary elements]&lt;br /&gt;
&lt;br /&gt;
[http://comms.ijs.si/~gkosec/data/papers/27339815.pdf G. Kosec, M. Depolli, A. Rashkovska, R. Trobec; Super linear speedup in a local parallel meshless solution of thermo-fluid problem, Computers &amp;amp; Structures, vol. 133, 2014]&lt;br /&gt;
&lt;br /&gt;
[http://comms.ijs.si/~gkosec/data/papers/26785063.pdf G. Kosec, P. Zinterhof; Local strong form meshless method on multiple Graphics Processing Units, Computer modeling in engineering &amp;amp; sciences, vol. 91, 2013]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/2599419.pdf G. Kosec, B. Šarler; Solution of a low Prandtl number natural convection benchmark by a local meshless method, International journal of numerical methods for heat &amp;amp; fluid flow]&lt;br /&gt;
&lt;br /&gt;
[http://comms.ijs.si/~gkosec/data/papers/EABE2499.pdf R. Trobec, G. Kosec, M. Šterk, B. Šarler; Comparison of local weak and strong form meshless methods for 2-D diffusion equation, Engineering analysis with boundary elements, vol. 36, 2012]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/1905659.pdf G. Kosec, M. Založnik, B. Šarler, H. Combeau; A meshless approach towards solution of macrosegregation phenomena, Computers, materials &amp;amp; continua : CMC, vol. 22, 2011 ]&lt;br /&gt;
&lt;br /&gt;
[https://e6.ijs.si/ParallelAndDistributedSystems/publications/992507.pdf G. Kosec, B. Šarler; Solution of thermo-fluid problems by collocation with local pressure correction, International journal of numerical methods for heat &amp;amp; fluid flow, vol.18, 2008]&lt;br /&gt;
&lt;br /&gt;
R. Trobec, G. Kosec; Parallel scientific computing : theory, algorithms, and applications of mesh based and meshless methods, 2015&lt;br /&gt;
&lt;br /&gt;
}}&lt;br /&gt;
&lt;br /&gt;
==Related pages==&lt;br /&gt;
* http://e6.ijs.si/ParallelAndDistributedSystems/products/medusa/&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burger%27s_equation&amp;diff=3616</id>
		<title>Burger's equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burger%27s_equation&amp;diff=3616"/>
				<updated>2024-03-02T13:26:04Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO moved page Burger's equation to Burgers' equation: Wrong spelling of Burgers'&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;#REDIRECT [[Burgers' equation]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3615</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3615"/>
				<updated>2024-03-02T13:26:02Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: VagifO moved page Burger's equation to Burgers' equation: Wrong spelling of Burgers'&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximantion of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundary conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1,\\&lt;br /&gt;
l &amp;amp;= 1,\\&lt;br /&gt;
a &amp;amp;= 4,\\&lt;br /&gt;
b &amp;amp;= 4,\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l},\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3,\\&lt;br /&gt;
n &amp;amp;= 5,\\&lt;br /&gt;
m &amp;amp;= 4.&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$ and $h$ is the distance between consecutive nodes. But eventualy it becomes constant value. The reason is that for finer spatial discretizations we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
where $u$ and $v$ are ve;ocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find n closest nodes of each node.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (a, a));&lt;br /&gt;
double step = std::stod(argv[1]);&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(n));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We will be simulating the following initial and boundary conditions:&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matricies Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matricies. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = t_0 + tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  // set empty vector for rhs&lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Fluid_Mechanics&amp;diff=3614</id>
		<title>Fluid Mechanics</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Fluid_Mechanics&amp;diff=3614"/>
				<updated>2024-03-02T13:23:09Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Numerical examples */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;= Introduction =&lt;br /&gt;
Computational fluid dynamics (CFD) is a field of a great interest among researchers in many fields of science, e.g. studying mathematical fundaments of numerical methods, developing novel physical models, improving computer implementations, and many others. Pushing the limits of all involved fields of science helps community to deepen the understanding of several natural and technological phenomena. Weather forecast, ocean dynamics, water transport, casting, various energetic studies, etc., are just few examples where fluid dynamics plays a crucial role. The core problem of the CFD is solving the Navier-Stokes Equation or its variants, e.g. Darcy or Brinkman equation for flow in porous media. Here, we discuss basic algorithms for solving CFD problems. Check reference list on the [[Medusa|main page]] for more details about related work.&lt;br /&gt;
&lt;br /&gt;
Long story short, we want to solve&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{v}}{\partial t}+\nabla \cdot ( \rho \b{vv}) = -\nabla p+ \nabla(\mu \nabla\b{v})+\b{f}&lt;br /&gt;
\label{NavierStokes}&lt;br /&gt;
\end{equation}&lt;br /&gt;
also known as a Navier-Stokes equation. &lt;br /&gt;
&lt;br /&gt;
Note that the $\b{v}\b{v}$ stands for the tensor or dyadic product \[ \b{v}\b{v} = \b{v}\otimes\b{v} = \b{v}\b{v}^\T = \left[ \begin{matrix}&lt;br /&gt;
   {{v}_{1}}{{v}_{1}} &amp;amp; \cdots &amp;amp; {{v}_{1}}{{v}_{n}}  \\&lt;br /&gt;
   \vdots &amp;amp; \ddots &amp;amp; \vdots  \\&lt;br /&gt;
   {{v}_{n}}{{v}_{1}} &amp;amp; \cdots &amp;amp; {{v}_{n}}{{v}_{n}}  \\&lt;br /&gt;
\end{matrix} \right]\]&lt;br /&gt;
&lt;br /&gt;
Combining equation \ref{NavierStokes} and mass contiunity equation&lt;br /&gt;
\begin{equation}&lt;br /&gt;
  \frac{\partial \rho }{\partial t} + \nabla  \cdot (\rho {\bf{u}}) = 0.&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
simplifies advection term into (see [https://en.wikipedia.org/wiki/Derivation_of_the_Navier%E2%80%93Stokes_equations] for derivation)&lt;br /&gt;
&lt;br /&gt;
\[\frac{\partial \left( \rho \b{v} \right)}{\partial t}+\nabla \cdot \left( \rho \b{vv} \right)=\frac{\partial \left( \rho \b{v} \right)}{\partial t}+(\rho \b{v}\cdot \nabla )\cdot \b{v}. \]&lt;br /&gt;
&lt;br /&gt;
An example of advection term in 2D would therefore be&lt;br /&gt;
\[\left( \b{v}\cdot \nabla  \right)\b{v}=\left( \left( \begin{matrix}&lt;br /&gt;
   u  \\&lt;br /&gt;
   v  \\&lt;br /&gt;
\end{matrix} \right) \cdot \left( \begin{matrix}&lt;br /&gt;
   \frac{\partial }{\partial x}  \\&lt;br /&gt;
   \frac{\partial }{\partial y}  \\&lt;br /&gt;
\end{matrix} \right) \right)\left( \begin{matrix}&lt;br /&gt;
   u  \\&lt;br /&gt;
   v  \\&lt;br /&gt;
\end{matrix} \right)=\left( \begin{matrix}&lt;br /&gt;
   u\frac{\partial u}{\partial x}+v\frac{\partial u}{\partial y}  \\&lt;br /&gt;
   u\frac{\partial v}{\partial x}+v\frac{\partial v}{\partial y}  \\&lt;br /&gt;
\end{matrix} \right)\]&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
In many cases we are interested in the incompressible fluids (Ma&amp;lt;0.3), reducing the continuity equation to&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\nabla \cdot \b{v}=0&lt;br /&gt;
\label{contuinity}&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
The goal of CFD is to solve system \ref{NavierStokes} and \ref{contuinity}. It is obvious that a special treatment will be needed to couple both equations. In the following discussion we cover some basic approaches, how this can be accomplished.&lt;br /&gt;
&lt;br /&gt;
= Solutions algorithms =&lt;br /&gt;
== Artificial compressibility method ==&lt;br /&gt;
The simplest, completely explicit approach, is an artificial compressibility method (ACM), where a compressibility term is included in the mass continuity&lt;br /&gt;
\[\frac{\partial \b{v}}{\partial t}+(\b{v}\cdot\nabla )\b{v}=-\frac{1}{\rho }\nabla p+\nu {{\nabla }^{2}}\b{v}+\b{f}\]&lt;br /&gt;
\[\frac{ 1 }{ \rho } \frac{\partial \rho }{\partial t}+\nabla \cdot \b{v}=0\]&lt;br /&gt;
\[\frac{ 1 }{ \rho } \frac{\partial \rho }{\partial p}\frac{\partial p}{\partial t}+\nabla \cdot \b{v}=0\]&lt;br /&gt;
Now, the above system can be solved directly.&lt;br /&gt;
&lt;br /&gt;
The addition of the time derivative of the pressure term physically means that waves of finite speed (the propagation of which depends on the magnitude of the ACM)&lt;br /&gt;
are introduced into the flow field as a mean to distribute the pressure within the domain. In a true&lt;br /&gt;
incompressible flow, the pressure field is affected instantaneously throughout the whole domain. In ACM there is a time delay between the flow disturbance and its effect on the&lt;br /&gt;
pressure field. Upon rearranging the equation yields&lt;br /&gt;
\[\frac{\partial p}{\partial t}+\rho {{C}^{2}}\nabla \cdot \b{v}=0\]&lt;br /&gt;
where the continuity equation is perturbed by the quantity $\frac{\partial p}{\partial t}$ denominated herein&lt;br /&gt;
as the AC parameter/artificial sound speed recognized by&lt;br /&gt;
$C$ [m/s] - speed of sound&lt;br /&gt;
\[\frac{1}{C^2}=\frac{\partial \rho }{\partial p}\]&lt;br /&gt;
Or in other words&lt;br /&gt;
\[C^2=\left( \frac{\partial p}{\partial \rho}\right)_S\]&lt;br /&gt;
where $\rho$ is the density of the material. It follows, by replacing partial derivatives, that the isentropic compressibility can be expressed as:&lt;br /&gt;
\[\beta =\frac{1}{\rho {{C}^{2}}}\]&lt;br /&gt;
The evaluation of the local ACM parameter in incompressible flows is inspired by the&lt;br /&gt;
speed of sound computations in compressible flows (for instance, from the perfect gas law).&lt;br /&gt;
However, in the incompressible flow situation, employing such a relation is difficult, but an artificial&lt;br /&gt;
relation can be developed from the convective and diffusive velocities.&lt;br /&gt;
Reverting to the justification of continuity modification, it can be immediately seen that the&lt;br /&gt;
artificial sound speed must be sufficiently large to have a significant regularizing effect and at&lt;br /&gt;
the same time must be as small as possible to minimizing perturbations on the incompressibility&lt;br /&gt;
equation.  Therefore, $C$ influences the convergence rate and stability of the solution method. In other words,&lt;br /&gt;
assists in reducing large disparity in the eigenvalues, leading to a well-conditioned system. &lt;br /&gt;
The $C$ can be '''estimated''' with&lt;br /&gt;
&lt;br /&gt;
\[ C = \beta \max \left( \left|\b{v}\right|_2, \left|\b{v}_{ref}\right|_2 \right),\]&lt;br /&gt;
where $\b{v}_{ref}$ stands for a reference velocity. &lt;br /&gt;
Values for $\beta$ in the range of 1–10 are recommended for better convergence to the steady state at which the&lt;br /&gt;
mass conservation is enforced. In addition, Equation ensures that $C$ does not reach zero at stagnation points&lt;br /&gt;
that cause instabilities in pseudo-time, effecting convergence&lt;br /&gt;
&lt;br /&gt;
Note, that for more complex simulation an internal iteration loops is required before marching in time. &lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;figure id=&amp;quot;ACM&amp;quot;&amp;gt;&lt;br /&gt;
[[File:ACM BlockDiagram.png|600px]]&lt;br /&gt;
&amp;lt;caption&amp;gt;Scheme of the ACM algorithm &amp;lt;/caption&amp;gt;&lt;br /&gt;
&amp;lt;/figure&amp;gt;&lt;br /&gt;
&lt;br /&gt;
== Explicit/Implicit pressure calculation ==&lt;br /&gt;
&lt;br /&gt;
Applying divergence on \ref{NavierStokes} yields&lt;br /&gt;
\[\nabla \cdot \frac{\partial \b{v}}{\partial t}+\nabla \cdot (\b{v}\cdot \nabla )\b{v}=-\frac{1}{\rho }{{\nabla }^{2}}p+\nabla \cdot \nu {{\nabla }^{2}}\b{v}+\nabla \cdot \b{f}\]&lt;br /&gt;
&lt;br /&gt;
And since $\nabla \cdot \b{v}=0$ and we can change order in $\nabla \cdot \nabla^2$ and $ \nabla^2 \cdot \nabla$ equation simplifies to&lt;br /&gt;
\[\frac{1}{\rho }{{\nabla }^{2}}p=\nabla \cdot \b{f}-\nabla \cdot (\b{v}\cdot \nabla )\b{v}\]&lt;br /&gt;
Now, we need boundary conditions that can be obtained by multiplying the equation  with a boundary normal vector&lt;br /&gt;
\[\b{\hat{n}}\cdot \left( \frac{\partial \b{v}}{\partial t}+(\b{v}\cdot \nabla )\b{v} \right)=\b{\hat{n}}\cdot \left( -\frac{1}{\rho }\nabla p+\nu {{\nabla }^{2}}\b{v}+\b{f} \right)\cdot \b{\hat{n}}\]&lt;br /&gt;
\[\frac{\partial p}{\partial \b{\hat{n}}}=\left( \nu {{\nabla }^{2}}\b{v}+\b{f}-\frac{\partial \b{v}}{\partial t}-(\b{v}\cdot\nabla ) \b{v} \right)\cdot \b{\hat{n}}\]&lt;br /&gt;
&lt;br /&gt;
Note that using tangential boundary vector gives equivalent BCs&lt;br /&gt;
\[\frac{\partial p}{\partial \b{\hat{t}}}=\left( \nu {{\nabla }^{2}}\b{v}+\b{f}-\frac{\partial \b{v}}{\partial t}-(\b{v}\cdot\nabla ) \b{v} \right)\cdot \b{\hat{t}}\]&lt;br /&gt;
For no-slip boundaries BCs simplify to&lt;br /&gt;
\[\frac{\partial p}{\partial \b{\hat{n}}}=\left( \nu {{\nabla }^{2}}\b{v}+\b{f} \right)\cdot \b{\hat{n}}\]&lt;br /&gt;
Otherwise an appropriate expression regarding the velocity can be written, i.e. write full  and taken in account velocity BCs. For example, Neumann velocity $\frac{\partial u}{\partial x}=0$ in 2D&lt;br /&gt;
\[\frac{\partial p}{\partial x}=\left( \nu {{\nabla }^{2}}u + {{f}_{x}}-\frac{\partial u}{\partial t}+v\frac{\partial u}{\partial y} \right)\]&lt;br /&gt;
Note that you allready know everything about the velocity and thus you can compute all the terms explicitly.&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
So the procedure is:&lt;br /&gt;
&lt;br /&gt;
* Compute Navier Stokes either explicitly or implicitly&lt;br /&gt;
* Solve pressure equations with  computed velocities&lt;br /&gt;
* March in time&lt;br /&gt;
&lt;br /&gt;
Basic boundary conditions&lt;br /&gt;
Wall:   $\b{v}=0$, \[\frac{\partial p}{\partial \hat{n}}=\left( \nabla \cdot \left( \nu \nabla \b{v} \right)+\b{f} \right)\cdot \hat{n}\]&lt;br /&gt;
Inlet:  $\b{v}=\b{a}$, \[\frac{\partial p}{\partial \hat{n}}=\left( \nabla \cdot \left( \nu \nabla \b{v} \right)+\b{f}-\nabla \cdot (\rho \b{v}\b{v})-\rho \frac{\partial \b{v}}{\partial t} \right)\cdot \hat{n}\]&lt;br /&gt;
&lt;br /&gt;
Above system can be linearized (advection term) and solved either explicitly or implicitly.&lt;br /&gt;
&lt;br /&gt;
Further reading:&lt;br /&gt;
&lt;br /&gt;
W. D. Henshaw, A fourth-order accurate method for the incompressible Navier–Stokes equations on overlapping grids, J. Comput. Phys. 113, 13 (1994)&lt;br /&gt;
&lt;br /&gt;
J. C. Strikwerda, Finite difference methods for the Stokes and Navier–Stokes equations, SIAM J. Sci. Stat.&lt;br /&gt;
Comput. 5(1), 56 (1984)&lt;br /&gt;
&lt;br /&gt;
== Explicit Pressure correction ==&lt;br /&gt;
Another possibility is to solve pressure correction equation. Again Consider the momentum equation and mass continuity and discretize it explicitly&lt;br /&gt;
\[\frac{{{\b{v}}_{2}}-{{\b{v}}_{1}}}{\Delta t}=-\frac{1}{\rho }\nabla {{p}_{1}}-({{\b{v}}_{1}}\nabla )\cdot {{\b{v}}_{1}}+\nu {{\nabla }^{2}}{{\b{v}}_{1}}+\b{f}\]&lt;br /&gt;
Computed velocity obviously does not satisfy the mass contunity and therefore let’s call it intermediate velocity. Intermediate velocity is calculated from guessed pressure and old velocity values.&lt;br /&gt;
\[{{\b{v}}^{inter}}=\b{v}_1 + \Delta t\left( -\frac{1}{\rho }\nabla {{p}_{1}}-({{\b{v}}_{1}}\nabla )\cdot {{\b{v}}_{1}}+\nu {{\nabla }^{2}}{{\b{v}}_{1}}+\b{f} \right)\]&lt;br /&gt;
A correction term is added that drives velocity to divergence free field&lt;br /&gt;
\[\nabla \cdot ({{\b{v}}^{inter}}+{{\b{v}}^{corr}})=0 \qquad \to \qquad \nabla \cdot {{\b{v}}^{inter}}=-\nabla \cdot {{\b{v}}^{corr}}\]&lt;br /&gt;
&lt;br /&gt;
Velocity correction is affected only by effect of pressure correction. This fact is obvious due to all terms except gradient of pressure on the right side of equation  are constant.&lt;br /&gt;
\[{{\b{v}}^{corr}}=-\frac{\Delta t}{\rho }\nabla {{p}^{corr}} \]&lt;br /&gt;
&lt;br /&gt;
Note that corrected velocity also satisfies boundary conditions&lt;br /&gt;
\[\b{v}^{iter}+\b{v}^{corr}=\b{v}^{BC}\]&lt;br /&gt;
Applying divergence  and  we get '''pressure correction poisson equation'''.&lt;br /&gt;
\[\,{{\nabla }^{2}}{{p}^{corr}}\,=\frac{\rho }{\Delta t}\nabla \cdot {{\mathbf{v}}^{iter}}\,\]&lt;br /&gt;
&lt;br /&gt;
Boundary conditions can be obtained by mulitplying the equation with a unit normal vector $\b{\hat{n}}$&lt;br /&gt;
\[\frac{\Delta t}{\rho }\frac{\partial {p}^{corr}}{\partial \b{\hat{n}}} = \b{\hat{n}} \cdot \left(\b{v}^{iter} - \b{v}^{BC} \right) \]&lt;br /&gt;
The most straightforward approach, for dirichlet BCs, is to take into account velocity boundary condition in computation of intermediate velocity, and clearly in such cases, pressure boundary condition simplifies to &lt;br /&gt;
\[\frac{\partial p^{corr}}{\partial \b{\hat{n}}} = 0 \]&lt;br /&gt;
As ${{\b{v}}^{\operatorname{int}er}}={{\b{v}}^{BC}}$ . Another option is to explicitely compute intermediate velocity also on boundaries and then correct it through pressure correction.&lt;br /&gt;
&lt;br /&gt;
The pressure poisson equation is, at given boundary conditions, defined only up to a constant. One solution is to select a node and set it to a constant, e.g. p(0, 0) = 0, however much more stable approach is to enforce solution with additional condition, also referred to as a regularization&lt;br /&gt;
	\[\int_{\Omega }^{{}}{pd}\Omega =0\]&lt;br /&gt;
\[\,{{\nabla }^{2}}{{p}^{corr}}\,-\alpha =\frac{\rho }{\Delta t}\nabla \cdot {{\b{v}}^{iter}}\,\]&lt;br /&gt;
Where $\alpha $ stands for Lagrange multiplier. Or in discrete form&lt;br /&gt;
	\[\sum\limits_{i}{p\left( {{x}_{i}} \right)=0}\]&lt;br /&gt;
	\[\b{Mp}-\alpha \b{1}=\frac{\rho }{\Delta t}\nabla \cdot {{\b{v}}^{iter}}\]&lt;br /&gt;
&lt;br /&gt;
where $\b{M}$ holds Laplace shape functions, i.e. the discrete version of Laplace differential operator. &lt;br /&gt;
&lt;br /&gt;
Solution of a system&lt;br /&gt;
&lt;br /&gt;
	\[\left[ \begin{matrix}&lt;br /&gt;
   {{M}_{11}} &amp;amp; .. &amp;amp; {{M}_{1n}} &amp;amp; 1  \\&lt;br /&gt;
   .. &amp;amp; .. &amp;amp; .. &amp;amp; 1  \\&lt;br /&gt;
   {{M}_{n1}} &amp;amp; ... &amp;amp; {{M}_{nn}} &amp;amp; 1  \\&lt;br /&gt;
   1 &amp;amp; 1 &amp;amp; 1 &amp;amp; 0  \\&lt;br /&gt;
\end{matrix} \right]\left[ \begin{matrix}&lt;br /&gt;
   {{p}_{1}}  \\&lt;br /&gt;
   ...  \\&lt;br /&gt;
   {{p}_{n}}  \\&lt;br /&gt;
   \alpha   \\&lt;br /&gt;
\end{matrix} \right]=\frac{\rho }{\Delta t}\left[ \begin{matrix}&lt;br /&gt;
   \nabla \cdot \b{v}_{_{1}}^{\text{iter}}  \\&lt;br /&gt;
   ...  \\&lt;br /&gt;
   \nabla \cdot \b{v}_{n}^{\text{iter}}  \\&lt;br /&gt;
   0  \\&lt;br /&gt;
\end{matrix} \right]\]&lt;br /&gt;
Gives us a solution of pressure correction.&lt;br /&gt;
&lt;br /&gt;
== CBS Algorithm ==&lt;br /&gt;
With explicit temporal discretization problem is formulated as&lt;br /&gt;
\[\b{\hat{v}}={{\b{v}}_{0}}+\Delta t\left( -\nabla {{p}_{0}}+\frac{1}{Re}{{\nabla }^{2}}{{\b{v}}_{0}}-\nabla \cdot ({{\b{v}}_{0}}{{\b{v}}_{0}}) \right)\]&lt;br /&gt;
\[p={{p}_{0}}-\xi \Delta {{t}_{F}}\nabla \b{\hat{v}}+\xi \Delta {{t}_{F}}\Delta t{{\nabla }^{2}}{{\overset{\scriptscriptstyle\frown}{P}}_{0}},\]&lt;br /&gt;
where $\b{\hat{v}}$, $\Delta t$, $\xi$ and $\Delta t_F$ stand for intermediate velocity, time step, relaxation parameter, and artificial time step, respectively, and index 0 stands for previous time / iteration step. First, the intermediate velocity is computed from previous time step. Second, the velocity is driven towards solenoidal field by correcting the pressure. Note that no special boundary conditions for pressure are used, i.e., the pressure on boundaries is computed with the same approach as in the interior of the domain. In general, the internal iteration with an artificial time step is required until the divergence of the velocity field is not below required criteria. However, if one is interested only in a steady-state solution, the internal iteration can be skipped and $\Delta t$ equals $\Delta {{t}_{F}}$. Without internal stepping the transient of the solution is distorted by artificial compressibility effect. This approach is also known as ACM with Characteristics-based discretization of continuity equation, where the relaxation parameter relates to the artificial speed of sound [35].&lt;br /&gt;
&lt;br /&gt;
The relaxation parameter should be set between 1-10, lower number more stable solution.&lt;br /&gt;
&lt;br /&gt;
And also dimensional form&lt;br /&gt;
&lt;br /&gt;
\[p={{p}_{0}}-{{C}^{2}}\Delta {{t}_{F}}\rho \nabla \b{\hat{v}}+{{C}^{2}}\Delta {{t}_{F}}\Delta t{{\nabla }^{2}}{{p}_{0}},\]&lt;br /&gt;
&lt;br /&gt;
Where C is speed of sound [m/s]&lt;br /&gt;
&lt;br /&gt;
== The Stream Function - Vorticity Approach ==&lt;br /&gt;
&lt;br /&gt;
In two dimensions, the Navier–Stokes equations can be expressed using the&lt;br /&gt;
stream function $\psi$ and the vorticity $\omega$ in place of the primitive variables $u$, $v$, and&lt;br /&gt;
$p$. This involves the elimination of the pressure $p$, thus yielding one dependent&lt;br /&gt;
variable less. In three dimensions, however, this formulation leads to six unknowns&lt;br /&gt;
rather than four (in primitive variables), which makes this approach less attractive&lt;br /&gt;
for that case.&lt;br /&gt;
&lt;br /&gt;
In the following, we briefly derive the resulting two-dimensional equations for&lt;br /&gt;
$\psi$ and $\omega$. We begin by considering the momentum equations:&lt;br /&gt;
\begin{align}&lt;br /&gt;
\frac{\partial u}{\partial t} + u \frac{\partial u}{\partial x} + v \frac{\partial u}{\partial y}&amp;amp; = -\frac{1}{\rho}\frac{\partial p}{\partial x} + \nu \left(\frac{\partial^2 u}{\partial x^2} + \frac{\partial^2 u}{\partial y^2}\right) + g_x \\&lt;br /&gt;
\frac{\partial v}{\partial t} + u \frac{\partial v}{\partial x} + v \frac{\partial v}{\partial y}&amp;amp; = -\frac{1}{\rho}\frac{\partial p}{\partial y} + \nu \left(\frac{\partial^2 v}{\partial x^2} + \frac{\partial^2 v}{\partial y^2}\right) + g_y&lt;br /&gt;
\end{align}&lt;br /&gt;
from which we can eliminate the pressure by differentiating the first equation by $y$ and the second by $x$, subtracting the second from the first and then substituting the definition for the vorticity $\omega = \partial u / \partial y - \partial v / \partial x$. In this manner we obtain the equation:&lt;br /&gt;
\[\frac{\partial \omega}{\partial t} + \frac{\partial u}{\partial x} \omega + u \frac{\partial \omega}{\partial x} + \frac{\partial v}{\partial y}\omega + v \frac{\partial \omega}{\partial y} = \nu \left(\frac{\partial^2 \omega}{\partial x^2} + \frac{\partial^2 \omega}{\partial y^2}\right) + \left(\frac{\partial g_x}{\partial y} - \frac{\partial g_y}{\partial x}\right)\]&lt;br /&gt;
&lt;br /&gt;
We can get rid of the terms containing $\partial u / \partial x$ and $\partial v / \partial y$ by using the continuity equation \eqref{contuinity}. Finally using the definition for the stream function $\partial \psi / \partial y = u$ and $\partial \psi / \partial x = -v$ we can transform the above equation to what is known as the ''vorticity transport equation'':&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \omega}{\partial t} + \frac{\partial \psi}{\partial y}\frac{\partial \omega}{\partial x} - \frac{\partial \psi}{\partial x}\frac{\partial \omega}{\partial y} = \nu \Delta \omega + \left(\frac{\partial g_x}{\partial y} - \frac{\partial g_y}{\partial x}\right)&lt;br /&gt;
\label{vorticity_transport}&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
Another equation is obtained by inserting the definition of the stream function into that of the vorticity:&lt;br /&gt;
\[\omega = \frac{\partial u}{\partial y} - \frac{\partial v}{\partial x} = \frac{\partial}{\partial y}\left(\frac{\partial \psi}{\partial y}\right) + \frac{\partial }{\partial x}\left(\frac{\partial \psi}{\partial x}\right) = \Delta \psi\]&lt;br /&gt;
leading to the ''Poisson equation for the stream function''&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\Delta \psi = \omega&lt;br /&gt;
\label{poisson_stream}&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
The two equations (\ref{vorticity_transport}) and (\ref{poisson_stream}) form a nonlinear coupled system of equations in which pressure has been eliminated and in which the continuity equation has been satisfied automatically.&lt;br /&gt;
&lt;br /&gt;
= Numerical examples =&lt;br /&gt;
* [[Lid driven cavity]]&lt;br /&gt;
* [[Burgers' equation]]&lt;br /&gt;
* [[de Vahl Davis natural convection test]]&lt;br /&gt;
* [[Natural convection in 3D irregular domain]]&lt;br /&gt;
* [[Natural convection from heated cylinder]]&lt;br /&gt;
* [[Natural convection between concentric cylinders]]&lt;br /&gt;
* [[Non-Newtonian fluid]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3613</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3613"/>
				<updated>2024-03-02T13:21:47Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burgers' equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximantion of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundary conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1,\\&lt;br /&gt;
l &amp;amp;= 1,\\&lt;br /&gt;
a &amp;amp;= 4,\\&lt;br /&gt;
b &amp;amp;= 4,\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l},\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3,\\&lt;br /&gt;
n &amp;amp;= 5,\\&lt;br /&gt;
m &amp;amp;= 4.&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$ and $h$ is the distance between consecutive nodes. But eventualy it becomes constant value. The reason is that for finer spatial discretizations we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N$ and $m$ to 1024 and 4 and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;br /&gt;
&lt;br /&gt;
=Solution in 2D=&lt;br /&gt;
In 2D the Burgers' equation (1) gives a system of two coupled nonlinear equations:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
\frac{\partial u}{\partial t} + u\frac{\partial u}{\partial x} + v\frac{\partial u}{\partial y} = \nu\nabla^2u,\\&lt;br /&gt;
\frac{\partial v}{\partial t} + u\frac{\partial v}{\partial x} + v\frac{\partial v}{\partial y} = \nu\nabla^2v,&lt;br /&gt;
\end{gather}&lt;br /&gt;
where $u$ and $v$ are ve;ocities in the $x$ and $y$ direction respectively. We discretize equations (4) and (5) in the same way as in the 1D case:&lt;br /&gt;
\begin{gather}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} + v^{n-1}_i\frac{\partial u^{n}}{\partial y} - \nu\nabla^2u^{n}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
v^{n}_i + \Bigl[u^{n-1}_i\frac{\partial v^{n}}{\partial x} + v^{n-1}_i\frac{\partial v^{n}}{\partial y} - \nu\nabla^2v^{n}\Bigr]\Delta t = v^{n-1}_i .&lt;br /&gt;
\end{gather}&lt;br /&gt;
We will solve each equation separately every time step, this will enable us to partialy parallelize the algorithm. First we contruct a 2D square domain with side length of 1 and find n closest nodes of each node.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
BoxShape&amp;lt;Vec2d&amp;gt; b((0, 0), (a, a));&lt;br /&gt;
double step = std::stod(argv[1]);&lt;br /&gt;
auto discretization = b.discretizeBoundaryWithStep(step);&lt;br /&gt;
GeneralFill&amp;lt;Vec2d&amp;gt; fill; fill.seed(0);&lt;br /&gt;
discretization.fill(fill, step);&lt;br /&gt;
Find support nodes&lt;br /&gt;
discretization.findSupport(FindClosest(n));&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
We will be simulating the following initial and boundary conditions:&lt;br /&gt;
\begin{gather*}&lt;br /&gt;
u(x,y,0) = sin(\pi x)cos(\pi y),\\&lt;br /&gt;
v(x,y,0) = cos(\pi x)sin(\pi y),\\&lt;br /&gt;
u(0,y,t)=u(1,y,t)=v(x,0,t)=v(x,1,t)=0.&lt;br /&gt;
\end{gather*}&lt;br /&gt;
The analytical solution is given here &amp;lt;ref name=&amp;quot;Analytical 2D&amp;quot;&amp;gt;Q. Gao, M.Y. Zou, An analytical solution for two and three dimensional nonlinear Burgers' equation, Applied Mathematical Modelling, Volume 45, 2017, Pages 255-270, ISSN 0307-904X, https://doi.org/10.1016/j.apm.2016.12.018.&amp;lt;/ref&amp;gt;. In the time stepping loop we use &amp;lt;code&amp;gt;#pragma omp parallel sections&amp;lt;/code&amp;gt; to enable concurrent solving for $u$ and $v$. In each section we define separate matricies Mu or Mv, rhs vectors rhu and rhv, and implicit operators opu and opv which will write into their respective matricies. The code bellow includes only the section for $u$. The types -1 ,-2 , -3 ,-4 correspond to the left, right, bottom and top boundaries. At the end of each section the velocities are updated.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
&lt;br /&gt;
double t = t_0 + tt * dt;&lt;br /&gt;
&lt;br /&gt;
#pragma omp parallel sections shared(domain_size, discretization, storage, dt, u, v) &lt;br /&gt;
{&lt;br /&gt;
    ////Solving for u////&lt;br /&gt;
    #pragma omp section&lt;br /&gt;
    {&lt;br /&gt;
        SparseMatrix&amp;lt;double, RowMajor&amp;gt; Mu(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
        VectorXd rhu = VectorXd::Zero(domain_size);  // set empty vector for rhs&lt;br /&gt;
        auto opu = storage.implicitOperators(Mu, rhu);        &lt;br /&gt;
        Mu.reserve(storage.supportSizes());&lt;br /&gt;
&lt;br /&gt;
        // Setting the equation &lt;br /&gt;
        #pragma omp parallel for&lt;br /&gt;
        for (int i : discretization.interior()) {&lt;br /&gt;
            opu.value(i) + (u[i]*dt) * opu.der1(i, 0) + (v[i]*dt) * opu.der1(i, 1) + (-1/rndls*dt) * opu.lap(i) = u[i];  &lt;br /&gt;
        }&lt;br /&gt;
        // Boundary conditions&lt;br /&gt;
        for (int i : discretization.types() == -3){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -4){&lt;br /&gt;
            opu.value(i) = analytical_u(discretization.pos(i,0), discretization.pos(i,1), t, 1.0/rndls);&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -1){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        for (int i : discretization.types() == -2){&lt;br /&gt;
            opu.value(i) = 0.0;&lt;br /&gt;
        }&lt;br /&gt;
        Mu.makeCompressed(); &lt;br /&gt;
        BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solveru; // Creating solver object&lt;br /&gt;
        solveru.compute(Mu); // Initialize the iterative solver&lt;br /&gt;
        VectorXd u2 = solveru.solve(rhu);&lt;br /&gt;
        u = u2;&lt;br /&gt;
    }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
=References=&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3612</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3612"/>
				<updated>2024-02-02T21:46:14Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Solution in 1D */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burger's equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximant of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundary conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1\\&lt;br /&gt;
l &amp;amp;= 1\\&lt;br /&gt;
a &amp;amp;= 4\\&lt;br /&gt;
b &amp;amp;= 4\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l}\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3\\&lt;br /&gt;
n &amp;amp;= 5\\&lt;br /&gt;
m &amp;amp;= 4&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$. But eventualy it reaches a constant value. We can see that for bigger $N$ we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N = 1024$ and $m = 4$ and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3611</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3611"/>
				<updated>2024-02-02T21:42:32Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: /* Solution in 1D */&lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burger's equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximant of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository).&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundarz conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1\\&lt;br /&gt;
l &amp;amp;= 1\\&lt;br /&gt;
a &amp;amp;= 4\\&lt;br /&gt;
b &amp;amp;= 4\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l}\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3\\&lt;br /&gt;
n &amp;amp;= 5\\&lt;br /&gt;
m &amp;amp;= 4&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$. But eventualy it reaches a constant value. We can see that for bigger $N$ we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N = 1024$ and $m = 4$ and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3610</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3610"/>
				<updated>2024-01-23T11:37:55Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burger's equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximant of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundarz conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1\\&lt;br /&gt;
l &amp;amp;= 1\\&lt;br /&gt;
a &amp;amp;= 4\\&lt;br /&gt;
b &amp;amp;= 4\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l}\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3\\&lt;br /&gt;
n &amp;amp;= 5\\&lt;br /&gt;
m &amp;amp;= 4&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in \{2, 4, 6, 8\}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$. But eventualy it reaches a constant value. We can see that for bigger $N$ we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N = 1024$ and $m = 4$ and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	<entry>
		<id>http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3609</id>
		<title>Burgers' equation</title>
		<link rel="alternate" type="text/html" href="http://e6.ijs.si/medusa/wiki/index.php?title=Burgers%27_equation&amp;diff=3609"/>
				<updated>2024-01-23T11:12:28Z</updated>
		
		<summary type="html">&lt;p&gt;VagifO: &lt;/p&gt;
&lt;hr /&gt;
&lt;div&gt;Let us consider the Burger's equation, which describes the dynamics of viscous fluid without the effects of pressure. Despite being unrealistic, it is the simplest description of advective flow with diffusive effects of viscosity. It has the following form:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\frac{\partial \b{u}}{\partial t} + (\b{u}\cdot\nabla)\b{u} = \nu \nabla^2 \b{u} ,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where $\b{u}$ is a velocity field. For time stepping the advection term must be linearized. There are several tactics of linearization, but we choose the simplest one, which is using the previous velocity as an approximant of the current velociy. Using the implicit Euler method we arrive at the equation&lt;br /&gt;
&lt;br /&gt;
\begin{equation}&lt;br /&gt;
\b{u}^{n}_i + \Bigl[\b{u}^{n-1}_i\cdot\nabla_i\b{u}^{n} - \nu\nabla^2_i \b{u}^{n}\Bigr]\Delta t  = \b{u}^{n-1}_i,&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
where the subscript $i$ is the index of the domain node, superscripts $n$ and $n-1$ denote the current and the previous time step respectively, $\Delta t$ is the length of the time step. The notation $\nabla_i$ means the gradient operator at node $i$, not the component of the gradient. Same holds for $\nabla^2_i$. Using the Medusa library, this equation can be solved for $\b{u}^{n}_i$.&lt;br /&gt;
&lt;br /&gt;
=Solution in 1D=&lt;br /&gt;
Let's look at an example in 1D with Dirichlet boundary conditions. We are solving the equivalent of equation (2) but with only one spatial variable:&lt;br /&gt;
\begin{equation}&lt;br /&gt;
u^{n}_i + \Bigl[u^{n-1}_i\frac{\partial u^{n}}{\partial x} - \nu\frac{\partial^2 u^{n}}{\partial x^2}\Bigr]\Delta t = u^{n-1}_i ,\\&lt;br /&gt;
u_i = 0 \text{ on } \partial \Omega .&lt;br /&gt;
\end{equation}&lt;br /&gt;
&lt;br /&gt;
This will be rewritten into a linear system of equations with Medusa and then solved with Eigen. Let's look at the code(find the full version in our repository)&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
// Build 1D domain&lt;br /&gt;
BoxShape&amp;lt;Vec1d&amp;gt; domain((-l), (l)); // the interval [-l, l]&lt;br /&gt;
auto discretization = domain.discretizeWithStep(dx);&lt;br /&gt;
int domain_size = discretization.size();&lt;br /&gt;
// Find support nodes&lt;br /&gt;
FindClosest find_support(5);&lt;br /&gt;
discretization.findSupport(find_support);&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
First we construct our 1D domain with lenght 2l, discretize it with a given step dx and find the closest 5 nodes of each node which are called stencil nodes.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
Polyharmonic&amp;lt;double, 3&amp;gt; p;&lt;br /&gt;
Monomials&amp;lt;Vec1d&amp;gt; mon(4);&lt;br /&gt;
RBFFD&amp;lt;Polyharmonic&amp;lt;double, 3&amp;gt;, Vec1d, ScaleToClosest&amp;gt;&lt;br /&gt;
        approx(p, mon);&lt;br /&gt;
auto storage = discretization.computeShapes(approx); &lt;br /&gt;
VectorXd E1 = VectorXd::Zero(domain_size);  // vector for containing the current state&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We will use polyharmonic splines of order 3 as Radial Basis Functions(RBFs) augmented with monomials up to 4th order for our approximation engine. We use it in conjunction with stencil nodes to compute the stencil weights(also called shape functions) which are used in approximations of differential operators on our domain. Next we set the initial condition to $$u(x, 0) = \frac{2\nu ake^{-\nu k^2t}\sin(kx)}{b + ae^{-\nu k^2t}\cos(kx)},$$ where $a, b, k$ are constants. We chose this function because it has an analytical solution which we will compare our solution to. It is derived using the Cole-Hopf transformation of a solution of the diffusion equation with the initial profile $f(x) = b + a\cos(kx)$ for $b&amp;gt;a$.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    //Set initial condition &lt;br /&gt;
    for (int i : discretization.all()){ &lt;br /&gt;
        E1(i) = 2*visc*a*k*std::exp(-visc*pow(k,2)*t_0)*std::sin(k*discretization.pos(i,0))/(b+a*std::exp(- &lt;br /&gt;
        visc*pow(k,2)*t_0)*std::cos(k*discretization.pos(i,0)));&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Inside our time-stepping loop, we instantiate the matrix $M$ and the vector $rhs$ representing the linear system. We then construct the differential operators for implicit solving.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
for (tt = 1; tt &amp;lt;= t_steps; ++tt) {&lt;br /&gt;
    SparseMatrix&amp;lt;double&amp;gt; M(domain_size, domain_size); // construct matrix of apropreate size&lt;br /&gt;
    VectorXd rhs = VectorXd::Zero(domain_size); &lt;br /&gt;
    M.reserve(Range&amp;lt;int&amp;gt;(domain_size, n));&lt;br /&gt;
    auto op = storage.implicitOperators(M, rhs);  // compute the implicit operators        &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now comes the crucial part - setting the equation. In essence, we rewrite equation (3) in Medusa's terms. Terms on the left hand side are written into matrix $M$ and the terms on the right into $rhs$. The terms with the prefix op. are matrices that represent the appropriate differential operators(op.value(i) is a matrix with $A_{ii}$ = 1 and zero everywhere else), vector E1[i] represents $\b{u}_i^{n-1}$ - result of the previous step.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting the equation &lt;br /&gt;
    for (int i : interior) {&lt;br /&gt;
        op.value(i) + (E1[i] * dt) * op.der1(i, 0) + (-dt*visc) * op.der2(i, 0, 0) = E1[i];  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Boundary conditions are handled in the same way. Here we set Dirichlet boundarz conditions.&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    // Setting Dirichlet boundary conditions&lt;br /&gt;
    for (int i : boundary) {&lt;br /&gt;
        op.value(i) = 0;  &lt;br /&gt;
}   &lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We then solve the equation and update E1 for use in the next time step. The solution is also saved into an output file with HDF5.&lt;br /&gt;
&lt;br /&gt;
&amp;lt;syntaxhighlight lang=&amp;quot;c++&amp;quot; line&amp;gt;&lt;br /&gt;
    M.makeCompressed(); // Optimization&lt;br /&gt;
    BiCGSTAB&amp;lt;SparseMatrix&amp;lt;double, RowMajor&amp;gt;&amp;gt; solver; // Creating solver object&lt;br /&gt;
    solver.compute(M); // Initialize the iterative solver&lt;br /&gt;
&lt;br /&gt;
    // Solve the linear system and update the state&lt;br /&gt;
    VectorXd E2 = solver.solve(rhs);&lt;br /&gt;
    E1 = E2;&lt;br /&gt;
&lt;br /&gt;
    if (tt%(t_factor) == 0) {&lt;br /&gt;
        // Saving state in output file&lt;br /&gt;
        hdf_out.openGroup(&amp;quot;/step&amp;quot; + std::to_string(t_save));&lt;br /&gt;
        hdf_out.writeSparseMatrix(&amp;quot;M&amp;quot;, M);&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;TimeStep&amp;quot;, t_save);&lt;br /&gt;
        std::cout&amp;lt;&amp;lt; &amp;quot;Time step &amp;quot; &amp;lt;&amp;lt; t_save &amp;lt;&amp;lt; &amp;quot; of &amp;quot; &amp;lt;&amp;lt; t_steps/t_factor &amp;lt;&amp;lt; &amp;quot;.&amp;quot; &amp;lt;&amp;lt; std::endl;&lt;br /&gt;
        hdf_out.writeDoubleAttribute(&amp;quot;time&amp;quot;,(t_0 + tt * dt));&lt;br /&gt;
        ++t_save;&lt;br /&gt;
        hdf_out.writeDoubleArray(&amp;quot;E&amp;quot;, E1);&lt;br /&gt;
        }&lt;br /&gt;
&amp;lt;/syntaxhighlight&amp;gt;&lt;br /&gt;
&lt;br /&gt;
=Results=&lt;br /&gt;
For our simulation we used the following parameters:&lt;br /&gt;
\begin{align*}&lt;br /&gt;
\nu &amp;amp;= 1\\&lt;br /&gt;
l &amp;amp;= 1\\&lt;br /&gt;
a &amp;amp;= 4\\&lt;br /&gt;
b &amp;amp;= 4\\&lt;br /&gt;
k &amp;amp;= \frac{4\pi}{2l}\\&lt;br /&gt;
\phi(r) &amp;amp;= r^3\\&lt;br /&gt;
n &amp;amp;= 5\\&lt;br /&gt;
m &amp;amp;= 4&lt;br /&gt;
\end{align*}&lt;br /&gt;
As mentioned before the support of each node are its 5 closest nodes including the node itself. We used the RBF-FD method with polyharmonic splines of order 3 augmented with polynomials of up to order 4. Animation of the result is shown below.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_animation.mp4|400px]]&lt;br /&gt;
&lt;br /&gt;
=Convergence=&lt;br /&gt;
&amp;lt;b&amp;gt;Spatial discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
We use the RBF-FD method with PHS radial basis function $\phi(r) = r^3$ and fix the time step to $dt = 10^{-6}$. We vary the polynomial augmentation order $m \in {2, 4, 6, 8}$ and analize the convergence with increasing number of discretization nodes N. &lt;br /&gt;
&lt;br /&gt;
After some initial oscilation the error decreases as $O(h^k)$, where $k$ is the fit parameter for every $m$. But eventualy it reaches a constant value. We can see that for bigger $N$ we are bounded by the time step because of linearization.&lt;br /&gt;
&lt;br /&gt;
[[File:Error_step6.png|400px]]&lt;br /&gt;
&lt;br /&gt;
&amp;lt;b&amp;gt;Temporal discretization&amp;lt;/b&amp;gt;&lt;br /&gt;
&lt;br /&gt;
Now we fix $N = 1024$ and $m = 4$ and vary the size of the time step from $10^{-3}$ to $10^{-6}$. The error decreases linearly with the time step.&lt;br /&gt;
&lt;br /&gt;
[[File:Burgers_time_err.png|400px]]&lt;/div&gt;</summary>
		<author><name>VagifO</name></author>	</entry>

	</feed>