<?xml version="1.0" encoding="UTF-8"?>
<rss  xmlns:atom="http://www.w3.org/2005/Atom" 
      xmlns:media="http://search.yahoo.com/mrss/" 
      xmlns:content="http://purl.org/rss/1.0/modules/content/" 
      xmlns:dc="http://purl.org/dc/elements/1.1/" 
      version="2.0">
<channel>
<title>Dr. Jean-Christophe Loiseau</title>
<link>https://loiseaujc.github.io/blog.html</link>
<atom:link href="https://loiseaujc.github.io/blog.xml" rel="self" type="application/rss+xml"/>
<description>Quarto Academic Website Template adapted by Dr. Gang He</description>
<image>
<url>https://quarto.org/quarto.png</url>
<title>Dr. Jean-Christophe Loiseau</title>
<link>https://loiseaujc.github.io/blog.html</link>
</image>
<generator>quarto-1.7.34</generator>
<lastBuildDate>Sun, 20 Sep 2026 22:00:00 GMT</lastBuildDate>
<item>
  <title>Red or Black?</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel.html</link>
  <description><![CDATA[ 




<center>
<hr width="50%" hr="">
</center>
<p><img src="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel.jpeg" class="img-fluid" style="width:100.0%"></p>
<center>
<hr width="50%" hr="">
</center>
<p><a href="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html">Last time</a>, we set out to restore Gauss-Seidel to its rightful place. And we managed to do it. Or so it seems… Starting from the textbook <em>lexicographic</em> kernel</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-3">        u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb1-4">                                       <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb1-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>we used loop-unrolling techniques and a bit of algebra to rewrite it as</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb2-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parameter</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> c  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span></span>
<span id="cb2-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parameter</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> c2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0625_dp</span></span>
<span id="cb2-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-4">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb2-5">      um   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j)</span>
<span id="cb2-6">      tmp1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-7">      tmp2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-8">      u(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(um <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> tmp1)</span>
<span id="cb2-9">      u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>um <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (c2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>tmp1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> c<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>tmp2)</span>
<span id="cb2-10">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb2-11"></span>
<span id="cb2-12">   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Handles the case where an odd number of grid points is used.</span></span>
<span id="cb2-13">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb2-14">      tmp1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(n, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-15">      u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> tmp1)</span>
<span id="cb2-16">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end if</span></span>
<span id="cb2-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>A slightly more complicated implementation admittedly. But the added complexity paid off: we drastically reduced the number of loop-carried dependencies. In doing so, we enabled the compiler to make use of instruction-level-parallelism and generate a much faster code. Using our classical 2D Poisson test case with 512 points per direction, we obtained the following timings:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Solver</th>
<th>Iterations</th>
<th>Time / iteration</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Jacobi</td>
<td>138 000</td>
<td><strong>115 µs</strong></td>
<td>16 s</td>
</tr>
<tr class="even">
<td>Textbook Gauss-Seidel</td>
<td>74 000</td>
<td>1054 µs</td>
<td>78 s</td>
</tr>
<tr class="odd">
<td>Unrolled Gauss-Seidel</td>
<td>74 000</td>
<td><strong>175 µs</strong></td>
<td><strong>13 s</strong></td>
</tr>
</tbody>
</table>
<p>This new kernel runs 6 times faster than the original one. And, although a single iteration is slightly slower than Jacobi, the better convergence rate of the Gauss-Seidel method allows this kernel to compute the solution to our problem almost 3 seconds faster. This is clearly a win, so why do I keep bothering you with Gauss-Seidel?</p>
<p>The reason is pretty simple: because of the loop-carried dependency, this kernel cannot be easily vectorized nor parallelized. And I like my kernels being vectorized. This can be inferred directly from the assembly code emitted by the compiler (<code>gfortran 15.1</code> with <code>-O3 -march=native -mtune=native</code> here) for the inner-most loop</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource {assembly} number-lines code-with-copy"><code class="sourceCode"><span id="cb3-1">.L5:</span>
<span id="cb3-2">    vmovsd  16(%rax), %xmm1</span>
<span id="cb3-3">    vmovsd  8(%rax), %xmm0</span>
<span id="cb3-4">    addq    $16, %rdx</span>
<span id="cb3-5">    vaddsd  8(%rax,%rsi,8), %xmm1, %xmm1</span>
<span id="cb3-6">    vaddsd  (%rax,%rsi,8), %xmm0, %xmm2</span>
<span id="cb3-7">    vaddsd  8(%rax,%rcx,8), %xmm1, %xmm1</span>
<span id="cb3-8">    vaddsd  (%rax,%rcx,8), %xmm2, %xmm2</span>
<span id="cb3-9">    addq    $16, %rax</span>
<span id="cb3-10">    vfmadd231sd -8(%rdx), %xmm5, %xmm1</span>
<span id="cb3-11">    vfmadd231sd -16(%rdx), %xmm5, %xmm2</span>
<span id="cb3-12">    vmulsd  %xmm4, %xmm1, %xmm1</span>
<span id="cb3-13">    vaddsd  %xmm3, %xmm2, %xmm0</span>
<span id="cb3-14">    vmulsd  %xmm4, %xmm0, %xmm0</span>
<span id="cb3-15">    vfmadd132sd %xmm6, %xmm1, %xmm2</span>
<span id="cb3-16">    vfmadd132sd %xmm6, %xmm2, %xmm3</span>
<span id="cb3-17">    vunpcklpd   %xmm3, %xmm0, %xmm0</span>
<span id="cb3-18">    vmovupd %xmm0, -16(%rax)</span>
<span id="cb3-19">    cmpq    %rax, %rdi</span>
<span id="cb3-20">    jne .L5</span></code></pre></div>
<p>If you look at the mnemonics (that is the last two letters of most instructions), you’ll see that almost all of them are <code>sd</code>. Take the <code>vaddsd</code> on line 5 for instance. What it stands for is <code>Vector Add Scalar Double-Precision</code>. The <code>Vector</code> here has nothing to do with SIMD vectorization. It simply means the instruction belongs to the Advanced Vector Extensions (AVX) set. <code>Add</code> is pretty obvious right, we add numbers. The problem is <code>Scalar Double-Precision</code>. Basically, this means that we process grid points one at a time. And even though reducing the loop-carried dependencies enabled the scheduler to use <a href="https://en.wikipedia.org/wiki/Instruction-level_parallelism">instruction-level parallelism</a> (which is why this kernel runs faster), I’d rather have this algorithm (not the kernel) be vectorized.</p>
<section id="but-why-is-the-lack-of-vectorization-a-problem" class="level1 page-columns page-full">
<h1>But why is the lack of vectorization a problem?</h1>
<div class="page-columns page-full"><p>Here is a simple reason: if a kernel is vectorized, modern CPUs can process several grid points with a single instruction (SIMD). If the computation is also free of dependencies between those points, we can distribute those independent operations across multiple threads. In my case, my CPU has 8 physical cores. From a practical point of view, what it entails is: if I can figure out how to have a vectorized implementation of the Gauss-Seidel algorithm with no loop-carried dependencies, this kernel could run (theoretically) up to 8 times faster at no extra cost by leveraging multithreading. Same code, same hardware, 8x speed-up.  So here is the problem: we did not restore Gauss-Seidel to its rightful place yet precisely because the Jacobi kernel is easily vectorized. And because it also has no loop-carried dependencies, it can also be accelerated quite significantly on the exact same machine.</p><div class="no-row-height column-margin column-container"><span class="margin-aside">Yes I know, memory-bounded so not quite true. We’ll get there eventually.</span></div></div>
<section id="lets-put-this-assertion-to-the-test" class="level2">
<h2 class="anchored" data-anchor-id="lets-put-this-assertion-to-the-test">Let’s put this assertion to the test</h2>
<p>Same old same old: 2D Poisson on the unit square with homogeneous Dirichlet boundary conditions, non-zero forcing, and second-order accurate finite differences on a uniform grid with 512 points per direction. Both kernels are compiled with <code>gfortran -O3 -march=native -mtune=native -ftree-parallelize-loops=n</code> where <code>n</code> is the number of threads we want to use. Hereafter, we’ll let <code>n</code> vary between 1 (serial) and 8 (the maximum number of threads on my laptop).</p>
<div id="b3dd2970" class="cell" data-execution_count="1">
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel_files/figure-html/cell-2-output-1.png" width="663" height="287" class="figure-img"></p>
<figcaption><strong>Figure 1 -</strong> Wall-clock time as a function of thread count for Jacobi and Gauss-Seidel. Jacobi scales close to linearly with the number of threads, while Gauss-Seidel stays essentially flat — at 8 threads, Jacobi is already 4.7<img src="https://latex.codecogs.com/png.latex?%5Ctimes"> faster.</figcaption>
</figure>
</div>
</div>
</div>
<p>This figure depicts the time-to-solution for the two kernels as we vary <code>n</code>. The left-most points are our serial setup, i.e.&nbsp;what we considered so far. As <a href="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html">before</a>, the unrolled Gauss-Seidel kernel is slightly faster than the Jacobi one! Things however go south as soon as we enable multithreading… By the time we reach the full 8 threads, the Jacobi solver is roughly 5 times faster than our brand new Gauss-Seidel one. This is why I said our win from last time was <em>serial only</em>. Because all the lattice updates in Jacobi are independent from one another, adding a single option (<code>-ftree-parallelize-loops=n</code>) lets the compiler parallelize the Jacobi solver and have it outperform Gauss-Seidel by quite a margin despite its worse convergence properties.</p>
</section>
<section id="so-what-can-we-do" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="so-what-can-we-do">So what can we do?</h2>
<div class="page-columns page-full"><p>Well, for this particular implementation of the Gauss-Seidel method, not much actually. We haven’t quite reached the end of the road yet, but taking the final step would require a hefty number of changes in the code as well as a deeper understanding of the underlying hardware.  And sometimes, you do have to bite the bullet and take this extra step because that is the only thing you can do. But here, I’d like to actually take a step back instead, and reflect on where the troubling loop-carried dependencies actually come from.</p><div class="no-row-height column-margin column-container"><span class="margin-aside">What I have in mind here are things like <em>temporal blocking</em>, <em>diamond tiling</em>, and the likes. <a href="https://stackoverflow.com/questions/77846522/how-can-i-generalize-diamond-tiling-to-higher-dimensions">Here</a> is a very comprehensive stackoverflow post if you’re interested.</span></div></div>
<p>Let’s look back to our original Gauss-Seidel kernel (the unrolled one is just a variation around it)</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb4-3">        u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb4-4">                                         <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb4-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb4-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>The loop-carried dependencies identified in the <a href="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html">previous post</a> come from the fact that <code>u(i, j)</code> cannot be updated until <code>u(i-1, j)</code> has already been. Had we implemented the kernel as</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-3">        u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb5-4">                                         <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb5-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb5-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>instead, i.e.&nbsp;scanning through the rows first rather than the columns, the loop-carried dependency would still be there: <code>u(i, j)</code> couldn’t be updated until <code>u(i, j-1)</code> had been. These two kernels correspond to slightly different <em>ordering</em> of the unknowns. The traversal path associated to each is illustrated below.</p>
<div id="0837f7da" class="cell" data-execution_count="2">
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel_files/figure-html/cell-3-output-1.png" width="722" height="396" class="figure-img"></p>
<figcaption><strong>Figure 2 -</strong> The two possible loop orderings of the lexicographic Gauss-Seidel kernel. (a) Column-wise ordering sweeps down each column (fixed <img src="https://latex.codecogs.com/png.latex?j">, incrementing <img src="https://latex.codecogs.com/png.latex?i">) before moving to the next. (b) Row-wise ordering sweeps across each row (fixed <img src="https://latex.codecogs.com/png.latex?i">, incrementing <img src="https://latex.codecogs.com/png.latex?j">) before moving to the next. Either way, the path is a single unbroken sequence through every grid point, which is exactly where the loop-carried dependency comes from.</figcaption>
</figure>
</div>
</div>
</div>
<p>Quite clearly, both of these orderings process the grid points sequentially. So the question is: <strong>is this sequential process (and the resulting loop-carried dependencies) a fundamental property of the Gauss-Seidel method, or is it simply a consequence of the way we ordered the grid points?</strong></p>
</section>
</section>
<section id="leveraging-the-mathematical-structure-of-the-problem" class="level1 page-columns page-full">
<h1>Leveraging the mathematical structure of the problem</h1>
<p>It turns out that the answer to this question is no, the sequential processing of grid points <strong>is not</strong> a fundamental property of the Gauss-Seidel method (at least when applied to this 2D Poisson problem). And yes, for our particular problem, we can find a better ordering that will actually get entirely rid of the loop-carried dependencies. But to find this ordering, we’ll have to look back at the problem from a completely different angle. Instead of trying to come up with variations of the lexicographic kernel hoping it’ll help the compiler do a better job, we’ll have to figure out on our own if there is an underlying mathematical structure to our problem that we can leverage to design a fundamentally better kernel.</p>
<section id="red-and-black-coloring" class="level2">
<h2 class="anchored" data-anchor-id="red-and-black-coloring">Red and black coloring</h2>
<p>Alright, so let’s start from the beginning again. Both the Jacobi and Gauss-Seidel update rules for the 2D Poisson equation can be written as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_%7Bi,%20j%7D%5E%7B(t+1)%7D%20=%20%5Cdfrac%7B1%7D%7B4%7D%20%5Cleft(%20b_%7Bi,%20j%7D%20%5Ccdot%20%5CDelta%20x%5E2%20+%20u_%7Bi-1,%20j%7D%5E%7B(?)%7D%20+%20u_%7Bi+1,%20j%7D%5E%7B(?)%7D%20+%20u_%7Bi,%20j-1%7D%5E%7B(?)%7D%20+%20u_%7Bi,%20j+1%7D%5E%7B(?)%7D%20%5Cright).%0A"></p>
<p>For Jacobi, the superscript <img src="https://latex.codecogs.com/png.latex?(?)"> is simply <img src="https://latex.codecogs.com/png.latex?(t)">, i.e.&nbsp;we update <img src="https://latex.codecogs.com/png.latex?u_%7Bi,%20j%7D%5E%7B(t+1)%7D"> from old values stored in a completely different buffer. For Gauss-Seidel on the other hand, the superscript <img src="https://latex.codecogs.com/png.latex?(?)"> can be either <img src="https://latex.codecogs.com/png.latex?(t)"> or <img src="https://latex.codecogs.com/png.latex?(t+1)"> since we directly use the newly updated values whenever possible, leading to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_%7Bi,%20j%7D%5E%7B(t+1)%7D%20=%20%5Cdfrac%7B1%7D%7B4%7D%20%5Cleft(%20b_%7Bi,%20j%7D%20%5Ccdot%20%5CDelta%20x%5E2%20+%20u_%7Bi-1,%20j%7D%5E%7B(t+1)%7D%20+%20u_%7Bi+1,%20j%7D%5E%7B(t)%7D%20+%20u_%7Bi,%20j-1%7D%5E%7B(t+1)%7D%20+%20u_%7Bi,%20j+1%7D%5E%7B(t)%7D%20%5Cright).%0A"></p>
<p>This is a very simple update rule, so how the hell can we find a more hardware-friendly ordering? To get some intuition, let’s look at the figure below.</p>
<div id="71dbcb59" class="cell" data-execution_count="3">
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel_files/figure-html/cell-4-output-1.png" width="653" height="459" class="figure-img"></p>
<figcaption><strong>Figure 3 -</strong> Nodes are partitioned into two disjoint sets: red where <img src="https://latex.codecogs.com/png.latex?(i+j)"> is even, and black where <img src="https://latex.codecogs.com/png.latex?(i+j)"> is odd. Blue lines highlight the standard 5-point discrete Laplacian stencil centered at node <img src="https://latex.codecogs.com/png.latex?(3,3)">. Because every red node relies exclusively on its four orthogonal black neighbors (and vice versa), all red nodes can be updated simultaneously without data dependencies, followed by an update of all black nodes.</figcaption>
</figure>
</div>
</div>
</div>
<p>The different grid points have been color-coded in a very specific way. Points for which <img src="https://latex.codecogs.com/png.latex?(i%20+%20j)"> is even are red, while those for which <img src="https://latex.codecogs.com/png.latex?(i%20+%20j)"> is odd are black. We’ve also highlighted the standard 5-point discrete Laplacian stencil we’re using for the node <img src="https://latex.codecogs.com/png.latex?(3,%203)">. And something should jump out directly: this red point update depends only on black points, as does every other red point. And conversely, any black point update depends solely on red points. It looks very promising, isn’t it! It seems like we could update all of the red points simultaneously based on the old values of the black points, and then update all of the black points (simultaneously again) based on the just-updated values of the red ones. Suspiciously similar to Gauss-Seidel… And it is actually the same plain old Gauss-Seidel we’ve been looking at for some time, simply using a different ordering breaking the sequential nature of the lexicographic kernel that would have led otherwise to loop-carried dependencies.</p>
</section>
<section id="a-detour-through-the-mathematics" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="a-detour-through-the-mathematics">A detour through the mathematics</h2>
<p>You should have by now at least a good intuition for why this red/black ordering may be beneficial. Let’s now try to formalize that mathematically starting from our original linear system</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AAx%20=%20b.%0A"></p>
<p>When <img src="https://latex.codecogs.com/png.latex?A"> is symmetric positive-definite, Gauss-Seidel uses the additive decomposition <img src="https://latex.codecogs.com/png.latex?A%20=%20D%20+%20L%20+%20L%5E%5Ctop"> (with <img src="https://latex.codecogs.com/png.latex?D"> diagonal, and <img src="https://latex.codecogs.com/png.latex?L"> strictly lower triangular) to arrive at the iterative update rule</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%20%20%20%20%5Cleft(%20D%20+%20L%20%5Cright)%20x_%7Bt+1%7D%20=%20b%20-%20L%5E%5Ctop%20x_t.%0A"></p>
<p>The loop-carried dependency we’ve identified in the lexicographic kernel comes precisely from the fact that the lower triangular matrix <img src="https://latex.codecogs.com/png.latex?D%20+%20L"> is inverted using forward substitution. But let’s now re-order our vector of unknowns <img src="https://latex.codecogs.com/png.latex?x"> by introducing a permutation matrix <img src="https://latex.codecogs.com/png.latex?P">. This leads to the linear system <img src="https://latex.codecogs.com/png.latex?PAP%5E%5Ctop%20z%20=%20Pb">, where <img src="https://latex.codecogs.com/png.latex?z%20=%20Px"> is the re-ordered vector of unknowns. Partitioning this vector into two sets leads to the block system</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7D%0AD_1%20&amp;%20B%20%5C%5C%0AC%20&amp;%20D_2%0A%5Cend%7Bbmatrix%7D%0A%5Cbegin%7Bbmatrix%7D%0Az_1%20%5C%5C%20z_2%0A%5Cend%7Bbmatrix%7D%0A=%0A%5Cbegin%7Bbmatrix%7D%0Ab_1%20%5C%5C%20b_2%0A%5Cend%7Bbmatrix%7D,%0A"></p>
<p>and we could try to iteratively solve this system using the following update rule</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0AD_1%20z_1%5E%7B(t+1)%7D%20&amp;%20=%20b_1%20-%20B%20z_2%5E%7B(t)%7D%20%5C%5C%0AD_2%20z_2%5E%7B(t+1)%7D%20&amp;%20=%20b_2%20-%20C%20z_1%5E%7B(t+1)%7D.%0A%5Cend%7Baligned%7D%0A"></p>
<p>Depending on the matrix <img src="https://latex.codecogs.com/png.latex?A"> and how we re-ordered the unknowns, this may or may not converge. But so far, we’ve only considered an arbitrary permutation matrix <img src="https://latex.codecogs.com/png.latex?P">. The name of the game thus is finding a permutation matrix <img src="https://latex.codecogs.com/png.latex?P"> such that</p>
<ol type="1">
<li><img src="https://latex.codecogs.com/png.latex?D_1"> and <img src="https://latex.codecogs.com/png.latex?D_2"> are easy to invert,</li>
</ol>
<p><strong>and</strong></p>
<ol start="2" type="1">
<li>this update rule actually converges.</li>
</ol>
<p>This is exactly what the red/black ordering is doing for our Poisson problem. There are two things to note. First, because our original system is symmetric, we actually have <img src="https://latex.codecogs.com/png.latex?B%20=%20C%5E%5Ctop">. Second, since the red (resp. black) depend exclusively on the black (resp. red) ones, the <img src="https://latex.codecogs.com/png.latex?D_1"> and <img src="https://latex.codecogs.com/png.latex?D_2"> are simply multiples of the identity matrix and thus trivial to invert. So our first condition checks out. Let’s move on with the second one: <em>does it actually converge?</em></p>
<p>Things get a bit more complicated here, but the answer is <em>for our specific problem, yes it does</em>. I’ll omit the proof and just give the main ideas. Because <img src="https://latex.codecogs.com/png.latex?A"> is symmetric positive definite, the matrix</p>
<p><img src="https://latex.codecogs.com/png.latex?%0AP%20A%20P%5E%5Ctop%0A=%0A%5Cbegin%7Bbmatrix%7D%0AD_1%20&amp;%20B%20%5C%5C%0AB%5E%5Ctop%20&amp;%20D_2%0A%5Cend%7Bbmatrix%7D,%0A"></p>
<div class="page-columns page-full"><p>also is (where <img src="https://latex.codecogs.com/png.latex?P"> is the permutation matrix associated to the red/black ordering). The red/black iteration is therefore simply block Gauss-Seidel applied to this SPD system, with <img src="https://latex.codecogs.com/png.latex?D_1"> and <img src="https://latex.codecogs.com/png.latex?D_2"> as the two diagonal blocks. Standard convergence results for block Gauss-Seidel then guarantee convergence. So condition 2 also checks out.  The important point however is that we have changed the ordering, not the underlying iteration: the converged solution is still the solution of the original linear system (up to the permutation of the unknowns).</p><div class="no-row-height column-margin column-container"><span class="margin-aside">Condition 2 holding true is not specific to red/black ordering. Any permutation would work because it has everything to do with the original matrix being symmetric positive definite.</span></div></div>
</section>
</section>
<section id="back-to-business" class="level1 page-columns page-full">
<h1>Back to business!</h1>
<p>Alright, we’re all set. Back to <code>Fortran</code>!</p>
<section id="the-redblack-gauss-seidel-kernel" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="the-redblack-gauss-seidel-kernel">The red/black Gauss-Seidel kernel</h2>
<p>After a bit of fiddling around, it shouldn’t be too hard to realize that the red/black Gauss-Seidel kernel can be written as</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> k <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!$omp parallel do default(none) private(j, i, istart) shared(n, u, b, dx2, k) schedule(static)</span></span>
<span id="cb6-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-4">        istart <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(k<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span>j, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb6-5">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> istart, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-6">            u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb6-7">                                           <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb6-9">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb6-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">!$omp end parallel do</span></span>
<span id="cb6-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<div class="page-columns page-full"><p>Forget about the <code>OpenMP</code> stuff for now.  If you look at lines 6 and 7, you’ll see that this is the exact same update rule as before. The only thing that changed is how we loop over <img src="https://latex.codecogs.com/png.latex?i"> and <img src="https://latex.codecogs.com/png.latex?j">. Note that, when <code>k = 0</code>, the loop updates the red points, and when <code>k = 1</code> it updates the black ones. So is this really good enough to recover the SIMD vectorization and multithreading capabilities of Jacobi?</p><div class="no-row-height column-margin column-container"><span class="margin-aside">In an ideal world, we could have written it using <code>do concurrent</code>. But for some reason, <code>gfortran</code> does not like it when going multithreaded.</span></div></div>
<p>As in the <a href="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html">previous post</a>, we’ll run this kernel through <a href="https://github.com/RRZE-HPC/OSACA"><code>OSACA</code></a>. We’ll thus compile it with the same options as before: <code>gfortran -O3 -march=native -mtune=native</code> so that vectorization is enabled. Here are the results for the inner-most loop updating only the red points.</p>
<pre><code>                                      Port pressure in cycles                                       
     |  0   - 0DV  |  1   |  2   -  2D  |  3   -  3D  |  4   |   5   |  6   |  7   ||  CP  | LCD  |
---------------------------------------------------------------------------------------------------
 168 |             |      |             |             |      |       |      |      ||      |      |   .L17:
 169 |             |      | 0.50   0.50 | 0.50   0.50 |      |       |      |      ||      |      |   vmovupd (%r9,%rax), %ymm1
 170 |             |      | 0.50   0.50 | 0.50   0.50 |      |       |      |      ||      |      |   vmovupd (%r8,%rax), %ymm10
 171 |             |      | 0.50   0.50 | 0.50   0.50 |      |       |      |      ||      |      |   vmovupd (%rdi,%rax), %ymm12
 172 |             |      | 0.50   0.50 | 0.50   0.50 |      |       |      |      ||      |      |   vmovupd (%rsi,%rax), %ymm14
 173 |             |      | 0.50   0.50 | 0.50   0.50 |      | 1.000 |      |      ||  3.0 |      |   vpermt2pd 32(%r9,%rax), %ymm2, %ymm1
 174 |             |      | 0.50   0.50 | 0.50   0.50 |      | 1.000 |      |      ||      |      |   vpermt2pd 32(%r8,%rax), %ymm2, %ymm10
 175 |             |      | 0.50   0.50 | 0.50   0.50 |      |       |      |      ||      |      |   vmovupd (%r10,%rax), %ymm0
 176 | 0.50        | 0.50 |             |             |      |       |      |      ||  4.0 |      |   vaddpd %ymm10, %ymm1, %ymm1
 177 |             |      | 0.50   0.50 | 0.50   0.50 |      | 1.000 |      |      ||      |      |   vpermt2pd 32(%rdi,%rax), %ymm2, %ymm12
 178 |             |      | 0.50   0.50 | 0.50   0.50 |      | 1.000 |      |      ||      |      |   vpermt2pd 32(%rsi,%rax), %ymm2, %ymm14
 179 |             |      | 0.50   0.50 | 0.50   0.50 |      | 1.000 |      |      ||      |      |   vpermt2pd 32(%r10,%rax), %ymm2, %ymm0
 180 | 0.50        | 0.50 |             |             |      |       |      |      ||  4.0 |      |   vaddpd %ymm12, %ymm1, %ymm1
 181 | 0.50        | 0.50 |             |             |      |       |      |      ||  4.0 |      |   vaddpd %ymm14, %ymm1, %ymm1
 182 | 0.50        | 0.50 |             |             |      |       |      |      ||  4.0 |      |   vfmadd132pd %ymm7, %ymm1, %ymm0
 183 | 0.50        | 0.50 |             |             |      |       |      |      ||  4.0 |      |   vmulpd %ymm4, %ymm0, %ymm0
 184 |             |      |             |             |      | 1.000 |      |      ||  3.0 |      |   vextractf64x2 $1, %ymm0, %xmm1
 185 |             |      | 0.50        | 0.50        | 1.00 |       |      |      ||      |      |   vmovlpd %xmm0, (%rdx,%rax)
 186 |             |      | 0.50        | 0.50        | 1.00 |       |      |      ||      |      |   vmovhpd %xmm0, 16(%rdx,%rax)
 187 |             |      |             |             |      | 1.000 |      |      ||      |      |   valignq $3, %ymm0, %ymm0, %ymm0
 188 |             |      | 0.50        | 0.50        | 1.00 |       |      |      ||  0.0 |      |   vmovsd %xmm1, 32(%rdx,%rax)
 189 |             |      | 0.50        | 0.50        | 1.00 |       |      |      ||      |      |   vmovsd %xmm0, 48(%rdx,%rax)
 190 | 0.00        | 0.00 |             |             |      | -0.01 | 1.00 |      ||      |  1.0 |   addq $64, %rax
 191 | 0.00        | 0.00 |             |             |      | -0.01 | 1.00 |      ||      |      |   cmpq %rax, %r12
 192 |             |      |             |             |      |       |      |      ||      |      | * jne .L17

       2.50          2.50   7.00   5.00   7.00   5.00   4.00   6.980   2.00           26.0    1.0  </code></pre>
<div class="page-columns page-full"><p>If you remember what we’ve done <a href="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html">last time</a>, the loop-carried dependencies have been reduced from 4 to 1, and this 1 is only related to incrementing the loop counter so utterly irrelevant. Moreover, if you look at the assembly code, you’ll see that the mnemonics are now <code>pd</code>. So there you have it! Not only does the red/black ordering for Gauss-Seidel lead to a complete disappearance of the loop-carried dependencies, but it also enables SIMD vectorization! That is a win on both accounts.  So let’s pit it against Jacobi now.</p><div class="no-row-height column-margin column-container"><span class="margin-aside">Surprisingly, the story for the black loop is slightly messier. It is not just the symmetric of this one, but the absence of loop-carried dependencies is still true so we won’t deep dive into this.</span></div></div>
</section>
<section id="one-step-at-a-time" class="level2 page-columns page-full">
<h2 class="anchored" data-anchor-id="one-step-at-a-time">One step at a time…</h2>
<p>There’s no point in showing the parallel performance if the serial one is not good enough. So before the grand reveal, let’s do just that: make sure we’ve at least not degraded the performance of the serial code. Same experiments as before: sanity check (no optimization whatsoever), critical one (<code>-O3</code> but no vectorization), and cherry on top (vectorization turned on).</p>
<section id="experiment-n1-baseline-no-optimization" class="level3">
<h3 class="anchored" data-anchor-id="experiment-n1-baseline-no-optimization">Experiment n°1 – baseline, no optimization</h3>
<p>First things first: sanity check. Compiled with <code>-O0</code>, so no scheduling cleverness of any kind gets to play.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;">Solver</th>
<th style="text-align: center;">Iterations</th>
<th style="text-align: center;">Time/iteration</th>
<th style="text-align: center;">Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Jacobi</td>
<td style="text-align: center;">138 000</td>
<td style="text-align: center;">2 ms</td>
<td style="text-align: center;">273 s</td>
</tr>
<tr class="even">
<td style="text-align: center;">Unrolled GS</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">2.07 ms</td>
<td style="text-align: center;">153 s</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Red/black GS</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">2 ms</td>
<td style="text-align: center;">148 s</td>
</tr>
</tbody>
</table>
<p>So unoptimized serial performance of the red/black Gauss-Seidel kernel is essentially the same as that of the unrolled kernel. And the solution, even though not shown, is the same up to the desired tolerance. And as before, both Gauss-Seidel kernels are effectively twice as fast to compute the solution compared to the Jacobi kernel. So everything consistent so far.</p>
</section>
<section id="experiment-n2-the-critical-one" class="level3 page-columns page-full">
<h3 class="anchored" data-anchor-id="experiment-n2-the-critical-one">Experiment n°2 – the critical one</h3>
<div class="page-columns page-full"><p>Now, compile with real optimization, but deliberately keep vectorization and multithreading switched off (<code>-O3 -march=native -mtune=native -fno-tree-vectorize</code> and no OpenMP shady business) so that any gains we see can’t be attributed to SIMD at all. </p><div class="no-row-height column-margin column-container"><span class="margin-aside">The actual command line used is <code>fpm run --profile release</code> with the flags specified in the text. So effectively, there are a bunch of other options turned on by <code>fpm</code>, but they’re the same for all kernels.</span></div></div>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;">Solver</th>
<th style="text-align: center;">Iterations</th>
<th style="text-align: center;">Time/iteration</th>
<th style="text-align: center;">Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Jacobi</td>
<td style="text-align: center;">138 000</td>
<td style="text-align: center;">167 µs</td>
<td style="text-align: center;">23 s</td>
</tr>
<tr class="even">
<td style="text-align: center;">Unrolled GS</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">175 µs</td>
<td style="text-align: center;">13 s</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Red/black GS</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">200 µs</td>
<td style="text-align: center;">15 s</td>
</tr>
</tbody>
</table>
<p>With no SIMD enabled, the red/black kernel is slightly slower than the unrolled lexicographic one, but still faster than Jacobi. One possible explanation is that the red/black kernel has to loop over the entire array twice: once to update the red points, and once more to update the black ones. So possibly a bit more memory traffic, but nothing too detrimental.</p>
</section>
<section id="experiment-n3-turning-simd-vectorization-back-on" class="level3">
<h3 class="anchored" data-anchor-id="experiment-n3-turning-simd-vectorization-back-on">Experiment n°3 – turning SIMD vectorization back on</h3>
<p>Finally, the same three kernels, same flags, but with vectorization allowed (but no multithreading still).</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;">Solver</th>
<th style="text-align: center;">Iterations</th>
<th style="text-align: center;">Time/iteration</th>
<th style="text-align: center;">Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Jacobi</td>
<td style="text-align: center;">138 000</td>
<td style="text-align: center;">115 µs</td>
<td style="text-align: center;">16 s</td>
</tr>
<tr class="even">
<td style="text-align: center;">Unrolled GS</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">175 µs</td>
<td style="text-align: center;">13 s</td>
</tr>
<tr class="odd">
<td style="text-align: center;">Red/black GS</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">175 µs</td>
<td style="text-align: center;">13 s</td>
</tr>
</tbody>
</table>
<p>So enabling SIMD shaves off the extra 25 µs for the red/black kernel execution, making its serial performance on par with the unrolled kernel we discussed last time. So that is good: essentially no regression in the serial performance.</p>
</section>
</section>
<section id="lets-go-parallel" class="level2">
<h2 class="anchored" data-anchor-id="lets-go-parallel">Let’s go parallel</h2>
<p>So far, we’ve only looked at serial performance. But the whole point of coming up with the red/black ordering was not to have yet another variation on Gauss-Seidel, but actually have a kernel that can run in parallel. So it’s time to show you what you came for! Same setup as experiment n°3, but now enable multithreading and vary the number of threads from 1 to 8. And TADA!</p>
<div id="953331df" class="cell" data-execution_count="4">
<div class="cell-output cell-output-display">
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel_files/figure-html/cell-5-output-1.png" width="663" height="287" class="figure-img"></p>
<figcaption><strong>Figure 4 -</strong> Wall-clock time comparison for the parallelized solvers. With loop dependencies removed, Red/Black Gauss-Seidel maintains an edge over Jacobi across all thread counts.</figcaption>
</figure>
</div>
</div>
</div>
<p>Everything works as expected. Leveraging the red/black ordering not only leads to the elimination of loop-carried dependencies while enabling SIMD vectorization, but also multithreading. So, even though the advantage of red/black Gauss-Seidel over Jacobi reduces as we increase the number of threads, it is still a bit faster. Now we can actually claim victory.</p>
</section>
</section>
<section id="is-this-finally-the-end-of-the-road" class="level1 page-columns page-full">
<h1>Is this finally the end of the road?</h1>
<p>It took us quite a bit of time to get where we wanted, but here we are: we’ve restored Gauss-Seidel to its rightful place! So it is only natural to ask <em>are we really done now?</em> Well… as far as Jacobi and Gauss-Seidel go, pretty much. But there is still a lot we can do.</p>
<p>First, let’s replot the data from the last figure to see the actual speed-up as we increase the number of threads.</p>
<div id="d827f52b" class="cell page-columns page-full" data-execution_count="5">
<div class="cell-output cell-output-display page-columns page-full">
<div class="quarto-figure quarto-figure-center page-columns page-full">
<figure class="figure page-columns page-full">
<p><img src="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel_files/figure-html/cell-6-output-1.png" class="figure-img" width="366" height="361"></p>
<figcaption class="margin-caption"><strong>Figure 5 -</strong> Parallel speed-up for Jacobi and Red/Black Gauss-Seidel.</figcaption>
</figure>
</div>
</div>
</div>
<p>Both track close to the ideal scaling: doubling the number of threads gets you the solution almost twice as fast. If you’d stop there, you would say that this is very good. But it actually is a misleading picture. Even though a grid of 512 <img src="https://latex.codecogs.com/png.latex?%5Ctimes"> 512 points results in roughly a quarter million unknowns, a really big problem by everyday standards, it simply is too small to really stress-test the hardware (even on my laptop). We do need to go to a bigger problem to make some meaningful conclusions. So we will eventually dabble into the field of performance engineering and explore the so-called <a href="https://en.wikipedia.org/wiki/Roofline_model">roofline model</a>, a deceptively simple but highly informative model.</p>
<p>The other question I’d like to answer is <em>can we modify Gauss-Seidel in just the right way as to massively reduce the number of iterations?</em> This will bring us to the territories of <a href="https://en.wikipedia.org/wiki/Successive_over-relaxation">successive over-relaxation</a>, <a href="https://en.wikipedia.org/wiki/Chebyshev_iteration">Chebyshev iterations</a> and <a href="https://en.wikipedia.org/wiki/Multigrid_method">multigrid methods</a>. But these are stories for another time.</p>
<!--Include social share buttons-->
<!-- 
AddToAny check more: https://www.addtoany.com/buttons/for/website 
Using includes will make edits easier, will only need to add or remove button here if needed.
https://quarto.org/docs/authoring/includes.html
-->
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
<p><a class="a2a_dd" href="https://www.addtoany.com/share"></a> <a class="a2a_button_linkedin"></a> <a class="a2a_button_bluesky"></a> <a class="a2a_button_facebook"></a> <a class="a2a_button_copy_link"></a> <a class="a2a_button_email"></a></p>
</div>
<script async="" src="https://static.addtoany.com/menu/page.js"></script>
<hr>
<p><strong>If you want to read more of my stuff</strong></p>
<ul>
<li><a href="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html">Make Gauss-Seidel great again!</a></li>
<li><a href="https://loiseaujc.github.io/posts/blog-title/gauss_seidel_experiments.html">Gauss-Seidel is faster than Jacobi. Or is it?</a></li>
<li><a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html">Jacobi method: From a naïve implementation to a modern Fortran multithreaded one</a></li>
</ul>


</section>

 ]]></description>
  <category>blog</category>
  <guid>https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel.html</guid>
  <pubDate>Sun, 20 Sep 2026 22:00:00 GMT</pubDate>
  <media:content url="https://loiseaujc.github.io/posts/blog-title/redblack_gauss_seidel.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Make Gauss-Seidel great again!</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html</link>
  <description><![CDATA[ 




<center>
<hr width="50%" hr="">
</center>
<p><img src="https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.jpeg" class="img-fluid" style="width:100.0%"></p>
<center>
<hr width="50%" hr="">
</center>
<p>Last time, we explored the <a href="https://loiseaujc.github.io/posts/blog-title/gauss_seidel_experiments.html">Gauss-Seidel</a> method. We arrived at the following conclusion: the math says Gauss-Seidel takes half as many iterations to converge, and this is backed up by our numerical test case. Yet, despite requiring only half the number of iterations, the Gauss-Seidel solver takes 4 to 5 times as long as the Jacobi one. A seemingly puzzling fact if you think about it: how can a method which is better on paper perform worse when implemented numerically? As we’ve seen, the explanation lies in that, unlike Jacobi, the Gauss-Seidel update rule has <a href="https://en.wikipedia.org/wiki/Loop_dependence_analysis">loop-carried dependencies</a> which prevent the compiler from vectorizing the code. The question we’ll try to answer today thus is: <strong>is this the end of the road or can we somehow recover Gauss-Seidel’s convergence advantage without sacrificing the hardware efficiency of Jacobi?</strong></p>
<p>And it turns out that, yes you can! We have two different routes though: one fairly general where you help the compiler as much as you can (but you need to understand a bit how compilers and CPUs work), and another, quite specific to our particular 2D Poisson test case, where you let the math guide you. None of them will be a smooth ride, but we’ll learn a lot along the way. Eventually, we’ll explore both, but for now we’ll ride along the first one and discover what <em>loop unrolling</em> is.</p>
<section id="wait-what-was-the-problem-again" class="level1 page-columns page-full">
<h1>Wait, what was the problem again?</h1>
<p>Let’s rewind for a second. Last time, we pitted two update rules against each other on the same 2D Poisson problem: the humble Jacobi iteration, and its slightly more sophisticated cousin, Gauss-Seidel.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb1-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-2">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb1-3">      u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb1-4">                                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb1-5">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb1-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>Jacobi always reads from the <em>old</em> iterate <code>v</code> and writes into a fresh array <code>u</code>. Gauss-Seidel, in contrast, updates in place:</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-2">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-3">      u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb2-4">                                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-5">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb2-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>That is a one-line difference that buys you a faster-converging method, at exactly half the number of sweeps to reach a given tolerance. On paper, Gauss-Seidel should win outright. And yet, when we actually ran the numbers, Gauss-Seidel took <strong>4 to 5 times longer</strong> than Jacobi, wall-clock, despite needing only half as many iterations. Something in that one-line difference is costing us dearly, and it isn’t showing up anywhere in the convergence theory.</p>
<p><strong>Loop-carried dependencies, made concrete</strong></p>
<p>The culprit, as we found, is that <code>u(i-1,j)</code> in the Gauss-Seidel update has <em>already been overwritten</em> by the time you read it. It’s not the old value from the previous sweep, it’s this sweep’s freshly computed neighbor. That’s precisely what makes Gauss-Seidel converge faster: you’re propagating information within a single sweep instead of waiting a full iteration for it. But it also means iteration <code>i</code> cannot start until iteration <code>i-1</code> has fully finished writing <code>u(i-1,j)</code>. Jacobi has no such constraint: every read in a Jacobi sweep comes from <code>v</code>, which nothing in that sweep ever touches.</p>
<p>This is the textbook definition of a <a href="https://en.wikipedia.org/wiki/Loop_dependence_analysis">loop-carried dependency</a>, and it’s exactly the kind of thing a compiler needs to reason about before deciding whether it can vectorize a loop, reorder instructions, or overlap iterations on an out-of-order core. Jacobi: no such dependency, compiler’s hands are free. Gauss-Seidel: hard dependency, compiler’s hands are tied.</p>
<p><strong>Putting a number on “tied”</strong></p>
<p>That’s a nice story, but it’s still just a story until you can measure it. So this time, rather than taking the compiler’s word for it, we reached for <a href="https://github.com/RRZE-HPC/OSACA">OSACA</a> — the Open Source Architecture Code Analyzer — and pointed it directly at the compiled assembly of both kernels.</p>
<p>OSACA reads a marked assembly loop and, assuming an idealized out-of-order core, reports two numbers per loop body: the <strong>critical path (CP)</strong>, the length of the longest dependency chain running through one iteration in isolation, and the <strong>loop-carried dependency (LCD)</strong>, the part of that chain that <em>must</em> cross from one iteration to the next. LCD is the number that matters here. It’s a hard floor on cycles-per-iteration, no amount of clever scheduling can get under it.</p>
<p>Here’s what came back for our <code>-O3 -march=native</code> kernels, compiled with plain <code>do</code> loops (no <code>do concurrent</code>, nothing fancy):</p>
<table class="caption-top table">
<caption>Summary of the <code>osaca</code> analysis of the Jacobi and standard Gauss-Seidel kernels. Both kernels have been compiled using <code>gfortran 15.3</code> with options <code>-O3 -mtune=native -march=native</code>. The precise numbers reported may depend on the exact CPU and compiler you’re using.</caption>
<colgroup>
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
<col style="width: 20%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>CP (cycles)</th>
<th>LCD (cycles)</th>
<th>Port-pressure floor (cycles)</th>
<th>Governing bound</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Jacobi (vectorized, 4 elements/it.)</td>
<td>24</td>
<td>1</td>
<td>3</td>
<td>throughput</td>
</tr>
<tr class="even">
<td>Gauss-Seidel (scalar, 1 element/it.)</td>
<td>24</td>
<td>12</td>
<td>2.5</td>
<td><strong>latency</strong></td>
</tr>
</tbody>
</table>
<p>Two things jump out immediately. First, the critical path is <em>identical</em> for both kernels — 24 cycles. That makes sense: it’s essentially the same chain of floating-point operations in both cases:</p>
<ul>
<li>Three adds : <code>tmp1 = v(i+1, j) + v(i-1, j) + v(i, j+1) + v(i, j-1)</code></li>
<li>One multiply : <code>tmp2 = b(i, j) * dx2</code></li>
<li>One FMA : <code>tmp3 = 0.25_dp * tmp1 + 0.25_dp*tmp2</code></li>
</ul>
<p>along with a a load and a store. The arithmetic hasn’t changed; only what’s allowed to overlap has.</p>
<p>Second, and this is the whole ballgame: Jacobi’s loop-carried dependency is a throwaway 1 cycle (just the loop counter incrementing, nothing to do with the actual computation). Gauss-Seidel’s is <strong>12 cycles</strong>, and OSACA even tells you exactly where it comes from:</p>
<pre><code>46 | 12.0 | vaddsd  %xmm1, %xmm0, %xmm0          | [46, 47, 48]</code></pre>
<p>Trace it back and it’s the west-neighbor term: <code>%xmm1</code> holds this sweep’s freshly written <code>u(i,j)</code>, and it gets read straight back in as <code>u(i-1,j)</code> on the very next loop trip. Three chained instructions — the west-neighbor add, the north-neighbor add, and the final multiply — each with 4-cycle latency, sum to exactly 12. That’s not a compiler being lazy; it’s the compiler correctly reporting that there is <em>no way</em> to start computing <code>u(i,j)</code> before <code>u(i-1,j)</code> exists, because the algorithm itself demands it.</p>
<p>The other giveaway, readable straight off the assembly without even needing OSACA, is the instruction mnemonics themselves. Jacobi’s inner loop is built entirely out of packed double-precision instructions — <code>vaddpd</code>, <code>vmulpd</code>, <code>vfmadd213pd</code> — each one crunching four grid points at a time in a single 256-bit AVX register. Gauss-Seidel’s inner loop uses the scalar forms of the exact same instructions — <code>vaddsd</code>, <code>vmulsd</code>, <code>vfmadd213sd</code> — one grid point at a time. The <code>pd</code>/<code>sd</code> suffix alone tells you, at a glance, which loop the compiler was able to vectorize and which one it wasn’t.</p>
<p><strong>Does the number add up?</strong></p>
<p>For each kernel, the CPU’s actual sustained cost per iteration is bounded below by <em>both</em> constraints at once, the throughput floor and the LCD, so the one that actually governs is whichever of the two is larger. For Gauss-Seidel, that’s the LCD (12 cycles, versus a 2.5-cycle throughput floor). For Jacobi, it’s the throughput floor (3 cycles, versus a negligible 1-cycle LCD). In other words, Gauss-Seidel is <em>latency-bound</em> and Jacobi is <em>throughput-bound</em>. Two different bottlenecks entirely, which is precisely why comparing them isn’t just a matter of reading off the same column for both.</p>
<p>Normalizing each kernel’s governing bound per grid point (dividing Jacobi’s by 4, since it processes four elements per vector iteration) gives a <em>predicted</em> per-sweep slowdown of roughly</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cdfrac%7B12%7D%7B3%20/%204%7D%20=%2016%5Ctimes"></p>
<p>That’s higher than the 4–5× we measured wall-clock. But remember, wall-clock measures <em>total time to solution</em>, and Gauss-Seidel needs only half as many sweeps as Jacobi to converge. Once you back that out, the actual per-sweep slowdown implied by the measurements is</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cleft(%5Cfrac%7BT_%7BGS%7D%7D%7BT_J%7D%5Cright)%20%5CBig/%20%5Cleft(%5Cfrac%7BN_%7BGS%7D%7D%7BN_J%7D%5Cright)%20%5Capprox%20(4%5Ctext%7B%E2%80%93%7D5)%20%5Ctimes%202%20%5Capprox%208%5Ctext%7B%E2%80%93%7D10%5Ctimes"></p>
<div class="page-columns page-full"><p>which lands within a factor of two of OSACA’s prediction, a healthy level of agreement for a <em>static</em> model that has zero visibility into the memory system.  The mechanism OSACA identifies — Gauss-Seidel latency-bound at a hard 12-cycle wall, Jacobi comfortably throughput-bound — is exactly right, even if the precise number needs a pinch of salt.</p><div class="no-row-height column-margin column-container"><span class="margin-aside">Jacobi’s real bottleneck at any decent problem size is almost certainly DRAM bandwidth rather than the 3-cycle compute floor OSACA reports, which would inflate its true per-sweep cost and close the remaining gap.</span></div></div>
<p>So, the diagnosis is confirmed in triplicate: theory, static analysis, and measurement all agree. Gauss-Seidel’s faster convergence is bought entirely at the price of a serialized, 12-cycle-per-element dependency chain that the compiler simply cannot break on its own. Which raises the obvious question: <strong>is that dependency chain <em>fundamental</em> to the algorithm, or is it an artifact of how we happened to write the loop?</strong></p>
</section>
<section id="so-what-is-loop-unrolling-then" class="level1">
<h1>So what is loop unrolling then?</h1>
<p><a href="https://en.wikipedia.org/wiki/Loop_unrolling">Loop unrolling</a> is one of the oldest tricks in the optimizing-compiler playbook, and the idea is disarmingly simple. Instead of executing a loop body once per iteration and paying the overhead of a branch and a counter update every single time, you write out the body two, four, or eight times per iteration, advancing the counter by that many steps at once.</p>
<p>Take a trivial example, nothing to do with our Poisson problem yet:</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n</span>
<span id="cb4-2">   a(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> a(i)</span>
<span id="cb4-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>Unrolled by a factor of two, this becomes:</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb5-2">   a(i)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> a(i)</span>
<span id="cb5-3">   a(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">2.0_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> a(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-4"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>modulo a bit of bookkeeping if <code>n</code> is odd, which we’ll conveniently ignore for now. Fewer branches, fewer counter increments, and — crucially — twice as much independent work exposed to the compiler <em>within a single loop trip</em>. That last point is really the whole appeal: with two statements sitting side by side and no dependency between them, the compiler is free to interleave them, pack them into a single vector instruction, or schedule them however the hardware likes. Unrolling doesn’t make the CPU do less work; it makes the <em>scheduler’s</em> job easier by giving it more to look at at once. So, naturally, the question we should ask is: does the same trick save Gauss-Seidel?</p>
<p><strong>A tempting shortcut that doesn’t work</strong></p>
<p>Let’s try the obvious thing and unroll our Gauss-Seidel inner loop by two, changing nothing else:</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb6-2">   u(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i,j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb6-3">                                     <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-4">   u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i,j)     <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>,j)     <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb6-5">                                     <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>,j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-6"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>Look closely at the second statement: it reads <code>u(i,j)</code> which is exactly the value the <em>first</em> statement just finished computing, two lines above. We haven’t removed the loop-carried dependency at all. We’ve just moved a copy of it <em>inside</em> the loop body, where it’s now a same-iteration dependency instead of a cross-iteration one. The compiler still can’t touch these two statements independently, still can’t vectorize them together, and still has to execute them strictly in order. We’ve paid for the bookkeeping of unrolling (larger code, an odd-<code>n</code> remainder to handle) and bought ourselves precisely nothing.</p>
<p>This is worth sitting with for a second, because it’s a genuinely common trap: unrolling <em>looks</em> like it should help with dependencies, since it’s so often reached for as a vectorization enabler, but by itself it does nothing whatsoever to break a <em>true</em> dependency chain. It’s very good at exposing parallelism that already exists (as in our trivial <code>a(i) = 2*a(i)</code> example) but is completely powerless against parallelism that has to be created. Gauss-Seidel falls squarely in the second category: the dependency isn’t a scheduling accident the compiler failed to notice, it’s baked into the mathematics of the update rule.</p>
<p>So if unrolling alone is a dead end, what’s missing? The answer is that the dependency itself has to be <em>rewritten away</em> before unrolling can do any good. That means reaching for a bit of algebra, not just a bit of loop restructuring.</p>
</section>
<section id="fair-enough-lets-do-that-for-gauss-seidel" class="level1">
<h1>Fair enough, let’s do that for Gauss-Seidel!</h1>
<p>To keep the algebra honest and readable, let’s forget about the full 2D grid for now, and work with the 1D Poisson equation, <img src="https://latex.codecogs.com/png.latex?u''%20=%20-f">, discretized on a uniform grid as</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cfrac%7Bu_%7Bi+1%7D%20-%202%20u_i%20+%20u_%7Bi-1%7D%7D%7B%5CDelta%20x%5E2%7D%20=%20-f_i."></p>
<p>Solving for <img src="https://latex.codecogs.com/png.latex?u_i"> gives the update rule</p>
<p><img src="https://latex.codecogs.com/png.latex?u_i%20=%20%5Cdfrac%7B1%7D%7B2%7D%5Cleft(u_%7Bi-1%7D%20+%20u_%7Bi+1%7D%20+%20%5CDelta%20x%5E2%20f_i%5Cright),"></p>
<p>so, unsurprisingly, the constant playing the role of our earlier <img src="https://latex.codecogs.com/png.latex?0.25"> is now <img src="https://latex.codecogs.com/png.latex?c%20=%20%5Ctfrac12">. Sweeping left to right and updating in place, Gauss-Seidel reads:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb7-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb7-2">   u(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i))</span>
<span id="cb7-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>Exactly one neighbor here is a problem: <code>u(i-1)</code> was overwritten by the <em>previous</em> loop trip, while <code>u(i+1)</code> is still the untouched, old value (the sweep hasn’t reached it yet). That’s the whole dependency, laid completely bare with nothing else to distract from it.</p>
<p><strong>Writing it as a recursion</strong></p>
<p>Group everything that <em>doesn’t</em> depend on the sweep’s own progress into a single per-column quantity,</p>
<p><img src="https://latex.codecogs.com/png.latex?t_i%20=%20u_%7Bi+1%7D%20+%20%5CDelta%20x%5E2%20f_i,"></p>
<p>which is fully known before the block starts (it’s old data, untouched by anything we’re computing right now). The update collapses to a clean, one-term affine recursion:</p>
<p><img src="https://latex.codecogs.com/png.latex?u_i%20=%20c%5Cleft(u_%7Bi-1%7D%20+%20t_i%5Cright),%20%5Cqquad%20u_%7Bi-1%7D%20%5Cequiv%20%5Ctexttt%7Bum%7D,"></p>
<p>where <code>um</code> is shorthand for the last already-updated value feeding into this block. This is precisely the shape that makes the dependency chain visible as <em>just</em> a recursion in <img src="https://latex.codecogs.com/png.latex?u"> — nothing more, nothing less.</p>
<p><strong>Solving the recursion two steps ahead</strong></p>
<p>Rather than computing <img src="https://latex.codecogs.com/png.latex?u_i"> and <em>then</em> <img src="https://latex.codecogs.com/png.latex?u_%7Bi+1%7D"> from it, we solve the recursion explicitly for both, purely in terms of quantities that existed <em>before</em> the block started:</p>
<p><img src="https://latex.codecogs.com/png.latex?u_i%20=%20c%5C,(%5Ctexttt%7Bum%7D%20+%20t_i)"></p>
<p><img src="https://latex.codecogs.com/png.latex?u_%7Bi+1%7D%20=%20c%5C,(u_i%20+%20t_%7Bi+1%7D)%20=%20c%5CBig(c%5C,(%5Ctexttt%7Bum%7D+t_i)%20+%20t_%7Bi+1%7D%5CBig)%20=%20c%5E2%5C,%5Ctexttt%7Bum%7D%20+%20c%5E2%5C,t_i%20+%20c%5C,t_%7Bi+1%7D"></p>
<p>Look at what happened: <img src="https://latex.codecogs.com/png.latex?u_%7Bi+1%7D"> no longer references <img src="https://latex.codecogs.com/png.latex?u_i"> at all. Both outputs are now pure functions of <code>um</code>, <img src="https://latex.codecogs.com/png.latex?t_i">, and <img src="https://latex.codecogs.com/png.latex?t_%7Bi+1%7D">. All quantities that existed <em>before</em> this pair of updates began. The intra-pair dependency that plain unrolling couldn’t touch (the “unroll-only” kernel from the previous section) has been algebraically dissolved.</p>
<p>In code, defining <img src="https://latex.codecogs.com/png.latex?c_2%20%5Cequiv%20c%5E2">, this gives the following kernel:</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb8-2">   um   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-3">   t0   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i)</span>
<span id="cb8-4">   t1   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-5">   u(i)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(um <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> t0)</span>
<span id="cb8-6">   u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>um <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> c2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>t0 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> c<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>t1 <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Does not depend on u(i) !</span></span>
<span id="cb8-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>Two independent expressions, no read of one output by the other’s computation. Exactly what the compiler needs to schedule them concurrently instead of serially.</p>
<p><strong>Back to 2D: nothing new needed</strong></p>
<p>This is where the 1D detour pays off. In the real 2D stencil, the north, south, and source contributions play no role whatsoever in creating the <code>i</code>-dependency — they’re either values from a row Gauss-Seidel has already finished, or values from a row it hasn’t touched yet, but never anything the <code>i</code>-loop itself is computing. So they simply fold into the same lumped <img src="https://latex.codecogs.com/png.latex?t_i"> we just defined:</p>
<p><img src="https://latex.codecogs.com/png.latex?t_i%20%5C;%5Clongrightarrow%5C;%20%5Ctexttt%7Btmp%7D_i%20=%20%5CDelta%20x%5E2%20b(i,j)%20+%20u(i+1,j)%20+%20u(i,j+1)%20+%20u(i,j-1),"></p>
<p>and the exact same two-line substitution applies, verbatim, giving you the unrolled kernel:</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb9-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parameter</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> c  <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span></span>
<span id="cb9-2"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parameter</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> c2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0625_dp</span></span>
<span id="cb9-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb9-4">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb9-5">      um   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j)</span>
<span id="cb9-6">      tmp1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb9-7">      tmp2 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb9-8">      u(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(um <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> tmp1)</span>
<span id="cb9-9">      u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>um <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (c2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>tmp1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> c<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>tmp2)</span>
<span id="cb9-10">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb9-11"></span>
<span id="cb9-12">   <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Handles the case where an odd number of grid points is used.</span></span>
<span id="cb9-13">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb9-14">      tmp1 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> dx2<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>b(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(n, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb9-15">      u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> c<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(u(n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> tmp1)</span>
<span id="cb9-16">   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end if</span></span>
<span id="cb9-17"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span></code></pre></div>
<p>The trailing <code>if</code> block just mops up a leftover column when <code>n</code> is odd, i.e.&nbsp;the same remainder bookkeeping any unrolled loop needs, nothing conceptually new.</p>
<p><strong>Does it actually work?</strong></p>
<p>Ok, so the new kernel looks a bit more complicated than our original one. But mathematically, it’s all the same thing, so is it truly more efficient? Time to check the theory against the machine. Same workflow as before: compile with <code>-O3 -march=native -S</code>, mark the inner loop, and let OSACA loose on it.</p>
<pre><code>                                     Port pressure in cycles                                      
     |  0   - 0DV  |  1   |  2   -  2D  |  3   -  3D  |  4   |  5   |  6   |  7   ||  CP  | LCD  |
--------------------------------------------------------------------------------------------------
  74 |             |      |             |             |      |      |      |      ||      |      |   .L5:
  75 |             |      | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||      |      |   vmovsd 16(%rax), %xmm1
  76 |             |      | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||      |      |   vmovsd 8(%rax), %xmm0
  77 | 0.00        | 0.00 |             |             |      | 0.50 | 0.50 |      ||      |      |   addq $16, %rdx
  78 | 0.50        | 0.50 | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||  4.0 |      |   vaddsd 8(%rax,%rsi,8), %xmm1, %xmm1
  79 | 0.50        | 0.50 | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||      |      |   vaddsd (%rax,%rsi,8), %xmm0, %xmm2
  80 | 0.50        | 0.50 | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||  4.0 |      |   vaddsd 8(%rax,%rcx,8), %xmm1, %xmm1
  81 | 0.50        | 0.50 | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||      |      |   vaddsd (%rax,%rcx,8), %xmm2, %xmm2
  82 | 0.00        | 0.00 |             |             |      | 0.50 | 0.50 |      ||      |      |   addq $16, %rax
  83 | 0.50        | 0.50 | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||  4.0 |      |   vfmadd231sd -8(%rdx), %xmm5, %xmm1
  84 | 0.50        | 0.50 | 0.50   0.50 | 0.50   0.50 |      |      |      |      ||      |      |   vfmadd231sd -16(%rdx), %xmm5, %xmm2
  85 | 0.50        | 0.50 |             |             |      |      |      |      ||  4.0 |      |   vmulsd %xmm4, %xmm1, %xmm1
  86 | 0.50        | 0.50 |             |             |      |      |      |      ||      |      |   vaddsd %xmm3, %xmm2, %xmm0
  87 | 0.50        | 0.50 |             |             |      |      |      |      ||      |      |   vmulsd %xmm4, %xmm0, %xmm0
  88 | 0.50        | 0.50 |             |             |      |      |      |      ||  4.0 |      |   vfmadd132sd %xmm6, %xmm1, %xmm2
  89 | 0.50        | 0.50 |             |             |      |      |      |      ||  4.0 |  4.0 |   vfmadd132sd %xmm6, %xmm2, %xmm3
  90 |             |      |             |             |      | 1.00 |      |      ||  1.0 |      |   vunpcklpd %xmm3, %xmm0, %xmm0
  91 |             |      | 0.00        | 0.00        | 1.00 |      |      | 1.00 ||  0.0 |      |   vmovupd %xmm0, -16(%rax)
  92 | 0.00        | 0.00 |             |             |      | 0.00 | 1.00 |      ||      |      |   cmpq %rax, %rdi
  93 |             |      |             |             |      |      |      |      ||      |      | * jne .L5

       5.50          5.50   4.00   4.00   4.00   4.00   1.00   2.00   2.00   1.00    29.0    4.0</code></pre>
<p>If you look at the last two columns, two things should stand out. The critical path CP got a bit longer, but the loop-carried dependencies (LCD) dropped from 12 to 4 cycles per loop trip — and since each trip now covers two elements, that’s roughly a 6× drop per grid point.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th></th>
<th>LCD (per element)</th>
<th>Throughput floor (per element)</th>
<th>Governing bound</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Gauss-Seidel (scalar, degree 1)</td>
<td>12.0</td>
<td>2.5</td>
<td>latency: <strong>12.0</strong></td>
</tr>
<tr class="even">
<td>Kernel 2 (degree 2)</td>
<td>2.0</td>
<td>2.75</td>
<td>throughput: <strong>2.75</strong></td>
</tr>
</tbody>
</table>
<p>That’s the static prediction. Time to see if the actual machine agrees — and, more interestingly, to see <em>why</em> it agrees, one compilation flag at a time.</p>
<p><strong>Experiment 1 — baseline, no optimization</strong></p>
<p>First, a sanity check. Compiled with <code>-O0</code>, so no scheduling cleverness of any kind gets to play a role:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Solver</th>
<th>Iterations</th>
<th>Time / iteration</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Jacobi</td>
<td>138 000</td>
<td>2 ms</td>
<td>273 s</td>
</tr>
<tr class="even">
<td>Textbook Gauss-Seidel</td>
<td>74 000</td>
<td>2.4 ms</td>
<td>178 s</td>
</tr>
<tr class="odd">
<td>Unrolled Gauss-Seidel</td>
<td>74 000</td>
<td>2 ms</td>
<td>147 s</td>
</tr>
</tbody>
</table>
<p>Two things worth checking here before moving on. First, all three solvers reach the solution in the same number of iterations as the earlier posts — confirming the unrolled kernel really is computing the same thing as the textbook one, algebra and all, not some subtly different (and subtly wrong) approximation. Second, at <code>-O0</code>, the unrolled kernel is already reliably faster than the textbook kernel, by almost exactly the ratio convergence theory predicts for Gauss-Seidel over Jacobi (2×) — a nice confirmation that even without any compiler scheduling to exploit, exposing two independent computations per loop trip buys you something, purely from halving the loop overhead. But nobody ships <code>-O0</code> code, so this is a baseline, not the result.</p>
<p><strong>Experiment 2 — the critical one</strong></p>
<p>Now compile with real optimization, but deliberately keep vectorization switched off (<code>-O3 -march=native -mtune=native -fno-tree-vectorize</code>), so that any gains we see can’t be attributed to SIMD at all:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Solver</th>
<th>Iterations</th>
<th>Time / iteration</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Jacobi</td>
<td>138 000</td>
<td>166 µs</td>
<td>23 s</td>
</tr>
<tr class="even">
<td>Textbook Gauss-Seidel</td>
<td>74 000</td>
<td>1054 µs</td>
<td>78 s</td>
</tr>
<tr class="odd">
<td>Unrolled Gauss-Seidel</td>
<td>74 000</td>
<td>175 µs</td>
<td>13 s</td>
</tr>
</tbody>
</table>
<p>This is the number that matters. With vectorization explicitly disabled, the unrolled kernel is still <strong>six times faster per sweep</strong> than the textbook kernel, and lands within 5% of Jacobi’s per-sweep cost. Since neither Gauss-Seidel kernel can be vectorized under this flag anyway, this gain has nothing to do with packing multiple grid points into one instruction. It comes entirely from something more subtle: reducing the loop-carried dependency lets the CPU’s out-of-order scheduler overlap the <em>scalar</em> work of consecutive iterations, filling execution ports that the textbook kernel’s 12-cycle dependency chain left idle. Fewer cycles spent waiting, same number of instructions issued. This is the empirical confirmation of the LCD-versus-throughput story from the OSACA table above, isolated from vectorization entirely.</p>
<p><strong>Experiment 3 — turning vectorization back on</strong></p>
<p>Finally, the same three kernels, same flags, but with vectorization allowed:</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th>Solver</th>
<th>Iterations</th>
<th>Time / iteration</th>
<th>Total</th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td>Jacobi</td>
<td>138 000</td>
<td>115 µs</td>
<td>16 s</td>
</tr>
<tr class="even">
<td>Textbook Gauss-Seidel</td>
<td>74 000</td>
<td>1054 µs</td>
<td>78 s</td>
</tr>
<tr class="odd">
<td>Unrolled Gauss-Seidel</td>
<td>74 000</td>
<td>175 µs</td>
<td>13 s</td>
</tr>
</tbody>
</table>
<p>Both Gauss-Seidel kernels are completely unchanged, down to the microsecond. That’s expected once you know the assembly: gfortran never vectorizes either GS kernel regardless of this flag, so there’s nothing for the flag to act on. Vectorization was never the mechanism behind experiment 2’s speedup. It’s a separate story that only applies to Jacobi.</p>
<p>And even for Jacobi, notice the gain is real but modest: 166 µs → 115 µs, about 1.4×, not the ~4× you might expect from packing four doubles into a single AVX register. This is the by-product the LCD story predicted but didn’t fully explain on its own: vectorization only pays off to the extent that a kernel’s <em>compute</em> is the bottleneck, and Jacobi’s inner loop, despite having no loop-carried dependency at all, is largely memory-bound at this problem size. It streams through the whole grid every sweep with essentially no data reuse, so DRAM bandwidth caps the achievable speedup well below what the instruction-level analysis alone would suggest.</p>
<p>Put the three experiments side by side and the conclusion is unambiguous: <strong>it’s the absence (or reduction) of a loop-carried dependency, not vectorization, that lets the scheduler do its job.</strong> Vectorization is a nice bonus when it’s available. It does still help Jacobi, just less than you’d naively guess. But it was never the reason Gauss-Seidel was slow, and reducing the LCD was already enough, on its own, to close almost the entire performance gap with Jacobi. Actually, this unrolled Gauss-Seidel kernel hands us the solution almost 3 seconds faster than the Jacobi one. Mission accomplished! Or so it appears…</p>
<p><strong>But look closely at the mnemonics</strong></p>
<p>Here’s the detail that’s easy to miss if you only look at the CP/LCD numbers and declare victory. If you look at the far right of the OSACA output, that is the assembly for inner-most loop (<code>i</code>) of our kernel. Every single arithmetic instruction in that table is still a scalar op — <code>vaddsd</code>, <code>vmulsd</code>, <code>vfmadd231sd</code>, <code>vfmadd132sd</code>, all operating on individual doubles in <code>%xmm</code> registers, not a single packed <code>pd</code> instruction among them. Compare that to Jacobi’s inner loop, built entirely out of <code>vaddpd</code>/<code>vmulpd</code>/<code>vfmadd213pd</code> on 256-bit <code>%ymm</code> registers, four grid points at a time. <strong>The compiler never vectorized this loop.</strong> It didn’t need to, to hit Jacobi’s throughput. It got there by exposing two <em>independent scalar chains</em> (one for <code>u(i,j)</code>, one for <code>u(i+1,j)</code>) that fill different execution ports at the same time, rather than by packing four elements into one instruction the way Jacobi does. The one and only place the two lanes actually meet is right at the end, at line 90: a <code>vunpcklpd</code> stitching the two independently-computed scalars into a single 128-bit register just before the store. That’s not vectorized computation. It’s a vectorized <em>write</em> of two scalar results, a small, free bonus riding on top of the real work, not the source of the speedup itself.</p>
<p>This distinction matters more than it might look like at first glance, and it’s the seed of the next post’s cliffhanger: because the compiler is achieving Jacobi-level throughput through instruction-level parallelism across ports rather than true SIMD, this kernel still can’t be handed a wider vector width for free, and it certainly can’t be split across threads. That tiny 4-cycle pair-to-pair dependency, however cheap, is still a <em>sequential</em> handoff that no <code>do concurrent</code> or OpenMP directive could safely break apart. We’ve matched (and even slightly exceeded) Jacobi’s single-core speed, but not its parallelizability. Those turn out to be two different prizes entirely. And only one of them is in hand.</p>
</section>
<section id="can-we-go-even-further" class="level1">
<h1>Can we go even further?</h1>
<p>Could we push the substitution one step further? Unroll by 4 instead of 2, and squeeze out even more performance? Not really. We’d be at risk of marginally reducing an LCD that’s already a non-issue, while greatly increasing the pressure on the CPU ports.</p>
<p>Here’s why. Going back to the general recursion from before,</p>
<p><img src="https://latex.codecogs.com/png.latex?u_i%20=%20c%5E%7Bi+1%7D%5C,u_%7B-1%7D%20+%20%5Csum_%7Bm=0%7D%5E%7Bi%7D%20c%5E%7Bi-m%7D%5C,t_m,"></p>
<p>each additional element you fold into the substitution costs you one more multiply-add. By the time you reach the degree-4 unrolling, you’re resolving a 4-term cascading sum, every term weighted by its own power of <img src="https://latex.codecogs.com/png.latex?c">. Averaged across the four outputs, that’s roughly twice the multiply-add work per element compared to degree 2. And recall where we ended up last section: the throughput floor is already the governing bound at degree 2 (2.75 cycles/element, versus an LCD of only 2.0). Doubling the arithmetic per element doesn’t touch the LCD’s role in the story at all. It just raises the throughput floor further, from the <em>bound that’s actually constraining us</em>. You’d be spending real port-pressure budget to shave cycles off a dependency chain that was already comfortably hidden behind other work. A losing trade, and one you can see coming from the algebra alone, without even needing to compile anything.</p>
<p>So degree 2 isn’t an arbitrary stopping point. It’s exactly the amount of unrolling needed to push the LCD below the throughput floor, and not a bit more. Past that point, every extra unit of unrolling is pure cost with no corresponding benefit. At least for Gauss-Seidel.</p>
<p><strong>So, where does that leave us?</strong></p>
<p>We made it: a Gauss-Seidel kernel that keeps the mathematical convergence advantage over Jacobi. Same iteration count as the textbook version, while very nearly matching Jacobi’s raw computational efficiency per sweep. A serial win on both fronts at once, which is exactly the “have your cake and eat it too” outcome we set out chasing at the start of this post.</p>
<p>But, there’s always a but, it’s a serial win only. The kernel is still built entirely out of scalar instructions. The compiler (<code>gfortran 15.3</code>) never vectorized it for my particular CPU. It got to Jacobi-level throughput through a different route (independent scalar chains sharing execution ports, not packed SIMD lanes). Worse, that tiny residual pair-to-pair dependency, however cheap, is still a genuine sequential handoff. No <code>do concurrent</code>, no OpenMP directive, no compiler flag can safely split this loop across threads or hand it a wider vector register, because the math still insists that pair <img src="https://latex.codecogs.com/png.latex?k"> cannot start until pair <img src="https://latex.codecogs.com/png.latex?k-1"> has written its result.</p>
<p>For a single core on a laptop, that’s a complete success. But it’s a dead end for anything that needs to scale. And scaling is, after all, the entire point of numerical computing once your grids get big enough to matter. If we want a Gauss-Seidel kernel that vectorizes <em>and</em> multithreads, unrolling and clever algebra won’t get us there. We’ll need to change the order in which we visit grid points altogether, which is exactly where red-black ordering comes in, next time.</p>
<!--Include social share buttons-->
<!-- 
AddToAny check more: https://www.addtoany.com/buttons/for/website 
Using includes will make edits easier, will only need to add or remove button here if needed.
https://quarto.org/docs/authoring/includes.html
-->
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
<p><a class="a2a_dd" href="https://www.addtoany.com/share"></a> <a class="a2a_button_linkedin"></a> <a class="a2a_button_bluesky"></a> <a class="a2a_button_facebook"></a> <a class="a2a_button_copy_link"></a> <a class="a2a_button_email"></a></p>
</div>
<script async="" src="https://static.addtoany.com/menu/page.js"></script>
<hr>
<p><strong>Acknowledgement –</strong> I have to give credit where credit is due. It is <a href="https://github.com/ivan-pi">Ivan Pribec</a> who showed me this particular unrolling trick for Gauss-Seidel. It is also he who put <code>osaca</code> under my radar, so none of what I presented here would have been possible without him. Thanks a lot!</p>
<p><strong>If you want to read more of my stuff</strong></p>
<ul>
<li><a href="https://loiseaujc.github.io/posts/blog-title/gauss_seidel_experiments.html">Gauss-Seidel is faster than Jacobi. Or is it?</a></li>
<li><a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html">Jacobi method: From a naïve implementation to a modern Fortran multithreaded one</a></li>
<li><a href="https://loiseaujc.github.io/posts/blog-title/fortran_vs_python.html">Is Fortran better than Python for teaching the basics of numerical linear algebra?</a></li>
</ul>


</section>

 ]]></description>
  <category>blog</category>
  <guid>https://loiseaujc.github.io/posts/blog-title/make_gauss_seidel_great_again.html</guid>
  <pubDate>Sun, 13 Sep 2026 22:00:00 GMT</pubDate>
  <media:content url="https://loiseaujc.github.io/files/images/blog/make_gauss_seidel_great_again.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Gauss-Seidel is faster than Jacobi. Or is it?</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/blog-title/gauss_seidel_experiments.html</link>
  <description><![CDATA[ 




<p>In a previous <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html">post</a>, I went through some details about how one could write an efficient Jacobi solver in modern <code>Fortran</code> to solve the 2D Poisson equation. Several colleagues and readers mentioned that using the <a href="https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method">Gauss-Seidel method</a> instead should lead to an even faster solver with very minimal modifications to my previous code. So let’s put this to a test.</p>
<p><strong>TL-DR -</strong> While the Gauss-Seidel method theoretically (and empirically) requires half as many iterations as the Jacobi method to solve our test problem, it does not necessarily translate into a solver running twice as fast because of how good compilers are at leveraging modern CPU instructions.</p>
<section id="solving-the-2d-poisson-equation-with-the-gauss-seidel-method" class="level2">
<h2 class="anchored" data-anchor-id="solving-the-2d-poisson-equation-with-the-gauss-seidel-method">Solving the 2D Poisson equation with the Gauss-Seidel method</h2>
<p>Consider a system of linear equations</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BAx%7D%20=%20%5Cmathbf%7Bb%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is an invertible <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> matrix. In the rest of this post, we’ll assume that this problem arises from the finite-difference discretization of the two-dimensional Poisson equation on the unit-square (see <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html#discretizing-the-problem">here</a> for more details). If you’ve ever had a numerical linear algebra, the <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html">Jacobi method</a> is probably the first <em>iterative method</em> you’ve seen to solve this kind of problems. You may even remember your lecturer telling you that, even though easy to implement and analyse, the Jacobi method can be terribly slow. And this is usually the stepping stone for introducing the <a href="https://en.wikipedia.org/wiki/Gauss%E2%80%93Seidel_method">Gauss-Seidel method</a>.</p>
<section id="a-brief-overview" class="level3">
<h3 class="anchored" data-anchor-id="a-brief-overview">A brief overview</h3>
<p>While the Jacobi method relies on the additive decomposition:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BA%7D%20=%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BR%7D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> is the diagonal component of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D">, and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BR%7D"> the remainder, the Gauss-Seidel method relies on the following decomposition:</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BA%7D%20=%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20+%20%5Cmathbf%7BU%7D.%0A"></p>
<p>As before <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> is the diagonal component of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D">, while the matrices <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BL%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BU%7D"> are its strictly lower and upper triangular parts, respectively. Starting from an initial guess <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_0">, the Gauss-Seidel method uses the following recurrence relation</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%20%20%20%20%5Cmathbf%7Bx%7D_%7Bt+1%7D%20=%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cleft(%20%5Cmathbf%7Bb%7D%20-%20%5Cmathbf%7BUx%7D_t%20%5Cright),%0A"></p>
<p>where subscript <img src="https://latex.codecogs.com/png.latex?t"> is the iteration number. This is in essence very similar to the Jacobi update rule <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_%7Bt+1%7D%20=%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cleft(%20%5Cmathbf%7Bb%7D%20-%20%5Cmathbf%7BRx%7D_t%20%5Cright)"> except that <img src="https://latex.codecogs.com/png.latex?%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D"> is a somewhat better approximation of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D%5E%7B-1%7D">. It is thus reasonable to expect the Gauss-Seidel method to converge faster than the Jacobi one.</p>
<div class="callout callout-style-default callout-note callout-titled">
<div class="callout-header d-flex align-content-center">
<div class="callout-icon-container">
<i class="callout-icon"></i>
</div>
<div class="callout-title-container flex-fill">
Note
</div>
</div>
<div class="callout-body-container callout-body">
<p><strong>Practical tip -</strong> Never ever compute <img src="https://latex.codecogs.com/png.latex?%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D">. It is an <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> matrix and computing its inverse requires <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n%5E3)"> operations, just as expensive as computing <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D%5E%7B-1%7D"> and thus defeating the whole point of iterative methods. Instead, realize that <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D"> is a lower triangular matrix and solve the linear system</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%20%5Cmathbf%7Bx%7D_%7Bt+1%7D%20=%20%5Cmathbf%7Bb%7D%20-%20%5Cmathbf%7BUx%7D_t%0A"></p>
<p>using forward substitution instead. In general, it’ll require <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n%5E2)"> operations or even only <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n)"> if <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is sparse. This is a substantial speed-up!</p>
</div>
</div>
</section>
<section id="fair-enough-but-does-it-actually-converge" class="level3">
<h3 class="anchored" data-anchor-id="fair-enough-but-does-it-actually-converge">Fair enough, but does it actually converge?</h3>
<p>Alright, so Gauss-Seidel is a variation on the theme of Jacobi methods, but does it actually converge as well? As in the previous <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html#fair-enough-but-does-it-actually-converge">post</a>, we’ll assume <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is symmetric positive definite. This will give us a sufficient albeit non-necessary condition but will make things easier.</p>
<p>Let us begin by rewriting the Gauss-Seidel update rule as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%20%20%20%20%5Cmathbf%7Bx%7D_%7Bt+1%7D%20&amp;%20=%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cleft(%20%5Cmathbf%7Bb%7D%20-%20%5Cmathbf%7BUx%7D_t%20%5Cright)%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&amp;%20=%20%5Cmathbf%7Bx%7D_t%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cleft(%20%5Cmathbf%7BAx%7D_t%20-%20%5Cmathbf%7Bb%7D%20%5Cright)%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&amp;%20=%20%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bx%7D_t%20+%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7Bb%7D.%0A%5Cend%7Baligned%7D%0A"></p>
<p>Now, let <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_%7B%5Cstar%7D"> be the true solution of the system (i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_%7B%5Cstar%7D%20=%20%5Cmathbf%7BA%7D%5E%7B-1%7D%20%5Cmathbf%7Bb%7D">), and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Be%7D_t%20=%20%5Cmathbf%7Bx%7D_t%20-%20%5Cmathbf%7Bx%7D_%7B%5Cstar%7D"> be the error vector. The dynamics of this error vector are governed by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%20%20%20%20%5Cmathbf%7Be%7D_%7Bt+1%7D%20=%20%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Be%7D_t.%0A"></p>
<p>Obviously, if <img src="https://latex.codecogs.com/png.latex?%5Cdisplaystyle%20%5Clim_%7Bt%20%5Cto%20%5Cinfty%7D%20%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C%20=%200"> then <img src="https://latex.codecogs.com/png.latex?%5Cdisplaystyle%20%5Clim_%7Bt%20%5Cto%20%5Cinfty%7D%20%5Cmathbf%7Bx%7D_t%20=%20%5Cmathbf%7Bx%7D_%7B%5Cstar%7D"> and the Gauss-Seidel method converges to the true solution. Clearly, this will hold provided the spectral radius of the iteration matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> is less than one. The problem of convergence thus reduces to the question of <em>what are the conditions on <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D">, <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BL%7D"> to guarantee that this spectral radius is less than one?</em></p>
<blockquote class="blockquote">
<p><strong>Theorem n°1 –</strong> Convergence of the Gauss-Seidel method</p>
<p>If <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is symmetric positive definite, the Gauss-Seidel iteration converges to the solution of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BAx%7D%20=%20%5Cmathbf%7Bb%7D">.</p>
</blockquote>
<blockquote class="blockquote">
<p><strong>Proof –</strong> Let <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> be symmetric positive definite and <img src="https://latex.codecogs.com/png.latex?%5Cmu"> be an eigenvalue of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> with eigenvectors <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D">. Then <img src="https://latex.codecogs.com/png.latex?%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cmu%20%5Cmathbf%7Bv%7D."> Rearranging terms yields <img src="https://latex.codecogs.com/png.latex?%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cdfrac%7B1%7D%7B1%20-%20%5Cmu%7D%20%5Cmathbf%7BAv%7D."> Then <img src="https://latex.codecogs.com/png.latex?%20%5Cmathbf%7Bv%7D%5E*%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cdfrac%7B1%7D%7B1%20-%20%5Cmu%7D%20%5Cmathbf%7Bv%7D%5E*%20%5Cmathbf%7BAv%7D."> Taking the conjugate transpose of the above expression, we can write <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D%5E*%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20+%20%5Cmathbf%7Bv%7D%5E*%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%5E*%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cleft(%20%5Cdfrac%7B1%7D%7B1%20-%20%5Cmu%7D%20+%20%5Cdfrac%7B1%7D%7B1%20-%20%5Cbar%7B%5Cmu%7D%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%5E*%20%5Cmathbf%7BAv%7D,"> which can be simplified to <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D%5E*%20%5Cleft(%20%5Cmathbf%7BA%7D%20+%20%5Cmathbf%7BD%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cdfrac%7B2%20-%202%20%5CRe(%5Cmu)%7D%7B%5Cvert%201%20-%20%5Cmu%20%5Cvert%5E2%7D%20%5Cmathbf%7Bv%7D%5E*%20%5Cmathbf%7BAv%7D."> After some algebraic manipulations, we obtain <img src="https://latex.codecogs.com/png.latex?%20%5Cmathbf%7Bv%7D%5E*%20%5Cmathbf%7BDv%7D%20=%20%5Cdfrac%7B1%20-%20%5Cvert%20%5Cmu%20%5Cvert%5E2%7D%7B%5Cvert%201%20-%20%5Cmu%20%5Cvert%5E2%7D%20%5Cmathbf%7Bv%7D%5E*%20%5Cmathbf%7BAv%7D."> Since <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> are symmetric positive-definite, and <img src="https://latex.codecogs.com/png.latex?%5Cvert%201%20-%20%5Cmu%20%5Cvert%5E2"> is strictly positive, the numerator must also be strictly positive <img src="https://latex.codecogs.com/png.latex?%201%20-%20%5Cvert%20%5Cmu%20%5Cvert%5E2%20%3E%200%20%5CRightarrow%20%5Cvert%20%5Cmu%20%5Cvert%5E2%20%3C%201%20%5CRightarrow%20%5Cvert%20%5Cmu%20%5Cvert%20%3C%201."> Hence, the iteration matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> has all of its eigenvalues inside the unit circle and the Gauss-Seidel iteration converges.</p>
</blockquote>
<p>Hidden in this proof are two little facts you need to have in mind. First, while a sufficient condition for convergence of the Jacobi method was that both <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> and <img src="https://latex.codecogs.com/png.latex?2%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D"> were symmetric positive-definite, the Gauss-Seidel method only requires <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> to be so. Hence, it converges for a wider set of matrices than the Jacobi method. Second, while the eigenvalues of the Jacobi method were all purely real, those of Gauss-Seidel can be complex (albeit within the unit disk). This will have no consequence whatsoever in the rest of this post, but it might be worth noting it.</p>
</section>
<section id="alright-it-converges.-but-how-fast" class="level3">
<h3 class="anchored" data-anchor-id="alright-it-converges.-but-how-fast">Alright, it converges. But how fast?</h3>
<p>Just like the Jacobi method, the convergence rate of the Gauss-Seidel can be estimated using the <a href="https://en.wikipedia.org/wiki/Spectral_radius">spectral radius</a> of the iteration matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BM%7D%20=%20%5Cmathbf%7BI%7D%20-%20%5Cleft(%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BL%7D%20%5Cright)%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> (which we’ve just proved to be smaller than unity). Obviously, the smaller <img src="https://latex.codecogs.com/png.latex?%5Crho(%5Cmathbf%7BM%7D)">, the better. But the question we are actually interested in is not how quickly does the Gauss-Seidel method converges, but <em>how fast does it converge relative to Jacobi?</em></p>
<p>The answer is actually <em>it depends</em>. There are actually cases where the Jacobi method converges, while the Gauss-Seidel method diverges. In my experience, these are however somewhat pathological cases. What can be said for sure is that: if <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is symmetric positive definite and has a tridiagonal (or block tridiagonal) structure, then the Gauss-Seidel method will asymptotically require only half the number of iterations of the Jacobi method to reach the same accuracy. The proof is by <a href="https://en.wikipedia.org/wiki/David_M._Young_Jr.">David Young</a> (1923-2008), one of the pioneers in numerical analysis and scientific computing.</p>
</section>
</section>
<section id="numerical-experiments-with-fortran" class="level2">
<h2 class="anchored" data-anchor-id="numerical-experiments-with-fortran">Numerical experiments with <code>Fortran</code></h2>
<p>Alright! So now that we know the Gauss-Seidel method is theoretically sound, let us implement it in <code>Fortran</code> and see how good it is in practice. For that purpose, we’ll use the same test case as before: the 2D Poisson equation discretized with the standard five-point Laplacian approximation along with homogeneous Dirichlet boundary conditions. For more details on the derivation of the discrete problem, you can go back to the post on the Jacobi method (<a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html#the-poissons-equation-on-the-unit-square%5D">here</a>).</p>
<section id="the-gauss-seidel-method-for-the-2d-poisson-equation" class="level3">
<h3 class="anchored" data-anchor-id="the-gauss-seidel-method-for-the-2d-poisson-equation">The Gauss-Seidel method for the 2D Poisson equation</h3>
<p>The discretized equation for the interior nodes reads</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdfrac%7B1%7D%7B%5CDelta%20x%5E2%7D%20%5Cleft(%20u_%7Bi+1,%20j%7D%20+%20u_%7Bi-1,%20j%7D%20+%20u_%7Bi,%20j+1%7D%20+%20u_%7Bi,%20j-1%7D%20-%204%20u_%7Bi,%20j%7D%20%5Cright)%20=%20-%20f_%7Bi,%20j%7D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5CDelta%20x"> is our uniform grid spacing in each direction, and <img src="https://latex.codecogs.com/png.latex?f_%7Bi,%20j%7D"> is the discretized source term of the equation. On the left-hand side, the <img src="https://latex.codecogs.com/png.latex?u_%7Bij%7D"> term corresponds to the diagonal component (<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D">), while all the others are the off-diagonal ones (<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BL%7D%20+%20%5Cmathbf%7BL%7D%5E%5Ctop">). Recall that, using the <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html#the-jacobi-method-for-the-2d-poisson-equation">Jacobi method</a>, we treat the diagonal term implicitly and the off-diagonal ones explicitly such that the update rule reads</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_%7Bi,%20j%7D%5E%7B(t+1)%7D%20=%20%5Cdfrac%7B1%7D%7B4%7D%20%5Cleft(%20%5CDelta%20x%5E2%20%5Ccdot%20f_%7Bi,%20j%7D%20+%20u_%7Bi+1,%20j%7D%5E%7B(t)%7D%20+%20u_%7Bi-1,%20j%7D%5E%7B(t)%7D%20+%20u_%7Bi,%20j+1%7D%5E%7B(t)%7D%20+%20u_%7Bi,%20j-1%7D%5E%7B(t)%7D%20%5Cright)%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?t"> is the iteration number. Suppose now that we loop through the different grid points sequentially, with <img src="https://latex.codecogs.com/png.latex?i"> being the fastest changing index and <img src="https://latex.codecogs.com/png.latex?j"> the slowest one. Then, once we reach <img src="https://latex.codecogs.com/png.latex?u_%7Bi,%20j%7D">, we already have improved estimates for <img src="https://latex.codecogs.com/png.latex?u_%7Bi-1,%20j%7D%5E%7B(t+1)%7D"> and <img src="https://latex.codecogs.com/png.latex?u_%7Bi,%20j-1%7D%5E%7B(t+1)%7D">. It would thus make sense to re-use these improved estimates rather than the old values <img src="https://latex.codecogs.com/png.latex?u_%7Bi-1,%20j%7D%5E%7B(t)%7D"> and <img src="https://latex.codecogs.com/png.latex?u_%7Bi,%20j-1%7D%5E%7B(t)%7D">. This leads to the update rule</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_%7Bi,%20j%7D%5E%7B(t+1)%7D%20=%20%5Cdfrac%7B1%7D%7B4%7D%20%5Cleft(%20%5CDelta%20x%5E2%20%5Ccdot%20f_%7Bi,%20j%7D%20+%20u_%7Bi+1,%20j%7D%5E%7B(t)%7D%20+%20u_%7Bi,%20j+1%7D%5E%7B(t)%7D%20+%20u_%7Bi-1,%20j%7D%5E%7B(t+1)%7D%20+%20u_%7Bi,%20j-1%7D%5E%7B(t+1)%7D%20%5Cright).%0A"></p>
<p>This is precisely the update rule for the Gauss-Seidel method applied to the 2D Poisson equation. And since we use improved estimates of some of the unknowns at each iteration, it intuitively makes sense to expect a faster convergence than for the Jacobi method. Another apparent benefit of the Gauss-Seidel method is that, while we need two different buffers for Jacobi (one to store <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bu%7D%5E%7B(t)%7D"> and one for <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bu%7D%5E%7B(t+1)%7D">), we can use only one here and update it in-place. This should reduce our memory consumption by roughly a factor 2, which is probably a good thing. Let’s now move on with the actual implementation.</p>
<section id="the-standard-computational-kernel" class="level4">
<h4 class="anchored" data-anchor-id="the-standard-computational-kernel">The standard computational kernel</h4>
<p>Let us start with the simplest implementation of this kernel. In the rest, we will use <code>double precision</code> arithmetic. The <code>kind</code> parameter will be defined as</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb1-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parameter</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dp <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">selected_real_kind</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">307</span>)</span></code></pre></div>
<p>This is often considered to be a good practice in <code>Fortran</code> and guarantees a certain portability of the code across different compilers and platforms. Let us now turn our attention to the Gauss-Seidel kernel. Our textbook implementation is shown below.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pure</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">subroutine</span> textbook_kernel(nx, ny, u, b, dx)</span>
<span id="cb2-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span> (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">type</span>, <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">external</span>)</span>
<span id="cb2-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny</span>
<span id="cb2-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(inout)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(nx, ny)</span>
<span id="cb2-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b(nx, ny), dx</span>
<span id="cb2-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> i, j</span>
<span id="cb2-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ny<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, nx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-9">            u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb2-10">                                               <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-11">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb2-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb2-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end subroutine</span></span></code></pre></div>
<p>To the actual solver now. As for the <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html#baseline-implementation">Jacobi method</a>, we will check the norm of the correction only now and then due to the slow convergence to avoid unnecessary computations.</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> solver(b, tol, maxiter) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(u)</span>
<span id="cb3-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb3-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b(:, :), tol</span>
<span id="cb3-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span>   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> maxiter</span>
<span id="cb3-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(:, :)</span>
<span id="cb3-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables.</span></span>
<span id="cb3-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>               <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny, i, j, iteration</span>
<span id="cb3-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(:, :)</span>
<span id="cb3-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>              <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dx, l2_norm</span>
<span id="cb3-10"></span>
<span id="cb3-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables.</span></span>
<span id="cb3-12">    nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) ; ny <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb3-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (nx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> ny) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb3-14">        error <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">stop</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Number of points in each direction need to be equal."</span></span>
<span id="cb3-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">endif</span></span>
<span id="cb3-16">    </span>
<span id="cb3-17">    dx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">/</span> (nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-18">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span>(u(nx, ny), v(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb3-19">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span></span>
<span id="cb3-20">    iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb3-21"></span>
<span id="cb3-22">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Beginning of the Gauss-Seidel iterative method.</span></span>
<span id="cb3-23">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ((iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> maxiter) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.and.</span> (l2_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> tol))</span>
<span id="cb3-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Save previous solution for norm correction computation.</span></span>
<span id="cb3-25">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(iteration, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) v <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> u</span>
<span id="cb3-26">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Gauss-Seidel kernel.</span></span>
<span id="cb3-27">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> textbook_kernel(nx, ny, u, b, dx)</span>
<span id="cb3-28">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Compute norm of the correction.</span></span>
<span id="cb3-29">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(iteration, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v)</span>
<span id="cb3-30">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Update iteration counter.</span></span>
<span id="cb3-31">        iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb3-33"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span></code></pre></div>
<p>If you recall the post on the <a href="https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html">Jacobi method</a>, you’ll see that the solver is organized very similarly. Even if you ain’t familiar with <code>Fortran</code>, it is quite readable. After having declared and initialized all of the required variables, the Gauss-Seidel method starts from line 23 and proceeds in 2 steps:</p>
<ol type="1">
<li>Perform the Gauss-Seidel update (line 27).</li>
<li>Compute the 2-norm of the correction (lines 25 and 29) every now and then.</li>
</ol>
<p>This loop keeps on going until the 2-norm of the correction is small enough to claim convergence. In all of our experiments, the tolerance is set to <img src="https://latex.codecogs.com/png.latex?10%5E%7B-8%7D">.</p>
<p><strong>Performance -</strong> We will use 512 points in each direction with a uniform grid spacing and assume the initial guess to be the zero solution for all of our experiments. We thus have slightly more than a quarter million of unknowns, a reasonably large linear system. The code is compiled using <code>gfortran 15.1</code> and the following options: <code>-O3 -march=native -mtune=native</code>. The table below summarizes some of the key computational metrics.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Solver</strong></th>
<th style="text-align: center;"><strong># of iterations</strong></th>
<th style="text-align: center;"><strong>Time/iteration</strong></th>
<th style="text-align: center;"><strong>Total</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Textbook Gauss-Seidel</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">1 ms</td>
<td style="text-align: center;">80 s</td>
</tr>
</tbody>
</table>
<p>Note that the time per iteration has been rounded. Nonetheless, solving a linear system with a quarter million of unknowns in under two minutes is quite impressive when you think about it. It is clearly orders of magnitude faster than if you were to do it by hand (and far less error-prone)! But the real question here is <em>how fast is it compared to the Jacobi method</em>?</p>
</section>
<section id="comparison-against-the-jacobi-method" class="level4">
<h4 class="anchored" data-anchor-id="comparison-against-the-jacobi-method">Comparison against the Jacobi method</h4>
<p>As a reference, we will use the fastest solver we implemented for the Jacobi method. This solver uses the following Jacobi kernel</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb4-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pure</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">subroutine</span> jacobi_kernel(nx, ny, u, v, b, dx)</span>
<span id="cb4-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span> (<span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">type</span>, <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">external</span>)</span>
<span id="cb4-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span>   <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny</span>
<span id="cb4-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(out)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(nx, ny)</span>
<span id="cb4-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span>  <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(nx, ny), b(nx, ny), dx</span>
<span id="cb4-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>               <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> i, j</span>
<span id="cb4-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> concurrent(j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:ny<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:nx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb4-8">        u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb4-9">                                         <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb4-10">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb4-11"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end subroutine</span></span></code></pre></div>
<p>along with the flip-flop trick to avoid any unnecessary copies. Let’s put it to the test!</p>
<p>We will actually proceed in two steps for the comparisons. First, let us tell the compiler to compile both codes using no optimization whatsoever using only the <code>-O0</code> option.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Solver</strong></th>
<th style="text-align: center;"><strong># of iterations</strong></th>
<th style="text-align: center;"><strong>Time/iteration</strong></th>
<th style="text-align: center;"><strong>Total</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Textbook Gauss-Seidel</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">2.5 ms</td>
<td style="text-align: center;">190 s</td>
</tr>
<tr class="even">
<td style="text-align: center;">Jacobi</td>
<td style="text-align: center;">138 000</td>
<td style="text-align: center;">3 ms</td>
<td style="text-align: center;">434 s</td>
</tr>
</tbody>
</table>
<p>As expected, the Gauss-Seidel solver requires roughly half the number of iterations compared to the Jacobi one, which translates into the solution being computed almost twice as fast. This looks consistent with the math. Right off the bat though, you can note that turning off compiler optimization leads to a slower solver, taking 190 seconds with <code>-O0</code> compared to only 80 seconds when compiled with <code>-O3 -march=native -mtune=native</code>. So clearly, the compiler is doing something useful when optimizing. And this is the whole point of using a language like <code>Fortran</code> for number crunching.</p>
<p>Let’s now rerun our test case but with compiler optimization turned on. The results are shown below.</p>
<table class="caption-top table">
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Solver</strong></th>
<th style="text-align: center;"><strong># of iterations</strong></th>
<th style="text-align: center;"><strong>Time/iteration</strong></th>
<th style="text-align: center;"><strong>Total</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Textbook Gauss-Seidel</td>
<td style="text-align: center;">74 000</td>
<td style="text-align: center;">1 ms</td>
<td style="text-align: center;">80 s</td>
</tr>
<tr class="even">
<td style="text-align: center;">Jacobi</td>
<td style="text-align: center;">138 000</td>
<td style="text-align: center;">0.1 ms</td>
<td style="text-align: center;">16 s</td>
</tr>
</tbody>
</table>
<p>Again, the Gauss-Seidel method requires only half the number of iterations to converge. But now, the Jacobi solver is about 5x faster in wall-clock time! This seems surprising, doesn’t it? Looking at the two update rules</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb5-1"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi update</span></span>
<span id="cb5-2">u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb5-3">                                 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb5-4"></span>
<span id="cb5-5"><span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Gauss-Seidel update</span></span>
<span id="cb5-6">u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb5-7">                                 <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span></code></pre></div>
<p>we can see that they have the exact same number of arithmetic operations, the only difference being that Jacobi uses two buffers while Gauss-Seidel uses only one. Our timings would somehow imply that, even though the Jacobi method needs twice as many iterations to converge, each iteration is roughly ten times faster than the Gauss-Seidel one. Clearly, something must have gone wrong when the compiler optimized the Gauss-Seidel kernel. But what could it be?</p>
</section>
</section>
<section id="what-did-go-wrong" class="level3">
<h3 class="anchored" data-anchor-id="what-did-go-wrong">What did go wrong?</h3>
<p>Two options here: either there is a critical issue with the compiler or, despite the two kernels being so similar, there is something fundamentally different between them preventing the compiler from optimizing. It turns out that the second option is the correct one. While a detailed explanation would require us to deep dive into how compilers and modern CPUs work, we will keep things simple and high-level.</p>
<p>In order to understand what is happening, let us consider the equation for <code>u(i, j)</code> and unroll the loop for a couple of <code>i</code> values. For the Gauss-Seidel kernel (dropping the source term for the sake of clarity), this gives</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb6-1">u(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb6-2">u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb6-3">u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb6-4">u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div>
<p>Updating <code>u(i+3, j)</code> (line 4) requires that we already computed <code>u(i+2, j)</code> and <code>u(i+3, j-1)</code>. But updating <code>u(i+2, j)</code> (line 3) itself requires <code>u(i+1, j)</code> to have been updated. Likewise, <code>u(i+1, j)</code> (line 2) requires <code>u(i, j)</code> to be updated. As written, the (lexicographic) Gauss-Seidel method is a sequential process where grid points can be updated only one at a time. This is a <a href="https://en.wikipedia.org/wiki/Loop_dependence_analysis">loop-carried dependence</a> that needs to be preserved, no matter how aggressive the compiler optimization is, in order to guarantee the correctness of the code. Unfortunately, there is not much that can be done here (not entirely true though, but that is a story for another time).</p>
<p>Let us now look at the unrolled loop for the Jacobi kernel.</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb7-1">u(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)  <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb7-2">u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i, j)   <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb7-3">u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span>
<span id="cb7-4">u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span> (v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">4</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">3</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">)</span></span></code></pre></div>
<p>While very similar to the Gauss-Seidel unrolled kernel, one striking difference is that the left-hand side assignment (the <code>u</code> values) depend solely on the <code>v</code> values (which are constant over one sweep). There is no loop-carried dependence here. You could evaluate these four lines in any order you like, it wouldn’t change a thing at the end. You could actually even evaluate all four lines at once! Being able to re-order the computations into whatever order is most convenient and perform multiple evaluations simultaneously (so-called <a href="https://en.wikipedia.org/wiki/Automatic_vectorization">vectorization</a>) are precisely what allow the compiler to be very aggressive in its optimization and leverage all the features of modern CPU architectures! This is what the use of <code>do concurrent</code> in the Jacobi kernel conveys. And this is why, at the end, despite requiring twice as many iterations to converge, the Jacobi method turns out to be faster. Mathematically, the algorithm is not better (it is actually considered to be worse). But practically, it is better suited than Gauss-Seidel for modern computers.</p>
</section>
</section>
<section id="is-all-hope-lost" class="level2">
<h2 class="anchored" data-anchor-id="is-all-hope-lost">Is all hope lost?</h2>
<p>Not at all! The failure of textbook Gauss-Seidel on modern hardware is not a mathematical defect. It is a <strong>dependency and parallelism problem</strong>. To make Gauss-Seidel fast on modern CPUs, we must eliminate the loop-carried dependency without losing its superior convergence rate. This brings us to <strong>grid reordering strategies</strong>:</p>
<ol type="1">
<li><strong>Red-Black (Checkerboard) Ordering:</strong> By splitting the grid into alternate red and black nodes, we can update half the domain in parallel using pure SIMD sweeps, recovering full SIMD vectorization while keeping the Gauss-Seidel convergence rate.</li>
<li><strong>Domain Decomposition &amp; Multi-Scale Layouts:</strong> Partitioning the domain into independent blocks allows us to execute localized Gauss-Seidel sweeps inside L1/L2 caches before exchanging boundary data.</li>
</ol>
<p>In the next post, we will go through the details of the <strong>Red-Black Gauss-Seidel</strong>, implement it in <code>Fortran</code> and explore whether we can recover Gauss-Seidel’s convergence advantage without sacrificing the hardware efficiency of Jacobi.</p>


</section>

 ]]></description>
  <category>blog</category>
  <guid>https://loiseaujc.github.io/posts/blog-title/gauss_seidel_experiments.html</guid>
  <pubDate>Mon, 07 Sep 2026 22:00:00 GMT</pubDate>
  <media:content url="https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Fortran_acs_cover.jpeg/250px-Fortran_acs_cover.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>The HydroGym reinforcement learning platform for fluid dynamics</title>
  <dc:creator>Christian Lagemann</dc:creator>
  <dc:creator>Sajeda Mokbel</dc:creator>
  <dc:creator>Miro Gondrum</dc:creator>
  <dc:creator>Mario Rüttgers</dc:creator>
  <dc:creator>Yuning Wang</dc:creator>
  <dc:creator>Pol Suarez</dc:creator>
  <dc:creator>Ludger Paehler</dc:creator>
  <dc:creator>Deniz A. Bezgin</dc:creator>
  <dc:creator>Aaron B. Buhendwa</dc:creator>
  <dc:creator>Jared L. Callaham</dc:creator>
  <dc:creator>Samuel Ahnert</dc:creator>
  <dc:creator>Nicholas Zolman</dc:creator>
  <dc:creator>Xiao Shao</dc:creator>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <dc:creator>Nikolaus A. Adams</dc:creator>
  <dc:creator>Matthias Meinke</dc:creator>
  <dc:creator>Wolgang Schröder</dc:creator>
  <dc:creator>Kai Lagemann</dc:creator>
  <dc:creator>Esther Lagemann</dc:creator>
  <dc:creator>Ricardo Vinuesa</dc:creator>
  <dc:creator>Steven L. Brunton</dc:creator>
  <link>https://loiseaujc.github.io/posts/paper-title/hydrogym.html</link>
  <description><![CDATA[ 




<!--Include academic icons or bottons-->
<!-- 
Load Academicons v1: https://jpswalsh.github.io/academicons/
-->
<p><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css"></p>
<p><link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"></p>
<!---
The following code are needed to show dimension citation and altmetrics.
https://api.altmetric.com/embeds.html
https://badge.dimensions.ai/
--->
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script>
<script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.plu.mx/widget-popup.js"></script>
<section id="abstract" class="level2">
<h2 class="anchored" data-anchor-id="abstract">Abstract</h2>
<p>Effective control of fluid flows is critical across transportation, energy and medicine, where it can increase lift, reduce drag, enhance mixing and attenuate noise. Yet fluids are notoriously difficult to control because they involve high-dimensional, nonlinear and multiscale dynamics that resist conventional approaches. Reinforcement learning has driven remarkable progress in fields such as protein folding and complex games, which have shared benchmarks and standaridized environments. Fluid dynamics has lacked such infrastructure, so each controller is typically tuned to a single geometry and operating condition, making progress difficult to accumulate, transfer and compare. Here we introduce HydroGym, a solver-independent reinforcement learning platform providing more than 60 validated, openly available flow control environments spanning from canonical laminar flows to complex turbulent flows, with systematic progression in the Reynolds number up to <img src="https://latex.codecogs.com/png.latex?Re%20=%204%20%5Ctimes%2010%5E5">, and Mach number variations in two and three dimensions. Across these environments, agents repeatedly discover robust control principles, including boundary layer manipulation, disruption of acoustic feedback and reorganization of turbulent wakes. Critically, we demonstrate a proof of concept of zero-shot transfer, in which agents that are trained exclusively in inexpensive surrogate environments are deployed to challenging real-world scenarios such as three-dimensional wing section. We achieve a 38% reduction in local skin friction while reducing exploration costs by four orders of magnitude compared with direct on-wing optimization. As this transfer exploits shared near-wall physics, the breadth of generalization remains open, suggesting a new pathway for research toward policy generalization across computationally prohibitive simulation environments. By offering a common, extensible foundation for reproducible research, HydroGym moves flow control from isolated case studies toward a cohesive community effort.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<p>Published <a href="https://www.nature.com/articles/s41586-026-10917-6">paper</a></p>
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script><div class="altmetric-embed" data-badge-popover="right" data-badge-type="donut" data-doi="10.1038/s41586-026-10917-6"></div>

<span class="__dimensions_badge_embed__" data-doi="10.1038/s41586-026-10917-6" data-hide-zero-citations="true" data-style="small_circle"></span><script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@article{lagemann2026,
  author = {Lagemann, Christian and Mokbel, Sajeda and Gondrum, Miro and
    Rüttgers, Mario and Wang, Yuning and Suarez, Pol and Paehler, Ludger
    and A. Bezgin, Deniz and B. Buhendwa, Aaron and L. Callaham, Jared
    and Ahnert, Samuel and Zolman, Nicholas and Shao, Xiao and Loiseau,
    Jean-Christophe and A. Adams, Nikolaus and Meinke, Matthias and
    Schröder, Wolgang and Lagemann, Kai and Lagemann, Esther and
    Vinuesa, Ricardo and L. Brunton, Steven},
  title = {The {HydroGym} Reinforcement Learning Platform for Fluid
    Dynamics},
  journal = {Nature},
  date = {2026-08-19},
  url = {https://www.nature.com/articles/s41586-026-10917-6},
  doi = {10.1038/s41586-026-10917-6},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-lagemann2026" class="csl-entry quarto-appendix-citeas">
Lagemann, Christian, Sajeda Mokbel, Miro Gondrum, Mario Rüttgers, Yuning
Wang, Pol Suarez, Ludger Paehler, et al. 2026. <span>“The HydroGym
Reinforcement Learning Platform for Fluid Dynamics.”</span>
<em>Nature</em>, August. <a href="https://doi.org/10.1038/s41586-026-10917-6">https://doi.org/10.1038/s41586-026-10917-6</a>.
</div></div></section></div> ]]></description>
  <category>paper</category>
  <guid>https://loiseaujc.github.io/posts/paper-title/hydrogym.html</guid>
  <pubDate>Tue, 18 Aug 2026 22:00:00 GMT</pubDate>
  <media:content url="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcRg1JIfdGVzB4czRcBOCQYPMuZiwe1R9YOceMQUuWmmdA&amp;s=10" medium="image"/>
</item>
<item>
  <title>Podcast: Paroles d’Arts et Métiers</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/news-title/ensam_podcast.html</link>
  <description><![CDATA[ 




<p>I have been invited to participate in the podcast <strong>Paroles d’Arts et Métiers</strong> to discuss about my research in fluid dynamics, my experience as a Faculty member at Arts et Métiers and various other topics. Although it is in French, you can find the episode <a href="https://www.youtube.com/watch?v=DEdsQ2K9gnY">here</a>.</p>
<center>
<hr width="50%" hr="">
</center>
<p><img src="https://s3-site.artsetmetiers.fr/public/inline-images/Pr%C3%A9sentation%20Podcast%20-%20Paroles%20d%27Arts%20et%20M%C3%A9tiers.jpg" class="img-fluid" style="width:100.0%"></p>
<!--Include social share buttons-->
<!-- 
AddToAny check more: https://www.addtoany.com/buttons/for/website 
Using includes will make edits easier, will only need to add or remove button here if needed.
https://quarto.org/docs/authoring/includes.html
-->
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
<p><a class="a2a_dd" href="https://www.addtoany.com/share"></a> <a class="a2a_button_linkedin"></a> <a class="a2a_button_bluesky"></a> <a class="a2a_button_facebook"></a> <a class="a2a_button_copy_link"></a> <a class="a2a_button_email"></a></p>
</div>
<script async="" src="https://static.addtoany.com/menu/page.js"></script>



 ]]></description>
  <category>news</category>
  <guid>https://loiseaujc.github.io/posts/news-title/ensam_podcast.html</guid>
  <pubDate>Tue, 28 Apr 2026 22:00:00 GMT</pubDate>
  <media:content url="https://s3-site.artsetmetiers.fr/public/inline-images/Pr%C3%A9sentation%20Podcast%20-%20Paroles%20d%27Arts%20et%20M%C3%A9tiers.jpg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>LightKrylov: Lightweight implementation of Krylov subspace techniques in modern Fortran</title>
  <dc:creator>J. Simon Kern</dc:creator>
  <dc:creator>Ricardo S. Frantz</dc:creator>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/paper-title/lightkrylov.html</link>
  <description><![CDATA[ 




<!--Include academic icons or bottons-->
<!-- 
Load Academicons v1: https://jpswalsh.github.io/academicons/
-->
<p><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css"></p>
<p><link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"></p>
<!---
The following code are needed to show dimension citation and altmetrics.
https://api.altmetric.com/embeds.html
https://badge.dimensions.ai/
--->
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script>
<script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.plu.mx/widget-popup.js"></script>
<section id="abstract" class="level2">
<h2 class="anchored" data-anchor-id="abstract">Abstract</h2>
<p>Direct solvers for linear algebraic systems scale cubically in the problem’s dimension, rapidly becoming intractable for large-scale problems, while sparse factorization may still require quadratic storage du fill-in. Krylov techniques avoid these costs by needing only a routine that computes a matrix-vector product, iteratively building a subspace from which the solution is obtained. <code>LightKrylov</code> is a Fortran package providing a suite of such Krylov methods along with an easy-to-use high level API based on <code>abstract types</code>. It is primarily intended for applications where the linear operator of interest is only available implicitly via a matrix-vector subroutine and enables users to maximally re-use existing components of their code base (including parallelization), thus requiring a minimal set of changes without sacrificing computational performance.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<p>Published <a href="https://joss.theoj.org/papers/10.21105/joss.09623">paper</a></p>
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script><div class="altmetric-embed" data-badge-popover="right" data-badge-type="donut" data-doi="10.21105/joss.09623"></div>

<span class="__dimensions_badge_embed__" data-doi="10.21105/joss.09623" data-hide-zero-citations="true" data-style="small_circle"></span><script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@article{simon_kern2026,
  author = {Simon Kern, J. and S. Frantz, Ricardo and Loiseau,
    Jean-Christophe},
  title = {LightKrylov: {Lightweight} Implementation of {Krylov}
    Subspace Techniques in Modern {Fortran}},
  journal = {Journal of Open Source Software},
  volume = {11},
  number = {117},
  pages = {9623},
  date = {2026-01-29},
  url = {https://joss.theoj.org/papers/10.21105/joss.09623},
  doi = {10.21105/joss.09623},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-simon_kern2026" class="csl-entry quarto-appendix-citeas">
Simon Kern, J., Ricardo S. Frantz, and Jean-Christophe Loiseau. 2026.
<span>“LightKrylov: Lightweight Implementation of Krylov Subspace
Techniques in Modern Fortran.”</span> <em>Journal of Open Source
Software</em> 11 (117): 9623. <a href="https://doi.org/10.21105/joss.09623">https://doi.org/10.21105/joss.09623</a>.
</div></div></section></div> ]]></description>
  <category>paper</category>
  <guid>https://loiseaujc.github.io/posts/paper-title/lightkrylov.html</guid>
  <pubDate>Wed, 28 Jan 2026 23:00:00 GMT</pubDate>
  <media:content url="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQTcztYR1fxZqI2zk6pNGRiiR7Q-FIJCIStOg2TwQh0FA&amp;s" medium="image"/>
</item>
<item>
  <title>Jacobi method: From a naïve implementation to a modern Fortran multithreaded one</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html</link>
  <description><![CDATA[ 




<p>In a previous <a href="https://loiseaujc.github.io/posts/blog-title/fortran_vs_python.html">post</a>, I used the Jacobi method to illustrate some merits of <code>Fortran</code> over <code>Python</code> for teaching purposes. Since then, I received a handful of messages asking how to write efficient <code>Fortran</code> code. Because of its algorithmic simplicity, the Jacobi method makes for an excellent case study. In this post, we’ll see how to go from a naïve implementation taking a minute to solve a linear system with a quarter million unknowns to a multithreaded version taking less 3 seconds. Bonus point: the code is entirely standard-compliant and you don’t need to know anything about <a href="https://www.openmp.org/">OpenMP</a> or <a href="https://en.wikipedia.org/wiki/Message_Passing_Interface">MPI</a>. If you want to see the whole code, check <a href="https://github.com/loiseaujc/Jacobi-Experiments">this</a> GitHub repo. But first, what is the Jacobi method?</p>
<section id="solving-a-linear-system-with-the-jacobi-method" class="level2">
<h2 class="anchored" data-anchor-id="solving-a-linear-system-with-the-jacobi-method">Solving a linear system with the Jacobi method?</h2>
<p>Consider the system of linear equations</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BAx%7D%20=%20%5Cmathbf%7Bb%7D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is an invertible <img src="https://latex.codecogs.com/png.latex?n%20%5Ctimes%20n"> matrix. If you ever had a course on numerical linear algebra, you have seen various algorithms to solve this problem. These are divided in two categories: direct solvers targetting small- to medium-sized dense matrices, and iterative solvers for large sparse matrices.</p>
<p>Among the zoo of iterative methods, the <a href="https://en.wikipedia.org/wiki/Jacobi_method">Jacobi method</a> is probably the first one you’ve encountered. There are two reasons for that:</p>
<ol type="1">
<li>It is easy to implement, no matter the programming language.</li>
<li>Its theoretical analysis is rather simple, even for undergrad students.</li>
</ol>
<p>It does come with its limitations though: it does not work for all possible matrices and the convergence is rather slow (i.e.&nbsp;it requires many iterations). Because of these, the Jacobi method is not a viable alternative compared to the (preconditioned) <a href="https://en.wikipedia.org/wiki/Conjugate_gradient_method">conjugate gradient</a> or <a href="https://en.wikipedia.org/wiki/Multigrid_method">multigrid</a> methods and is thus hardly used in production codes. It is however, in my opinion, a fantastic learning example. So, how does it work?</p>
<section id="a-brief-overview" class="level3">
<h3 class="anchored" data-anchor-id="a-brief-overview">A brief overview</h3>
<p>The Jacobi method relies on the additive decomposition:</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D%20=%20%5Cmathbf%7BD%7D%20+%20%5Cmathbf%7BR%7D,"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> is the diagonal component of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D">, and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BR%7D"> consists of the off-diagonal terms. Plugging this decomposition into our system leads to</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BDx%7D%20+%20%5Cmathbf%7BRx%7D%20=%20%5Cmathbf%7Bb%7D.%0A"></p>
<p>Starting from an initial guess <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_0">, the core idea of the Jacobi method is to treat the diagonal contributions <em>implicitly</em> and the off-diagonal ones <em>explicitly</em>, analoguous to a time-integration scheme. This leads to the following iterative scheme</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Bx%7D_%7Bt+1%7D%20=%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cleft(%20%5Cmathbf%7Bb%7D%20-%20%5Cmathbf%7BRx%7D_t%20%5Cright),%0A"></p>
<p>where subscript <img src="https://latex.codecogs.com/png.latex?t"> denotes the <img src="https://latex.codecogs.com/png.latex?t">-th iteration of the method. What we claim then is that, under suitable assumptions on <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D">, the iterate <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_t"> converges to the actual solution of the system as <img src="https://latex.codecogs.com/png.latex?t%20%5Cto%20%5Cinfty">. So when does it converge? And if so, how fast does it converge?<sup>1</sup></p>
</section>
<section id="fair-enough-but-does-it-actually-converge" class="level3">
<h3 class="anchored" data-anchor-id="fair-enough-but-does-it-actually-converge">Fair enough, but does it actually converge?</h3>
<p>The questions of whether or not an iterative method converges and, if so, how fast does it converge are obviously critical to assess its competitiveness. To answer to both of these questions, let us rewrite the Jacobi iteration as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Baligned%7D%0A%5Cmathbf%7Bx%7D_%7Bt+1%7D%20%20%20%20&amp;%20%20%20=%20%5Cmathbf%7Bx%7D_t%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cleft(%20%5Cmathbf%7Bb%7D%20-%20%5Cmathbf%7BAx%7D_t%20%5Cright)%20%5C%5C%0A%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20%20&amp;%20%20%20=%20%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bx%7D_t%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7Bb%7D.%0A%5Cend%7Baligned%7D%0A"></p>
<p>To derive this expression, simply add and subtract <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BDx%7D_t"> inside the parenthesized term in the right-hand side and group terms together. Now, let <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_%7B%5Cstar%7D"> be the true solution of the system, i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_%7B%5Cstar%7D%20=%20%5Cmathbf%7BA%7D%5E%7B-1%7D%20%5Cmathbf%7Bb%7D">, and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Be%7D_t%20=%20%5Cmathbf%7Bx%7D_t%20-%20%5Cmathbf%7Bx%7D_%7B%5Cstar%7D"> be the error at iteration <img src="https://latex.codecogs.com/png.latex?t">. Using simple algebraic manipulations, the dynamics of the error vector are governed by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7Be%7D_%7Bt+1%7D%20=%20%5Cleft(%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Be%7D_t.%0A"></p>
<p>Obviously, the Jacobi method converges to the correct solution provided <img src="https://latex.codecogs.com/png.latex?%5Cdisplaystyle%20%5Clim_%7Bt%20%5Cto%20%5Cinfty%7D%20%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C%20=%200"> where <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Ccdot%20%5C%7C"> is a suitable vector norm. The question of its convergence thus reduces to: under what condition on <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> does the norm of the error vector goes to zero?</p>
<blockquote class="blockquote">
<p><strong>Theorem n°1 –</strong> The Jacobi iterative method</p>
<p><img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_%7Bt+1%7D%20=%20%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bx%7D_t%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%5Cmathbf%7Bb%7D"></p>
<p>converges for any initial vector <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bx%7D_0"> provided <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%20%3C%201"> where <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Ccdot%20%5C%7C"> is a matrix norm induced by the corresponding vector norm.</p>
</blockquote>
<blockquote class="blockquote">
<p><strong>Sketch of the proof –</strong> Let <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Ccdot%20%5C%7C"> be a matrix norm consistent with a vector norm. Then</p>
<p><img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7Be%7D_%7Bt+1%7D%20%5C%7C%20=%20%5C%7C%20%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Be%7D_t%20%5C%7C%20%5Cleq%20%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%20%5Ccdot%20%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C."></p>
<p>A simple inductive argument shows that (for <img src="https://latex.codecogs.com/png.latex?t"> large enough)</p>
<p><img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C%20%5Cleq%20%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%5Et%20%5Ccdot%20%5C%7C%20%5Cmathbf%7Be%7D_0%20%5C%7C."></p>
<p>Hence, <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C"> converges to zero as <img src="https://latex.codecogs.com/png.latex?t%20%5Cto%20%5Cinfty"> for all <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Be%7D_0"> provided that <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%20%3C%201">.</p>
</blockquote>
<p>Alright, we now know the Jacobi method converges provided <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%20%3C%201">. But what are the necessary and/or sufficient conditions on <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> for <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C"> to be less than unity?</p>
<blockquote class="blockquote">
<p><strong>Theorem n°2 –</strong> Let <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> and <img src="https://latex.codecogs.com/png.latex?2%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D"> be symmetric positive definite matrices. Then, the Jacobi iteration converges.</p>
</blockquote>
<p>The proof is divided in two parts.</p>
<blockquote class="blockquote">
<p><strong>Proof (part 1) –</strong> Let <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> be symmetric positive definite and <img src="https://latex.codecogs.com/png.latex?%5Cmu"> be an eigenvalue of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> with eigenvector <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D">. Then <img src="https://latex.codecogs.com/png.latex?%20%5Cleft(%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cmu%20%5Cmathbf%7Bv%7D."> Mutliplying from the left by <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D%5E%7B-1%7D"> leads to <img src="https://latex.codecogs.com/png.latex?%20%5Cleft(%20%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cmu%20%5Cmathbf%7BDv%7D."> Then <img src="https://latex.codecogs.com/png.latex?%20%5Cmathbf%7Bv%7D%5ET%20%5Cleft(%20%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cmu%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BDv%7D."> Re-arranging terms yields <img src="https://latex.codecogs.com/png.latex?%20%5Cleft(1%20-%20%5Cmu%20%5Cright)%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BDv%7D%20=%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BAv%7D."> <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> and <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> being symmetric positive definite, we have <img src="https://latex.codecogs.com/png.latex?%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BAv%7D%20%3E%200%20%5Cquad%20%5Ctext%7Band%7D%20%5Cquad%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BDv%7D%20%3E%200."> It implies <img src="https://latex.codecogs.com/png.latex?%5Cleft(%201%20-%20%5Cmu%20%5Cright)%20%3E%200"> and thus <img src="https://latex.codecogs.com/png.latex?%5Cmu%20%3C%201">. Hence, all the eigenvalues <img src="https://latex.codecogs.com/png.latex?%5Cmu"> of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D"> are less than unity.</p>
</blockquote>
<p>While we arrived at the conclusion that <img src="https://latex.codecogs.com/png.latex?%5Cmu%20%3C%201">, nothing so far implies <img src="https://latex.codecogs.com/png.latex?-1%20%3C%20%5Cmu"> and thus <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%20%3C%201">. This is where the condition on <img src="https://latex.codecogs.com/png.latex?2%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D"> comes into play.</p>
<blockquote class="blockquote">
<p><strong>Proof (part 2) –</strong> Let <img src="https://latex.codecogs.com/png.latex?2%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D"> be symmetric positive definite. Then <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D%5ET%20%5Cleft(%202%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20%3E%200"> and thus <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D%5ET%20%5Cleft(%20%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20%3E%20-%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BDv%7D."> From part 1, we know that <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7Bv%7D%5ET%20%5Cleft(%20%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D%20%5Cright)%20%5Cmathbf%7Bv%7D%20=%20%5Cmu%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BDv%7D">. Hence <img src="https://latex.codecogs.com/png.latex?%5Cmu%20%5Cmathbf%7Bv%7D%5ET%20%5Cmathbf%7BDv%7D%20%3E%20-%20%5Cmathbf%7Bv%7D%5ET%5Cmathbf%7BDv%7D"> implying <img src="https://latex.codecogs.com/png.latex?-1%20%3C%20%5Cmu">. Combined with part 1, we thus have <img src="https://latex.codecogs.com/png.latex?-1%20%3C%20%5Cmu%20%3C%201">, i.e.&nbsp;the eigenvalues of <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%5Cmathbf%7BA%7D"> are inside the unit circle (and real) and the Jacobi iteration converges.</p>
</blockquote>
<p>Note that the condition “<img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> and <img src="https://latex.codecogs.com/png.latex?2%5Cmathbf%7BD%7D%20-%20%5Cmathbf%7BA%7D"> being symmetric positive definite” is sufficient although not necessary to guarantee the convergence of the Jacobi method. Another classical sufficient but non-necessary condition is that <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is strictly row diagonally dominant. Again, it is relatively easy to prove but since I’m the teacher here, I’ll end this theoretical analysis with the nefarious: <em>This is left as an exercise for the reader.</em></p>
</section>
<section id="alright-it-converges.-but-how-fast" class="level3">
<h3 class="anchored" data-anchor-id="alright-it-converges.-but-how-fast">Alright, it converges. But how fast?</h3>
<p>We’ve actually already partially answered this question. From the sketch of the proof for Theorem n°1, we have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C%20%5Cleq%20%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%5Et%20%5Ccdot%20%5C%7C%20%5Cmathbf%7Be%7D_0%20%5C%7C.%0A"></p>
<p>Obviously, the smaller <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C">, the faster the convergence. And we know that <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C%20%3C%201"> is related to the eigenvalues of the iteration matrix being inside the unit circle. So how do the eigenvalues influence the convergence rate of the method?</p>
<p>A useful quantity to estimate the convergence rate of the method is the <a href="https://en.wikipedia.org/wiki/Spectral_radius">spectral radius</a> of the iteration matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BM%7D%20=%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D">. It is defined as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Crho(%5Cmathbf%7BM%7D)%20=%20%5Cmax%20%5Cleft%5C%7B%20%5Cvert%20%5Cmu_1%20%5Cvert,%20%5Ccdots,%20%5Cvert%20%5Cmu_n%20%5Cvert%20%5Cright%5C%7D,%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cmu_i"> are the eigenvalues. Moreover, <img src="https://latex.codecogs.com/png.latex?%5Crho(%5Cmathbf%7BM%7D)%20%5Cleq%20%5C%7C%20%5Cmathbf%7BM%7D%20%5C%7C"> for every natural matrix norms. From our previous discussion, we know that <img src="https://latex.codecogs.com/png.latex?%5Crho(%5Cmathbf%7BM%7D)%20%3C%201"> since the Jacobi method converges. Eventhough the spectral radius is only a lower bound for <img src="https://latex.codecogs.com/png.latex?%5C%7C%20%5Cmathbf%7BI%7D%20-%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BA%7D%20%5C%7C">, we’ll assume for the sake of simplicity that it is pretty tight. Hence, we roughly have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5C%7C%20%5Cmathbf%7Be%7D_t%20%5C%7C%20%5Cleq%20%5Crho(%5Cmathbf%7BM%7D)%5Et%20%5Ccdot%20%5C%7C%20%5Cmathbf%7Be%7D_0%20%5C%7C.%0A"></p>
<p>We could make this statement more formal but it wouldn’t change the intuition: the smaller the spectral radius, the larger the asymptotic convergence rate. Unfortunately, there is not much else to say without knowing exactly the matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> so let’s turn to the actual problem of interest of this post.</p>
</section>
</section>
<section id="the-poissons-equation-on-the-unit-square" class="level2">
<h2 class="anchored" data-anchor-id="the-poissons-equation-on-the-unit-square">The Poisson’s equation on the unit square</h2>
<p>The <a href="https://en.wikipedia.org/wiki/Poisson%27s_equation">Poisson’s equation</a> is an elliptic partial differential equation (PDE) appearing in numerous fields of physics. Let’s parse what this means:</p>
<ul>
<li><strong>PDE –</strong> The solution of the equation depends on more than one variable, where the variables are typically the different spatial dimensions.</li>
<li><strong>Elliptic –</strong> The solution exhibits a certain notion of smoothness, whatever that means mathematically. Typically, it implies that the solution will not exhibit any discontinuities or very steep fronts in contrast to what you may see for hyperbolic equations (think shock waves for instance).</li>
</ul>
<p>Mathematically, it reads</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cnabla%5E2%20u%20=%20-f,%0A"></p>
<p>along with appropriate boundary conditions. In the rest of this post, we’ll consider one of its simplest variations. The domain <img src="https://latex.codecogs.com/png.latex?%5COmega"> is the unit square, i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?%5COmega%20=%20%5Cleft%5B0,%201%20%5Cright%5D%5E2"> and we will consider only homogenous Dirichlet boundary conditions, i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?u%20=%200"> on the boundaries of the square. Our problem thus is</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft%5C%7B%0A%5Cbegin%7Baligned%7D%0A%20%20%20%20%5Cdfrac%7B%5Cpartial%5E2%20u%7D%7B%5Cpartial%20x%5E2%7D%20+%20%5Cdfrac%7B%5Cpartial%5E2%20u%7D%7B%5Cpartial%20y%5E2%7D%20=%20-f%20%5Cquad%20&amp;%20%5Ctext%7Bfor%20%7D%20(x,%20y)%20%5Cin%20%5COmega%20%5C%5C%0A%20%20%20%20u(x,%20y)%20=%200%20%5Cquad%20&amp;%20%5Ctext%7Bfor%20%7D%20(x,%20y)%20%5Cin%20%5Cpartial%20%5COmega.%0A%5Cend%7Baligned%7D%0A%5Cright.%0A"></p>
<p>Note that the problem is sufficiently simple that you can express its analytical solution using Fourier series. But we are computational scientists, so we’ll solve the problem numerically.</p>
<section id="discretizing-the-problem" class="level3">
<h3 class="anchored" data-anchor-id="discretizing-the-problem">Discretizing the problem</h3>
<p>There are many different ways to discretize a partial differential equation. Finite differences, finite volumes, finite elements, spectral elements, spectral methods, pseudo-spectral methods, etc. There are no silver bullets though. Each has its pros and cons. At the end of the day, the discretization method used is often a matter of personal preferences. To keep things simple, we will consider the standard second-order accurate finite-difference scheme. We will also consider a uniform grid spacing in each direction so that our differential operators can be approximated as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdfrac%7B%5Cpartial%5E2%20u%7D%7B%5Cpartial%20x%5E2%7D%20%5Csimeq%20%5Cdfrac%7Bu_%7Bi+1,%20j%7D%20-%202u_%7Bi,%20j%7D%20+%20u_%7Bi-1,%20j%7D%7D%7B%5CDelta%20x%5E2%7D%0A%5Cquad%20%5Ctext%7Band%7D%20%5Cquad%0A%5Cdfrac%7B%5Cpartial%5E2%20u%7D%7B%5Cpartial%20y%5E2%7D%20%5Csimeq%20%5Cdfrac%7Bu_%7Bi,%20j+1%7D%20-%202u_%7Bi,%20j%7D%20+%20u_%7Bi,%20j-1%7D%7D%7B%5CDelta%20y%5E2%7D%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5CDelta%20x"> and <img src="https://latex.codecogs.com/png.latex?%5CDelta%20y"> are the grid sizes in each direction, and <img src="https://latex.codecogs.com/png.latex?u_%7Bi,%20j%7D"> is the value of our unknown function evaluated at the grid point <img src="https://latex.codecogs.com/png.latex?(x_i,%20y_j)%20=%20(i%20%5CDelta%20x,%20j%20%5CDelta%20y)">. For the sake of simplicity, we’ll assume furthermore that <img src="https://latex.codecogs.com/png.latex?%5CDelta%20x%20=%20%5CDelta%20y">. Our discretized partial differential equation for points inside of the domain then reads</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdfrac%7B1%7D%7B%5CDelta%20x%5E2%7D%20%5Cleft(%20u_%7Bi+1,%20j%7D%20+%20u_%7Bi-1,%20j%7D%20+%20u_%7Bi,%20j+1%7D%20+%20u_%7Bi,%20j-1%7D%20-%204%20u_%7Bi,%20j%7D%20%5Cright)%20=%20-f_%7Bi,%20j%7D.%0A"></p>
<p>It may not seem like a linear system, but trust me, it is. The field <img src="https://latex.codecogs.com/png.latex?u(x,%20y)"> is represented as a two-dimensional array (and I mean <em>array</em>, not <em>matrix</em>) for the sake of simplicity. But you can always represent it as a vector by simply stacking the columns of the array on top of one another, and likewise for the right-hand side forcing <img src="https://latex.codecogs.com/png.latex?f(x,%20y)">.</p>
<p>So, where is the matrix then? Consider a single column of the array <img src="https://latex.codecogs.com/png.latex?u">, that is we fix <img src="https://latex.codecogs.com/png.latex?x"> and only consider different <img src="https://latex.codecogs.com/png.latex?y">-values. The second-order derivative in the <img src="https://latex.codecogs.com/png.latex?y">-direction can be represented as an <img src="https://latex.codecogs.com/png.latex?(n_y-2)%20%5Ctimes%20(n_y-2)"> matrix given by</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cmathbf%7BD%7D_y%0A=%0A%5Cdfrac%7B1%7D%7B%5CDelta%20y%5E2%7D%0A%5Cbegin%7Bbmatrix%7D%0A%20%20%20%20-2%20%20&amp;%201%20%5C%5C%0A%20%20%20%201%20%20%20&amp;%20-2%20&amp;%201%20%5C%5C%0A%20%20%20%20%20%20%20%20&amp;%201%20&amp;%20-2%20&amp;%201%20%5C%5C%0A%20%20%20%20%20%20%20%20&amp;%20%20&amp;%20%5Cddots%20&amp;%20%5Cddots%20&amp;%20%5Cddots%20%5C%5C%0A%20%20%20%20%20%20%20%20&amp;%20&amp;%20%20%20&amp;%20%20%201%20%20%20&amp;%20%20%20-2%20%20&amp;%20%20%201%20%20%20%5C%5C%0A%20%20%20%20%20%20%20%20&amp;%20&amp;%20%20%20&amp;%20%20%20%20%20%20%20&amp;%20%20%201%20%20%20&amp;%20-2%0A%5Cend%7Bbmatrix%7D%0A"></p>
<p>where we excluded the points on the upper and lower boundaries as these are equal to zero owing to our choice of boundary conditions. Likewise, considering a single row of <img src="https://latex.codecogs.com/png.latex?u"> (i.e.&nbsp;fixing <img src="https://latex.codecogs.com/png.latex?y"> and considering different <img src="https://latex.codecogs.com/png.latex?x">-values), the second-order derivative in the horizontal direction can be represented as an <img src="https://latex.codecogs.com/png.latex?(n_x%20-%202)%20%5Ctimes%20(n_x%20-2)"> matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D_x"> with the same tridiagonal structure as <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D_y">. Our problem can then be represented in a standard linear system form as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cleft(%20%5Cmathbf%7BI%7D_%7Bn_y%7D%20%5Cotimes%20%5Cmathbf%7BD%7D_x%20+%20%5Cmathbf%7BD%7D_y%20%5Cotimes%20%5Cmathbf%7BI%7D_%7Bn_x%7D%20%5Cright)%20%5Cmathrm%7Bvec%7D(u)%20=%20-%5Cmathrm%7Bvec%7D(f),%0A"></p>
<p>where <img src="https://latex.codecogs.com/png.latex?%5Cotimes"> is the <a href="https://en.wikipedia.org/wiki/Kronecker_product">Kronecker product</a>. If we were to explicitly construct it, this matrix would have <img src="https://latex.codecogs.com/png.latex?n_x%20%5Ctimes%20n_y"> rows and likewise for the number of columns. For a discretization employing 512 points in each direction, that would be 260 100 columns and rows. Pretty big then, and completely intractable for standard direct linear solvers!</p>
</section>
<section id="the-jacobi-method-for-the-2d-poisson-equation" class="level3">
<h3 class="anchored" data-anchor-id="the-jacobi-method-for-the-2d-poisson-equation">The Jacobi method for the 2D Poisson equation</h3>
<p>Time to write the Jacobi update rule for our particular problem. Recall that our problem reads</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cdfrac%7B1%7D%7B%5CDelta%20x%5E2%7D%20%5Cleft(%20u_%7Bi+1,%20j%7D%20+%20u_%7Bi-1,%20j%7D%20+%20u_%7Bi,%20j+1%7D%20+%20u_%7Bi,%20j-1%7D%20-%204%20u_%7Bi,%20j%7D%20%5Cright)%20=%20-f_%7Bi,%20j%7D.%0A"></p>
<p>On the left-hand side, the <img src="https://latex.codecogs.com/png.latex?u_%7Bij%7D"> term corresponds to the diagonal component while all the others are the off-diagonal ones. Following what we have written for the Jacobi method in matrix form, specializing for this equation leads to the following update rule</p>
<p><img src="https://latex.codecogs.com/png.latex?%0Au_%7Bi,%20j%7D%5E%7B(t+1)%7D%20=%20%5Cdfrac%7B1%7D%7B4%7D%20%5Cleft(%20%5CDelta%20x%5E2%20%5Ccdot%20f_%7Bi,%20j%7D%20-%20u_%7Bi+1,%20j%7D%5E%7B(t)%7D%20-%20u_%7Bi-1,%20j%7D%5E%7B(t)%7D%20-%20u_%7Bi,%20j+1%7D%5E%7B(t)%7D%20-%20u_%7Bi,%20j-1%7D%5E%7B(t)%7D%20%5Cright)%0A"></p>
<p>where the superscript <img src="https://latex.codecogs.com/png.latex?%5Ccdot%20%5E%7B(t)%7D"> denotes the iteration number. This will be fairly simple to implement. Create two arrays, one to store the solution at iteration <img src="https://latex.codecogs.com/png.latex?t"> and the other one at iteration <img src="https://latex.codecogs.com/png.latex?t+1">. Loop over the indices and update the <img src="https://latex.codecogs.com/png.latex?(i,%20j)">-th entries of the second table with the appropriate combination of values from the first one. Note that it is important to keep these two tables. If you were to have only one table and directly update its <img src="https://latex.codecogs.com/png.latex?(i,%20j)"> entry, you would end-up with a different method: Gauss-Seidel. More on that in a later post (maybe).</p>
<p><strong>Convergence properties –</strong> The second-order accurate central finite-difference approximation <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BL%7D%20=%20%5Cmathbf%7BI%7D_%7Bn_y%7D%20%5Cotimes%20%5Cmathbf%7BD%7D_x%20+%20%5Cmathbf%7BD%7D_y%20%5Cotimes%20%5Cmathbf%7BI%7D_%7Bn_x%7D"> of the Laplace operator <img src="https://latex.codecogs.com/png.latex?%5Cnabla%5E2"> is a symmetric negative definite matrix. Hence, <img src="https://latex.codecogs.com/png.latex?-%5Cmathbf%7BL%7D"> is symmetric positive definite (and this is the reason for why there is a minus sign on the right-hand side of our problem if you wondered). It is easy to show moreover that <img src="https://latex.codecogs.com/png.latex?-%5Cmathbf%7BL%7D"> satisfies the assumptions for Theorem n°2 to hold. Hence, after a sufficiently large number of iterations, the Jacobi method will converge to the actual solution of our linear system. But again, how fast?</p>
<p>As before, we can get some intuition by looking at the spectral radius of this matrix. I won’t go through the calculations (and I’ll assume <img src="https://latex.codecogs.com/png.latex?n_x%20=%20n_y">), but we basically have</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Crho(%5Cmathbf%7BI%7D%20+%20%5Cmathbf%7BD%7D%5E%7B-1%7D%20%5Cmathbf%7BL%7D)%20%5Csimeq%201%20-%20%5Cdfrac%7B%5Cpi%5E2%7D%7B2%20n_x%5E2%7D.%0A"></p>
<p>Using an increasing number of grid points to discretize our domain (that is considering a finer and finer mesh), the spectral radius of the iteration matrix gets closer and closer to unity. As a consequence, the Jacobi method requires more and more iterations to compute a reasonnably accurate solution. This poor scaling property is one of the reasons why it ain’t actually used nowadays in high-performance computing solvers. But this does not concern us here.</p>
</section>
</section>
<section id="let-fortran-shine" class="level2">
<h2 class="anchored" data-anchor-id="let-fortran-shine">Let <code>Fortran</code> shine!</h2>
<p>Alright! It’s time for what you all expected: the <code>Fortran</code> implementation. We’ll start with a simple translation to <code>Fortran</code> of the pseudo-code. This implementation will be our baseline. We will then incrementally improve it by using various tips and tricks with a particular constraint: use only standard-compliant <code>Fortran</code> code. I’ll try to explain the rationale behind every decision I make along the way. By the end of our journey, we’ll have a standard-compliant implementation which can naturally leverage multithreaded computations without having have to write a single openMP pragma. Performance-wise, we’ll end up with a 20x to 30x speed-up compared to our baseline implementation without every leaving the realm of <code>Fortran</code>. Too good to be true? Bare with me then!</p>
<section id="baseline-implementation" class="level3">
<h3 class="anchored" data-anchor-id="baseline-implementation">Baseline implementation</h3>
<p>Let us start with an almost verbatim translation of the pseudo-code to <code>Fortran</code>. In the rest, we will use <code>double precision</code> arithmetic. The <code>kind</code> parameter will be defined as</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb1-1"><span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">parameter</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dp <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">selected_real_kind</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">15</span>, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">307</span>)</span></code></pre></div>
<p>This is often considered to be a good practice in <code>Fortran</code> and guarantees a certain portability of the code across different compilers and platforms. Let us now turn our attention to the Jacobi kernel. Our textbook implementation is shown below.</p>
<div class="sourceCode" id="cb2" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb2-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pure</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">subroutine</span> textbook_kernel(nx, ny, u, v, b, dx)</span>
<span id="cb2-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb2-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny</span>
<span id="cb2-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(out)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(nx, ny)</span>
<span id="cb2-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(nx, ny), b(nx, ny), dx</span>
<span id="cb2-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> i, j</span>
<span id="cb2-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ny<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-8">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, nx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb2-9">            u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb2-10">                                             <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb2-11">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb2-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb2-13"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end subroutine</span></span></code></pre></div>
<p>The <code>pure</code> keyword is here to tell the compiler that we guarantee this subroutine has no unintended side-effect. It is not technically mandatory, but it is also part of the good practices in <code>Fortran</code>. Hopefully, if we do things right, this kernel should be where we spend most of the computational time. To the actual solver now.</p>
<div class="sourceCode" id="cb3" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb3-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> textbook_solver(b, tol, maxiter) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(u)</span>
<span id="cb3-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb3-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b(:, :), tol</span>
<span id="cb3-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> maxiter</span>
<span id="cb3-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(:, :)</span>
<span id="cb3-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables</span></span>
<span id="cb3-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny, i, j, iteration</span>
<span id="cb3-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(:, :)</span>
<span id="cb3-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dx, l2_norm</span>
<span id="cb3-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables</span></span>
<span id="cb3-11">    nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>); ny <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>); dx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">/</span>(nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb3-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (nx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> ny) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb3-13">        error <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">stop</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Number of points in each direction need to be equal."</span></span>
<span id="cb3-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">endif</span></span>
<span id="cb3-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span> (u(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb3-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span> (v(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb3-17">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span></span>
<span id="cb3-18">    iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb3-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Begining of the Jacobi iterative method.</span></span>
<span id="cb3-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ((iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> maxiter) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.and.</span> (l2_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> tol))</span>
<span id="cb3-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi iteration.</span></span>
<span id="cb3-22">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> textbook_kernel(nx, ny, v, u, b, dx)</span>
<span id="cb3-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Compute error norm.</span></span>
<span id="cb3-24">        l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v)</span>
<span id="cb3-25">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Update variable.</span></span>
<span id="cb3-26">        u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> v</span>
<span id="cb3-27">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Update iteration counter.</span></span>
<span id="cb3-28">        iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb3-29">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb3-30">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Textbook solver :"</span></span>
<span id="cb3-31">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    - Number of iterations :"</span>, iteration</span>
<span id="cb3-32">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    - l2-norm of the error :"</span>, l2_norm</span>
<span id="cb3-33"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span></code></pre></div>
<p>Even if you ain’t familiar with <code>Fortran</code>, the code should be quite readable. After having declared and initialized all of the required variables, the Jacobi method starts from Line 20 and proceeds in 3 steps:</p>
<ol type="1">
<li>Perform the Jacobi update by calling our textbook kernel.</li>
<li>Compute the 2-norm of the correction.</li>
<li>Update the current solution with its latest estimate.</li>
</ol>
<p>This loop keeps on going until the 2-norm of the correction is small enough to claim convergence. In all of our experiments, the tolerance is set to <img src="https://latex.codecogs.com/png.latex?10%5E%7B-8%7D">.</p>
<p><strong>Performances –</strong> We will use 512 points in each direction with a uniform grid spacing and assume the initial guess to be the zero solution for all of our experiments. We thus have slightly more than a quarter million of unknowns, a reasonnably large linear system. The code is compiled using <code>gfortran 15.1</code> and the following options: <code>-O3 -march=native -mtune=native</code>. The table below summarizes some of the key computational metrics.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Solver</strong></th>
<th style="text-align: center;"><strong># of iterations</strong></th>
<th style="text-align: center;"><strong>Time to solution</strong></th>
<th style="text-align: center;"><strong>Speed-up w.r.t. baseline</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Textbook</td>
<td style="text-align: center;">128 395</td>
<td style="text-align: center;">58 s</td>
<td style="text-align: center;">1</td>
</tr>
</tbody>
</table>
<p>Solving a linear system with a quarter million of unknowns in under one minute is quite impressive when you think about it. It is clearly orders of magnitude faster than if you were to do it by hand (and far less error-prone)! But is this the best we can do? You might be inclined to say <em>yes</em>. After all, our implementation is an almost verbatim translation of the pseudo-code and maths don’t lie. But that ain’t completely true though… When it comes to scientific computing, there are many streetfighting skills you can pick along the way to massively improve the computational performances of a given algorithm. So let’s start optimizing!</p>
</section>
<section id="you-shall-not-copy" class="level3">
<h3 class="anchored" data-anchor-id="you-shall-not-copy">You shall not copy!</h3>
<p>Our Jacobi kernel is so simple that there ain’t much room for improvement so let’s look at the solver itself starting with line 26</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb4-1">    u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> v</span></code></pre></div>
<p>It is the update of our current estimate of the solution with the one we’ve just computed. It essentially is a <code>copy</code> operation. Given how simple our Jacobi kernel is, it acutally takes almost as long as computing a Jacobi update. So let’s get rid of it by simply performing an additional call to the Jacobi kernel with the role of <code>u</code> and <code>v</code> being flipped.</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> nocopy_solver(b, tol, maxiter) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(u)</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb5-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b(:, :), tol</span>
<span id="cb5-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> maxiter</span>
<span id="cb5-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(:, :)</span>
<span id="cb5-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables</span></span>
<span id="cb5-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny, i, j, iteration</span>
<span id="cb5-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(:, :)</span>
<span id="cb5-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dx, l2_norm</span>
<span id="cb5-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables</span></span>
<span id="cb5-11">    nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>); ny <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>); dx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">/</span>(nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb5-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (nx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> ny) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb5-13">        error <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">stop</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Number of points in each direction need to be equal."</span></span>
<span id="cb5-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">endif</span></span>
<span id="cb5-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span> (u(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb5-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span> (v(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb5-17">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span></span>
<span id="cb5-18">    iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb5-19">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Begining of the Jacobi iterative method.</span></span>
<span id="cb5-20">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ((iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> maxiter) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.and.</span> (l2_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> tol))</span>
<span id="cb5-21">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi iteration (no copy).</span></span>
<span id="cb5-22">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> textbook_kernel(nx, ny, v, u, b, dx)</span>
<span id="cb5-23">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> textbook_kernel(nx, ny, u, v, b, dx)</span>
<span id="cb5-24">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Compute error norm.</span></span>
<span id="cb5-25">        l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v)</span>
<span id="cb5-26">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Update iteration counter.</span></span>
<span id="cb5-27">        iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb5-28">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb5-29">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"No-copy solver  :"</span></span>
<span id="cb5-30">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    - Number of iterations :"</span>, iteration</span>
<span id="cb5-31">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    - l2-norm of the error :"</span>, l2_norm</span>
<span id="cb5-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span></code></pre></div>
<p><strong>Performances –</strong> Code-wise, very little has changed compared to our baseline implementation. The <code>nocopy_solver</code> slightly departs from the pseudo-code but is still as readable. Peformance-wise, it is a different story.</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Solver</strong></th>
<th style="text-align: center;"><strong># of iterations</strong></th>
<th style="text-align: center;"><strong>Time to solution</strong></th>
<th style="text-align: center;"><strong>Speed-up w.r.t. baseline</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Textbook</td>
<td style="text-align: center;">128 395</td>
<td style="text-align: center;">58 s</td>
<td style="text-align: center;">1</td>
</tr>
<tr class="even">
<td style="text-align: center;">No-copy</td>
<td style="text-align: center;">128 396</td>
<td style="text-align: center;">30 s</td>
<td style="text-align: center;">1.9</td>
</tr>
</tbody>
</table>
<p>This one-line change makes our solver compute the solution twice as fast! But don’t get too excited, it is somewhat expected if you think of it. A <code>copy</code> is roughly as expensive as computing a Jacobi update itself. As a consequence, in the time frame it took our baseline implementation to peform a Jacobi update followed by a copy, the <code>nocopy_solver</code> performed no copy (hence the name) but two updates. And boom, twice as fast. This is the first but probably most important take-away message:</p>
<blockquote class="blockquote">
<p><strong>Avoid copies like the plague and re-use intermediate results as much as possible.</strong></p>
</blockquote>
<p>This is not specific to <code>Fortran</code> and is true for pretty much any programming language you use.</p>
</section>
<section id="further-optimizations" class="level3">
<h3 class="anchored" data-anchor-id="further-optimizations">Further optimizations</h3>
<p>While the no-copy trick is fairly general, let us turn now to somewhat Jacobi-specific optimization tricks starting with the Jacobi kernel itself. Let <img src="https://latex.codecogs.com/png.latex?v"> be the current approximate solution and <img src="https://latex.codecogs.com/png.latex?u"> the new one being computed. Recall that the update rule is as follows</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%20%20%20%20u_%7Bi,%20j%7D%20=%20%5Cdfrac%7B1%7D%7B4%7D%20%5Cleft(%20b_%7Bi,%20j%7D%20%5Ccdot%20%5CDelta%20x%5E2%20-%20(v_%7Bi+1,%20j%7D%20+%20v_%7Bi-1,%20j%7D%20+%20v_%7Bi,%20j+1%7D%20+%20v_%7Bi,%20j-1%7D)%20%5Cright)%0A"></p>
<p>for all <img src="https://latex.codecogs.com/png.latex?i"> and <img src="https://latex.codecogs.com/png.latex?j"> corresponding to points in the interior of the computational domain. One crucial observation is that there are no dependencies between the entries of <img src="https://latex.codecogs.com/png.latex?u">: they can be update in any abitrary order, not necessarily the lexicographic one. In particular, we could let the compiler decide on its own what is the most efficient way to do this update based on its internal mechanics. The 2008 standard introduced a particular construct conveying precisely this: the <code>do concurrent</code>. Below is the Jacobi kernel rewritten using this construct.</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb6-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pure</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">subroutine</span> doconcurrent_kernel(nx, ny, u, v, b, dx)</span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">external</span>)</span>
<span id="cb6-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer(ilp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny</span>
<span id="cb6-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(out)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(nx, ny)</span>
<span id="cb6-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(nx, ny), b(nx, ny), dx</span>
<span id="cb6-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> i, j</span>
<span id="cb6-7">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> concurrent(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:ny <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb6-8">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi update.</span></span>
<span id="cb6-9">        u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb6-10">                                         <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i, j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i, j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb6-12"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end subroutine</span> doconcurrent_kernel</span></code></pre></div>
<p>For now, it does not actually improve the computational performances of our kernel. For serial computations, it mostly is a syntactic sugar letting someone reading the code know that this loop could technically be computed in parallel with no problem. It might help the compiler optimize a bit, but the kernel being so simple I haven’t seen much changes. It’ll be different though once we go to multithreaded computations but that’s a story for slightly later.</p>
<p>The main source of computational improvement is located on line 25:</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb7-1">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(u<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span>v)</span></code></pre></div>
<p>There is nothing particularly wrong with this line. In practice however, the Jacobi method is quite slow to converge and computing the residual norm at every iteration incurs extra computational costs which are unecessary. We would be much better off by checking the residual only once in a while. We could replace it with</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb8-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(iteration, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>)) l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v)</span></code></pre></div>
<p>or using the newest <code>do concurrent</code> construct</p>
<div class="sourceCode" id="cb9" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb9-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(iteration, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>)) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb9-2">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span></span>
<span id="cb9-3">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> concurrent(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:nx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:ny<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) reduce(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span>:l2_norm)</span>
<span id="cb9-4">        l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i, j))<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb9-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb9-6">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sqrt</span>(l2_norm)</span>
<span id="cb9-7"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">endif</span></span></code></pre></div>
<p>Either way is fine, <code>norm2</code> is an intrinsic <code>Fortran</code> function and its implementation has already been optimized by the compiler vendors anyway. Checking the residual norm every 1000 iterations is arbitrary. It has been chosen out of simplicity considering that the method takes 128 000 iterations to converge for our particular problem. In practice, you might actually pass this as an extra argument to the solver to let the user decide. Here is the updated solver.</p>
<div class="sourceCode" id="cb10" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb10-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> doconcurrent_solver(b, tol, maxiter) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(u)</span>
<span id="cb10-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span>(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">external</span>)</span>
<span id="cb10-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b(:, :), tol</span>
<span id="cb10-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> maxiter</span>
<span id="cb10-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(:, :)</span>
<span id="cb10-6">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables.</span></span>
<span id="cb10-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny, i, j, iteration</span>
<span id="cb10-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> v(:, :)</span>
<span id="cb10-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dx, l2_norm</span>
<span id="cb10-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables</span></span>
<span id="cb10-11">    nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>); ny <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>); dx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">/</span>(nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb10-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (nx <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/=</span> ny) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">then</span></span>
<span id="cb10-13">        error <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">stop</span> <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Number of points in each direction need to be equal."</span></span>
<span id="cb10-14">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">endif</span></span>
<span id="cb10-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span> (u(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb10-16">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span> (v(nx, ny), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span>)</span>
<span id="cb10-17">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0_dp</span></span>
<span id="cb10-18">    iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span></span>
<span id="cb10-19">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">while</span> ((iteration <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;</span> maxiter) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">.and.</span> (l2_norm <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&gt;</span> tol))</span>
<span id="cb10-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi kernel (no-copy).</span></span>
<span id="cb10-21">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> doconcurrent_kernel(nx, ny, v, u, b, dx)</span>
<span id="cb10-22">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> doconcurrent_kernel(nx, ny, u, v, b, dx)</span>
<span id="cb10-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Compute error norm.</span></span>
<span id="cb10-24">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (<span class="bu" style="color: null;
background-color: null;
font-style: inherit;">mod</span>(iteration, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1000</span>) <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">==</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>) l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> error_norm(u, v)</span>
<span id="cb10-25">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Update iteration counter.</span></span>
<span id="cb10-26">        iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb10-27">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb10-28">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> error_norm(u, v) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Sanity check</span></span>
<span id="cb10-29">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"Do-concurrent solver :"</span></span>
<span id="cb10-30">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    - Number of iterations :"</span>, iteration</span>
<span id="cb10-31">    <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">print</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>, <span class="st" style="color: #20794D;
background-color: null;
font-style: inherit;">"    - l2-norm of the error :"</span>, l2_norm</span>
<span id="cb10-32"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span>
<span id="cb10-33"></span>
<span id="cb10-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">pure</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> error_norm(u, v) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(l2_norm)</span>
<span id="cb10-35">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb10-36">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real(dp)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u(:, :), v(:, :)</span>
<span id="cb10-37">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> i, j</span>
<span id="cb10-38">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0_dp</span></span>
<span id="cb10-39">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> concurrent(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(u, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>:<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(u, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) reduce(<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span>:l2_norm)</span>
<span id="cb10-40">        l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span> (u(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> v(i, j))<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span></span>
<span id="cb10-41">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb10-42">    l2_norm <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">sqrt</span>(l2_norm)</span>
<span id="cb10-43"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span></code></pre></div>
<p><strong>Performances –</strong> Again, the new solver is just as readable as the previous ones. No big changes here, but look at the performances below!</p>
<table class="caption-top table">
<colgroup>
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
<col style="width: 25%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Solver</strong></th>
<th style="text-align: center;"><strong># of iterations</strong></th>
<th style="text-align: center;"><strong>Time to solution</strong></th>
<th style="text-align: center;"><strong>Speed-up w.r.t. baseline</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">Textbook</td>
<td style="text-align: center;">128 395</td>
<td style="text-align: center;">58 s</td>
<td style="text-align: center;">1</td>
</tr>
<tr class="even">
<td style="text-align: center;">No-copy</td>
<td style="text-align: center;">128 396</td>
<td style="text-align: center;">30 s</td>
<td style="text-align: center;">1.9</td>
</tr>
<tr class="odd">
<td style="text-align: center;">do concurrent</td>
<td style="text-align: center;">129 002</td>
<td style="text-align: center;">16 s</td>
<td style="text-align: center;">3.6</td>
</tr>
</tbody>
</table>
<p>The new solver is 3 to 4 times faster than our baseline! This is quite remarkable given that we changed only a couple of lines compared to the original textbook implementation. Things are not always so clear cut for more complex algorithms, but still. And here is our second take-way:</p>
<blockquote class="blockquote">
<p><strong>Compute just what you need, not more.</strong></p>
</blockquote>
<p>Here, computing the residual norm for each iteration was clearly a non-negligible and non-necessary bottleneck. At this point, there is no more low-hanging fruit for optimization. You might think this is it. A 3.6x speed-up is good enough and you may call it a day. But you know what? There’s more. We can reach a 20x to 30x speed-up without changing anything else to the code!</p>
</section>
<section id="multithreaded-performances" class="level3">
<h3 class="anchored" data-anchor-id="multithreaded-performances">Multithreaded performances</h3>
<p>Computers these days tend to have built-in parallel computing capabilities. Yet, we haven’t leverage these so far. Let’s change that. I will not get into a discussion about openMP vs MPI or GPU offloading. I will keep things very practical instead. Remember when I said we can perform the Jacobi update in any <img src="https://latex.codecogs.com/png.latex?i,%20j"> order we want? The Jacobi update rule is embarassingly parallel. That is precisely what the <code>do concurrent</code> construct is conveying. And compilers can leverage this for increased computational performances. For our solver, it is as simple as changing the <code>gfortran</code> compilation options from</p>
<pre><code>-O3 -mtune=native -march=native</code></pre>
<p>to</p>
<pre><code>-O3 -mtune=native -march=native -ftree-parallelize-loops=n</code></pre>
<p>where <code>n</code> is the number of processes/threads to be used. And that’s it. Litterally. And look at these performances!</p>
<table class="caption-top table">
<colgroup>
<col style="width: 33%">
<col style="width: 33%">
<col style="width: 33%">
</colgroup>
<thead>
<tr class="header">
<th style="text-align: center;"><strong>Number of threads</strong></th>
<th style="text-align: center;"><strong>Time to solution</strong></th>
<th style="text-align: center;"><strong>Speed-up w.r.t. baseline</strong></th>
</tr>
</thead>
<tbody>
<tr class="odd">
<td style="text-align: center;">1</td>
<td style="text-align: center;">16 s</td>
<td style="text-align: center;">3.6</td>
</tr>
<tr class="even">
<td style="text-align: center;">2</td>
<td style="text-align: center;">8.6 s</td>
<td style="text-align: center;">6.7</td>
</tr>
<tr class="odd">
<td style="text-align: center;">4</td>
<td style="text-align: center;">4.1 s</td>
<td style="text-align: center;">14.1</td>
</tr>
<tr class="even">
<td style="text-align: center;">8</td>
<td style="text-align: center;">2.1 s</td>
<td style="text-align: center;">27.6</td>
</tr>
</tbody>
</table>
<p>As promised, we finish with a linear solver computing the solution of a system with a quarter million of unknowns in less than 3 seconds and not a single openMP pragma or MPI call. The code is the <strong>exact same as before</strong>. The only thing that changed is the addition of the new compilation option. And that is enough to reach a 27x speed-up compared to our original textbook implementation!<sup>2</sup> Pretty good considering we changed only a handful of lines of code and added only one extra compilation option, innit? There would be a lot more to say about the different parallel computing paradigms and the associated neaty greedy details, but this post is already sufficiently long as it is so I’ll stop right there. I’ll leave you with a cautionnary quote by the famous <a href="https://en.wikipedia.org/wiki/Donald_Knuth">Donald Knuth</a> though</p>
<blockquote class="blockquote">
<p>The real problem is that programmers have spent far too much time worrying about efficiency in the wrong places and at the wrong times; premature optimization is the root of all evil (or at least most of it) in programming.</p>
</blockquote>
<!--Include social share buttons-->
<!-- 
AddToAny check more: https://www.addtoany.com/buttons/for/website 
Using includes will make edits easier, will only need to add or remove button here if needed.
https://quarto.org/docs/authoring/includes.html
-->
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
<p><a class="a2a_dd" href="https://www.addtoany.com/share"></a> <a class="a2a_button_linkedin"></a> <a class="a2a_button_bluesky"></a> <a class="a2a_button_facebook"></a> <a class="a2a_button_copy_link"></a> <a class="a2a_button_email"></a></p>
</div>
<script async="" src="https://static.addtoany.com/menu/page.js"></script>


</section>
</section>


<div id="quarto-appendix" class="default"><section id="footnotes" class="footnotes footnotes-end-of-document"><h2 class="anchored quarto-appendix-heading">Footnotes</h2>

<ol>
<li id="fn1"><p>We need to invert the matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> at each iteration. While this operation requires <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n%5E3)"> flops for a general matrix, <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BD%7D"> here is diagonal. Hence, its inverse is straightforward to compute and only requires <img src="https://latex.codecogs.com/png.latex?n"> flops. Likewise, the matrix-vector product <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BRx%7D_t"> requires in general <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n%5E2)"> flops. In most applications though, the matrix <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BA%7D"> is sparse and so is <img src="https://latex.codecogs.com/png.latex?%5Cmathbf%7BR%7D">, typically reducing the number of floating points operations down to <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n)"> as well. For a sparse linear system, each iteration of the Jacobi method hence requires <img src="https://latex.codecogs.com/png.latex?%5Cmathcal%7BO%7D(n)"> flops. The question then is how many iterations does it take to converge?↩︎</p></li>
<li id="fn2"><p>If you run the code on your own computer, you may get different results as it depends on the number of cores you have, how fast they are, etc. But still, you should get pretty much the same trend.↩︎</p></li>
</ol>
</section></div> ]]></description>
  <category>blog</category>
  <guid>https://loiseaujc.github.io/posts/blog-title/jacobi_experiments.html</guid>
  <pubDate>Mon, 22 Sep 2025 22:00:00 GMT</pubDate>
  <media:content url="https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Fortran_acs_cover.jpeg/250px-Fortran_acs_cover.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>Is Fortran better than Python for teaching the basics of numerical linear algebra?</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <link>https://loiseaujc.github.io/posts/blog-title/fortran_vs_python.html</link>
  <description><![CDATA[ 




<p><u><strong>Disclaimer</strong></u> – This is <strong>not</strong> a post about which language is the most elegant or which implementation is the fastest (we all know it’s <code>Fortran</code>). It’s about <strong>teaching</strong> the basics of scientific computing to engineering students with a limited programming experience. Yes, the <code>Numpy</code>/<code>Scipy</code>/<code>matplotlib</code> stack is awesome. Yes, you can use <code>numba</code> or <code>jax</code> to speed up your code, or <code>Cython</code>, or even <code>Mojo</code> the latest kid in the block. Or you know what? Use <code>Julia</code> or <code>Rust</code> instead. But that’s not the <em>basics</em> and it’s beyond the point.</p>
<center>
<hr width="50%" hr="">
</center>
<div class="quarto-figure quarto-figure-center">
<figure class="figure">
<p><img src="https://upload.wikimedia.org/wikipedia/commons/d/de/Lochkarte_FORTRAN.jpg" class="img-fluid figure-img" style="width:100.0%"></p>
<figcaption>Punched card in 80-column format according to the IBM standard. Printed for use with one line of FORTRAN source code (used in the example as a declaration of six float variables). Source: <a href="https://commons.wikimedia.org/wiki/File:Lochkarte_FORTRAN.jpg">Wikimedia Commons</a></figcaption>
</figure>
</div>
<center>
<hr width="50%" hr="">
</center>
<p>I’ve been teaching an <em>Intro to Scientific Computing</em> class for nearly 10+ years. This class is intended for second year engineering students and, as such, places a large emphasis on numerical linear algebra. Like the rest of Academia, I’m using a combination of <code>Python</code> and <code>numpy</code> arrays for this. Yet, after all these years, I start to believe it ain’t necessarily the right choice for a first encounter with numerical linear algebra. Obvisouly everything is not black and white and I’ll try to be nuanced. But, in my opinion, a strongly typed language such as <code>Fortran</code> might lead to an overall better <u>learning</u> experience. And that’s what it’s all about when you start Uni: learning the principles of scientific programming, not the quirks of a particular language (unless you’re a CS student, which is a different crowd).</p>
<p>Don’t get me wrong though. Being proficient with <code>numpy</code>, <code>scipy</code> and <code>matplotlib</code> is an absolute necessity for STEM students today, and that’s a good thing. Even from an educational perspective, the scientific <code>Python</code> ecosystem enables students to do really cool projects, putting the fun back in learning. It would be completely non-sensical to deny this. But using <code>x = np.linalg.solve(A, b)</code> ain’t the same thing as having a basic understanding of how these algorithms work. And to be clear: the goal of these classes is not to transform a student into a numerical linear algebra expert who could write the next generation LAPACK. It is to teach them just enough of numerical computing so that, when they’ll transition to an engineering position, they’ll be able to make an informed decision regarding which solver or algorithm to use when writing a simulation or data analysis tool to tackle whatever business problem they’re working on.</p>
<p>If you liked and aced your numerical methods class, then what I’ll discuss might not necessary be relatable. You’re one of a kind. More often than not, students struggle with such courses. This could be due to genuine comprehension difficulties, or lazyness and lack of motivation simply because they don’t see the point. While both issues are equally important to address, I’ll focus on the first one: students who are willing to put the effort into learning the subject but have difficulties transforming the mathematical algorithm into an actionnable piece of code. Note however that initially motivated but struggling students might easily drift to the second type, hence my focus there first.</p>
<p>In the rest of this post, I’ll go through two examples. For each, I’ll show a typical <code>Python</code> code such a student might write and discuss all of the classical problems they’ve encountered to get there. A large part of these are syntax issues or result from the permissiveness of an interpreted language like <code>Python</code> which is a double edged sword. Then I’ll show an equivalent <code>Fortran</code> implementation and explain why I believe it can solve part of these problems. But first, I need to address the two elephants in the room:</p>
<ol type="1">
<li>My research is on applied mathematics and numerical linear algebra for the physical sciences. I am <strong>not</strong> doing research on Education. Everything that follows comes from my reflection about my interactions with students I taught to or mentored. If you have scientific evidence (pertaining to scientific computing in particular) proving me wrong, please tell me.</li>
<li>When I write <code>Fortran</code>, what I really mean is modern <code>Fortran</code>, not <code>FORTRAN</code>. Anything pre-dating the <code>Fortran 90</code> standard (or even better, the <code>Fortran 2018</code> one) is not even an option (yes, I’m looking at you <code>FORTRAN 77</code> and your incomprehensible <code>goto</code>, error-prone <code>common</code>, artithmetic <code>if</code> and what not).</li>
</ol>
<p>With that being said, let’s get started with a concrete, yet classical, example to illustrate my point.</p>
<section id="the-hello-world-of-iterative-solvers" class="level2">
<h2 class="anchored" data-anchor-id="the-hello-world-of-iterative-solvers">The <code>Hello World</code> of iterative solvers</h2>
<p>You’ve started University a year ago and are taking your first class on scientific computing. Maybe you already went through the hassle of Gaussian elimination and the LU factorization. During the last class, Professor X discussed about <a href="https://en.wikipedia.org/wiki/Iterative_method">iterative solvers</a> for linear systems. It is now the hands-on session and today’s goal is to implement the <a href="https://en.wikipedia.org/wiki/Jacobi_method">Jacobi method</a>. Why Jacobi? Because it is simple enough to implement in an hour or so.</p>
<p>The exact problem you’re given is the following:</p>
<blockquote class="blockquote">
<p>Consider the Poisson equation with homogeneous Dirichlet boundary conditions on the unit-square. Assume the Laplace operator has been discretized using a second-order accurate central finite-difference scheme. The discretized equation reads <img src="https://latex.codecogs.com/png.latex?%5Cdfrac%7Bu_%7Bi+1,%20j%7D%20-%202u_%7Bi,%20j%7D%20+%20u_%7Bi-1,%20j%7D%7D%7B%5CDelta%20x%5E2%7D%20+%20%5Cdfrac%7Bu_%7Bi,%20j+1%7D%20-%202u_%7Bi,%20j%7D%20+%20u_%7Bi,%20j-1%7D%7D%7B%5CDelta%20y%5E2%7D%20=%20b_%7Bi,%20j%7D."> For the sake of simplicity, take <img src="https://latex.codecogs.com/png.latex?%5CDelta%20x%20=%20%5CDelta%20y">. Write a function implementing the Jacobi method to solve the resulting linear system to a user-prescribed tolerance.</p>
</blockquote>
<p>We can all agree this is a simple enough yet somewhat realistic example. More importantly, it is sufficient to illustrate my point. Here is what the average student might write in <code>Python</code>.</p>
<div class="sourceCode" id="cb1" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb1-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb1-2"></span>
<span id="cb1-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> jacobi(b , dx, tol, maxiter):</span>
<span id="cb1-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initialize variables.</span></span>
<span id="cb1-5">    nx, ny <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> b.shape</span>
<span id="cb1-6">    residual <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb1-7">    u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((nx, ny))</span>
<span id="cb1-8">    tmp <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((nx, ny))</span>
<span id="cb1-9"></span>
<span id="cb1-10">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Jacobi solver.</span></span>
<span id="cb1-11">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(maxiter):</span>
<span id="cb1-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Jacobi iteration.</span></span>
<span id="cb1-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, nx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb1-14">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ny<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb1-15">                tmp[i, j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>(b[i, j]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j] </span>
<span id="cb1-16">                                                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i, j<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i, j<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span>
<span id="cb1-17"></span>
<span id="cb1-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Compute residual</span></span>
<span id="cb1-19">        residual <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(u<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span>tmp)</span>
<span id="cb1-20">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Update solution.</span></span>
<span id="cb1-21">        u <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> tmp</span>
<span id="cb1-22">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># If converged, exit the loop.</span></span>
<span id="cb1-23">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> residual <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> tol:</span>
<span id="cb1-24">            <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">break</span></span>
<span id="cb1-25"></span>
<span id="cb1-26">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> u</span></code></pre></div>
<p>Yes, you shouldn’t do <code>for</code> loops in <code>Python</code>. But remember, you are not a seasoned programmer. You’re taking your first class on scientific computing and that’s how the Jacobi method is typically presented. Be forgiving.</p>
<section id="where-do-students-struggle" class="level3">
<h3 class="anchored" data-anchor-id="where-do-students-struggle">Where do students struggle?</h3>
<p>Admittidely, the code is quite readable and look very similar to the pseudocode you’d use to describe the Jacobi method. But if you’re reading this blog post, there probably are a handful of things you’ve internalized and don’t even think about anymore (true for both <code>Python</code> and <code>Fortran</code>). And that’s precisely what the students (at least mine) struggle with, starting with the very first line.</p>
<p><strong>What the hell is <code>numpy</code> and why do I need it? Also, why import it as <code>np</code>? –</strong> These questions come back every year. Yet, I don’t have satisfying answers. I always hesitate between</p>
<blockquote class="blockquote">
<p>Trust me kid, you don’t want to use nested lists in <code>Python</code> to do any serious numerical computing.</p>
</blockquote>
<p>which naturally begs the question of why, or</p>
<blockquote class="blockquote">
<p>When I said we’ll use <code>Python</code> for this scientific computing class, what I really meant is we’ll use <code>numpy</code> which is a package written for numerical computing because <code>Python</code> doesn’t naturally have good capabilities for number crunching. As for the import as <code>np</code>, that’s just a convention.</p>
</blockquote>
<p>And this naturally leads to the question of “<em>why Python in the first place then?</em>” for which the only valid answer I have is</p>
<blockquote class="blockquote">
<p>Well, because <code>Python</code> is supposed to be easy to learn and everybody uses it.</p>
</blockquote>
<p>Clearly, <code>import numpy as np</code> is an innocent-looking line of code. It has nothing to do with the subject being taught though, and everything with the choice of the language, only diverting the students from the learning process.</p>
<p><strong>I coded everything correctly, 100% sure, but I get this weird error message about indentation –</strong> Oh boy! What a classic! The error message varies between</p>
<pre><code>IndentationError: expected an indented block</code></pre>
<p>and</p>
<pre><code>TabError: inconsistent use of tabs and spaces in indentation</code></pre>
<p><code>&lt;TAB&gt;</code> versus <code>SPACE</code> is a surprisingly hot topic in programming which I don’t want to engage in. A seasoned programmer might say “<em>simply configure your IDE properly</em>” which is fair. But we’re talking about your average student (who’s not a CS one remember) and they might use IDLE or even just notepad. As for the <code>IndentationError</code>, it is a relatively easy error to catch. Yet, the fact that <code>for</code>, <code>if</code> or <code>while</code> constructs are not clearly delineated in <code>Python</code> other than visually is surprisingly hard for students. I find that it puts an additional cognitive burden on top of a subject which is already demanding enough.</p>
<p>It could also be more subtle. The code might run but the results are garbage because the student wrote something like</p>
<div class="sourceCode" id="cb4" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb4-1">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(maxiter):</span>
<span id="cb4-2">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Jacobi iteration.</span></span>
<span id="cb4-3">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, nx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb4-4">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, ny<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb4-5">    tmp[i, j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>(b[i, j]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>dx<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j] </span>
<span id="cb4-6">                                                <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i, j<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> u[i, j<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>])</span></code></pre></div>
<p>You might argue that this perfectly understandable, though if you want to be picky, there is no dealineation of where the different loops end. Which the whole point of indentation in <code>Python</code>. But students do not necessarily get that.</p>
<p><strong>Why <code>range(1, nx-1)</code> and not <code>range(2, nx-1)</code>? The first column/row is my boundary. –</strong> Another classic related to 0-based vs 1-based indexing. And another very hot debate I don’t want to engage in. The fact however is that linear algebra (and a lot of scientific computing for that matter) use 1-based indexing. Think about vectors or matrices. Almost every single maths books write them as</p>
<p><img src="https://latex.codecogs.com/png.latex?%0A%5Cbegin%7Bbmatrix%7D%0A%20%20%20%20a_%7B11%7D%20&amp;%20a_%7B12%7D%20&amp;%20a_%7B13%7D%20%5C%5C%0A%20%20%20%20a_%7B21%7D%20&amp;%20a_%7B22%7D%20&amp;%20a_%7B23%7D%20%5C%5C%0A%20%20%20%20a_%7B31%7D%20&amp;%20a_%7B32%7D%20&amp;%20a_%7B33%7D%0A%5Cend%7Bbmatrix%7D.%0A"></p>
<p>The upper left element has the (1, 1) index, not (0, 0). Why use a language with 0-based indexing for linear algebra other than putting an additional cognitive burden on the students learning the subject? This is a recipe for the nefarious off-by-one error. And these errors are sneaky. The code might run but produce incorrect results and it’s a nightmare for the students (or the poor TA helping them) to figure out why.</p>
<p><strong>Why <code>np.linalg.norm</code> and not just <code>norm</code> or <code>np.norm</code>? –</strong> This is one is related to my first point. When you’re used to it, you no longer question it. But you don’t know students then and, once more, I don’t have a really clear answer other than</p>
<blockquote class="blockquote">
<p>Well, <code>linalg</code> stand for linear algebra, and <code>np.linalg</code> is a collection of linear algebra related function. It is a submodule of <code>numpy</code>, the package I told you about before.</p>
</blockquote>
<p>Grouping like-minded functionalities into a dedicated submodule is definitely good practice, no question there. Discussing the architecture of <code>numpy</code> makes a lot of sense when students have to do a big project involving numerical computing but not strictly speaking about numerical computing. On the other hand, when it is their first numerical computing class (and possibly first with <code>Python</code>) I find it distracting. Again, it’s not a big thing really but still. And then you have to explain why <code>np.det</code> and <code>np.trace</code> are not part of <code>np.linalg</code>…</p>
<p><strong>Other common problems –</strong> There are other very common problems like using the wrong function or inconsistent use of lower- or upper-case for variables. Once you know <code>Python</code> is case-sensitive, this is mainly a concentration problem. No big deal there. But there is one last thing that tends to cause problems to distracted students and that has to do with the dynamic nature of <code>Python</code>. Nowhere in the code snippet is it clearly specified that <code>b</code> needs to be a two-dimensional <code>np.array</code> of real numbers nor that it shouldn’t be modified by the function. It is only implicit. And that can be a big problem for students when working with marginally more complicated algorithms. Sure enough, type annotation is a thing now in <code>Python</code>, but it still is pretty new and comparatively few people actually use them.</p>
</section>
<section id="what-about-fortran" class="level3">
<h3 class="anchored" data-anchor-id="what-about-fortran">What about <code>Fortran</code>?</h3>
<p>Alright, I’ve spent the last five minutes talking shit about <code>Python</code> but how does <code>Fortran</code> compare with it? Here is a typical implementation of the same function. I’ve actually digged it from my own set of archived homeworks I did 15+ years ago and hardly modified it.</p>
<div class="sourceCode" id="cb5" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb5-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> jacobi(b, dx, tol, maxiter) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(u)</span>
<span id="cb5-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb5-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b</span>
<span id="cb5-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> dx, tol</span>
<span id="cb5-5">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> maxiter</span>
<span id="cb5-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> u</span>
<span id="cb5-7">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables.</span></span>
<span id="cb5-8">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> tmp</span>
<span id="cb5-9">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> nx, ny, i, j, iteration</span>
<span id="cb5-10"></span>
<span id="cb5-11">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables.</span></span>
<span id="cb5-12">    nx <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) ; ny <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(b, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb5-13">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span>(u(nx, ny), source <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb5-14">    residual <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">1.0</span></span>
<span id="cb5-15"></span>
<span id="cb5-16">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi solver.</span></span>
<span id="cb5-17">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> iteration <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, maxiter</span>
<span id="cb5-18">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Jacobi iteration.</span></span>
<span id="cb5-19">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ny<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-20">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, nx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb5-21">                tmp(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&amp;</span></span>
<span id="cb5-22">                                                <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb5-23">            <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb5-24">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb5-25"></span>
<span id="cb5-26">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Compute residual.</span></span>
<span id="cb5-27">        residual <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> tmp)</span>
<span id="cb5-28">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Update solution.</span></span>
<span id="cb5-29">        u <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> tmp</span>
<span id="cb5-30">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! If convered, exit the loop.</span></span>
<span id="cb5-31">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">if</span> (residual <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">&lt;=</span> tol) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">exit</span></span>
<span id="cb5-32">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb5-33"></span>
<span id="cb5-34"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span></code></pre></div>
<p>No surprise there. The task is sufficiently simple that both implementations are equally readable. If anything, the <code>Fortran</code> one is a bit more verbose. But in view of what I’ve just said about the <code>Python</code> code, I think it actually a good thing. Let me explain.</p>
<p><strong>Definition of the variables –</strong> <code>Fortran</code> is a strongly typed language. Lines 2 to 8 are nothing but the definitions of the different variables used in the routine. While you might argue it’s a pain in the a** to write these, I think it can actually be very beneficial for students. Before even implementing the method, they have to clearly think about which variables are input, which are ouput, what are their types and dimensions. And to do so, they have to have at least a minimal understanding of the algorithm itself. Once it’s done, there are no more surprises (hopefully), and the contract between the code and the user is crystal clear. And more importantly, the effort put in clearly identifying the input and output of numerical algorithm usually pays off and leads to less error-prone process.</p>
<p><strong>Begining and end of the constructs –</strong> <code>Fortran</code> uses the <code>do</code>/<code>end do</code> (or <code>enddo</code>) construct, clearly specifying where the loop starts where it ends. The indentation used in the code snippet really is just a matter of style. In constrast to <code>Python</code>, writing</p>
<div class="sourceCode" id="cb6" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb6-1">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, ny<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>, nx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb6-3">    tmp(i, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.25</span><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>(b(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>dx<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">**</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, j) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">&amp;</span></span>
<span id="cb6-4">                                    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">+</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> u(i, j<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>))</span>
<span id="cb6-5">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb6-6">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span></code></pre></div>
<p>does not make the code any less readable and wouldn’t change a dime in terms of computations. It’s a minor thing, fair enough. But it instantly get rid of the <code>IndentationError</code> or <code>TabError</code> which are puzzling students. I may be wrong, but I believe it actually reduces the cognitive load associated with the programming language and let the students focus on the actual numerical linear algebra task.</p>
<p><strong>No off-by-one error –</strong> By default, <code>Fortran</code> uses a 1-based indexing. No off-by-one errors, period.</p>
<p><strong>Intrinsic functions for basic scientific computations –</strong> While you have to use <code>np.linalg.norm</code> in <code>Python</code> to compute the norm of a vector, <code>Fortran</code> natively has the <code>norm2</code> function for that. No external library required. If you want to be picky, you may say that <code>norm2</code> is a weird name and that <code>norm</code> might be just fine.</p>
<p><strong>Some quirks of <code>Fortran</code> –</strong> All is not perfect though, starting with Line 2 and the <code>implicit none</code> statement. This is a historical remnant which is considered good practice by modern <code>Fortran</code> standards but not actually needed. Students being students, they will more likely than not ask questions about it although it has nothing to do with the subject of the class itself. Admittidely, it can be a bit cumbersome to explicitely define all the integers you use even if it’s just for a one-time loop. Likewise, there is the question of <code>real</code> vs <code>double precision</code> vs <code>real(wp)</code> (where <code>wp</code> is yet another variable you’ve defined somewhere). I don’t think it matters too much though when learning the basics of numerical linear algebra algorithms, although it certainly does when you start discussing about precision and performances.</p>
</section>
</section>
<section id="linear-least-squares-your-first-step-into-machine-learning" class="level2">
<h2 class="anchored" data-anchor-id="linear-least-squares-your-first-step-into-machine-learning">Linear least-squares, your first step into Machine Learning</h2>
<p>Alright, let’s look at another example. Same class, later in the semester. Professor X now discusses over-determined linear systems and how it relates to least-squares, regression and basic machine learning applications. During the hands-on session, you’re given the following problem</p>
<blockquote class="blockquote">
<p>Consider the following unconstrained quadratic program <img src="https://latex.codecogs.com/png.latex?%5Cmathrm%7Bminimize%7D%20%5Cquad%20%5C%7C%20Ax%20-%20b%20%5C%7C_2%5E2."> Write a least-squares solver based on the QR factorization of the matrix <img src="https://latex.codecogs.com/png.latex?A">. You can safely assume that <img src="https://latex.codecogs.com/png.latex?A"> is a tall matrix (i.e.&nbsp;<img src="https://latex.codecogs.com/png.latex?m%20%3E%20n">).</p>
</blockquote>
<p>Here is what the typical <code>Python</code> code written by the students might look like.</p>
<div class="sourceCode" id="cb7" style="background: #f1f3f5;"><pre class="sourceCode numberSource python number-lines code-with-copy"><code class="sourceCode python"><span id="cb7-1"><span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">import</span> numpy <span class="im" style="color: #00769E;
background-color: null;
font-style: inherit;">as</span> np</span>
<span id="cb7-2"></span>
<span id="cb7-3"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> qr(A):</span>
<span id="cb7-4">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initialize variables.</span></span>
<span id="cb7-5">    m, n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> A.shape</span>
<span id="cb7-6">    Q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((m, n))</span>
<span id="cb7-7">    R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((n, n))</span>
<span id="cb7-8"></span>
<span id="cb7-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># QR factorization based on the Gram-Schmidt orthogonalization process.</span></span>
<span id="cb7-10">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n):</span>
<span id="cb7-11">        q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> A[:, i]</span>
<span id="cb7-12">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Orthogonalization w.r.t. to the previous basis vectors.</span></span>
<span id="cb7-13">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(i):</span>
<span id="cb7-14">            R[j, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.vdot(q, Q[:, j])</span>
<span id="cb7-15">            q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> R[j, i]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>Q[:, j]</span>
<span id="cb7-16"></span>
<span id="cb7-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Normalize and store the new vector.</span></span>
<span id="cb7-18">        R[i, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.linalg.norm(q)</span>
<span id="cb7-19">        Q[:, i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> q <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> R[i, i]</span>
<span id="cb7-20"></span>
<span id="cb7-21">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> Q, R</span>
<span id="cb7-22"></span>
<span id="cb7-23"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> upper_triangular_solve(R, b):</span>
<span id="cb7-24">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Initialize variables.</span></span>
<span id="cb7-25">    n <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> R.shape[<span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">0</span>]</span>
<span id="cb7-26">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> np.zeros((n))</span>
<span id="cb7-27"></span>
<span id="cb7-28">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Backsubstitution.</span></span>
<span id="cb7-29">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb7-30">        x[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> b[i]</span>
<span id="cb7-31">        <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">for</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">in</span> <span class="bu" style="color: null;
background-color: null;
font-style: inherit;">range</span>(n<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, i, <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>):</span>
<span id="cb7-32">            x[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">-</span> R[i, j]<span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">*</span>x[j]</span>
<span id="cb7-33">        x[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> x[i] <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">/</span> R[i, i]</span>
<span id="cb7-34"></span>
<span id="cb7-35">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x</span>
<span id="cb7-36"></span>
<span id="cb7-37"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">def</span> lstsq(A, b):</span>
<span id="cb7-38">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># QR factorization.</span></span>
<span id="cb7-39">    Q, R <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> qr(A)</span>
<span id="cb7-40">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;"># Solve R @ x = Q.T @ b.</span></span>
<span id="cb7-41">    x <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">=</span> upper_triangular_solve(R, Q.T <span class="op" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">@</span> b)</span>
<span id="cb7-42">    <span class="cf" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">return</span> x</span></code></pre></div>
<p>This one was adapted from an exercise I gave last year. In reality, students lumped everything into one big function unless told otherwise, but nevermind. For comparison, here is the equivalent <code>Fortran</code> code.</p>
<div class="sourceCode" id="cb8" style="background: #f1f3f5;"><pre class="sourceCode numberSource fortran number-lines code-with-copy"><code class="sourceCode fortranfixed"><span id="cb8-1"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">subroutine</span> qr(A, Q, R)</span>
<span id="cb8-2">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb8-3">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> A</span>
<span id="cb8-4">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(out)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> Q, R</span>
<span id="cb8-5">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables.</span></span>
<span id="cb8-6">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> i, j, m, n</span>
<span id="cb8-7">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> q_hat</span>
<span id="cb8-8"></span>
<span id="cb8-9">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables.</span></span>
<span id="cb8-10">    m <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(A, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>); n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(A, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">2</span>)</span>
<span id="cb8-11">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span>(Q(m, n), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb8-12">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span>(R(n, n), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb8-13">    </span>
<span id="cb8-14">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! QR factorization based on the Gram-Schmidt orthogonalization process.</span></span>
<span id="cb8-15">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, n</span>
<span id="cb8-16">        q_hat <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> A(:, i)</span>
<span id="cb8-17">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Orthogonalize w.r.t. the previous basis vectors.</span></span>
<span id="cb8-18">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, i<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-19">            R(j, i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">dot_product</span>(q_hat, Q(:, j))</span>
<span id="cb8-20">            q_hat <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> q_hat <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> R(j, i)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>Q(:, j)</span>
<span id="cb8-21">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb8-22"></span>
<span id="cb8-23">        <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Normalize and store the new vector.</span></span>
<span id="cb8-24">        R(i, i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> norm2(q_hat)</span>
<span id="cb8-25">        Q(:, i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> q_hat <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">/</span> R(i, i)</span>
<span id="cb8-26">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb8-27"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end subroutine</span></span>
<span id="cb8-28"></span>
<span id="cb8-29"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> upper_triangular_solve(R, b) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(x)</span>
<span id="cb8-30">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb8-31">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> R</span>
<span id="cb8-32">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b</span>
<span id="cb8-33">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> x</span>
<span id="cb8-34">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables.</span></span>
<span id="cb8-35">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">integer</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> n, i, j</span>
<span id="cb8-36"></span>
<span id="cb8-37">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Initialize variables.</span></span>
<span id="cb8-38">    n <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">size</span>(R, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>)</span>
<span id="cb8-39">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">allocate</span>(x(n), source<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span><span class="fl" style="color: #AD0000;
background-color: null;
font-style: inherit;">0.0</span>)</span>
<span id="cb8-40"></span>
<span id="cb8-41">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Backsubstitution.</span></span>
<span id="cb8-42">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> i <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> n, <span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-43">        x(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> b(i)</span>
<span id="cb8-44">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">do</span> j <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> n<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span>, i, <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span><span class="dv" style="color: #AD0000;
background-color: null;
font-style: inherit;">1</span></span>
<span id="cb8-45">            x(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> x(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">-</span> R(i, j)<span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">*</span>x(j)</span>
<span id="cb8-46">        <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">enddo</span></span>
<span id="cb8-47">        x(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> x(i) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">/</span> R(i, i)</span>
<span id="cb8-48">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end do</span></span>
<span id="cb8-49"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span>
<span id="cb8-50"></span>
<span id="cb8-51"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">function</span> lstsq(A, b) <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">result</span>(x)</span>
<span id="cb8-52">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">implicit</span> <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">none</span></span>
<span id="cb8-53">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> A</span>
<span id="cb8-54">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">intent(in)</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> b</span>
<span id="cb8-55">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> x</span>
<span id="cb8-56">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Internal variables.</span></span>
<span id="cb8-57">    <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">real</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">dimension(:, :)</span>, <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">allocatable</span> <span class="dt" style="color: #AD0000;
background-color: null;
font-style: inherit;">::</span> Q, R</span>
<span id="cb8-58"></span>
<span id="cb8-59">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! QR factorization.</span></span>
<span id="cb8-60">    <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">call</span> qr(A, Q, R)</span>
<span id="cb8-61">    <span class="co" style="color: #5E5E5E;
background-color: null;
font-style: inherit;">! Solve R @ x = Q.T @ b.</span></span>
<span id="cb8-62">    x <span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">=</span> upper_triangular_solve(R, <span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">matmul</span>(<span class="fu" style="color: #4758AB;
background-color: null;
font-style: inherit;">transpose</span>(Q), b))</span>
<span id="cb8-63"><span class="kw" style="color: #003B4F;
background-color: null;
font-weight: bold;
font-style: inherit;">end function</span></span></code></pre></div>
<p>Just like the Jacobi example, both implementations are equally readable. At this point in the semester, the students got somewhat more comfortable with <code>Python</code>. The classical indentation problems were not so much of a problem anymore. The off-by-one errors due to 0-based indexing for the Gram-Schmidt orthogonalization in <code>qr</code> or in the backsubstitution algorithm on the other hand… That was painful. In a 90-minutes class, it took almost a whole hour simply for them to debug these errors.</p>
<p>But there was another thing that confused students. A lot. And that has to do with computing dot products in <code>numpy</code>. There’s so many different ways: <code>np.vdot(x, y)</code>, <code>np.dot(x.T, y)</code>, <code>np.dot(np.transpose(x), y)</code>, or <code>x.transpose().dot(y)</code> to list just the ones I have seen in their codes. Again, this has nothing to do with linear algebra, but everything with the language. Not only do they need to learn the math, but they simultaneously need to learn the not-quite-necessarily-math-standard syntax used in the language (yes, I’m looking at you <code>@</code>). It’s just a question of habits, sure enough, but again it can be impeding the learning process.</p>
<p>On the other hand, the <code>Fortran</code> implementation is even closer to the standard mathematical description of the algorithm: 1-based indexing, intrinsic <code>dot_product</code> function, etc. But beside the <code>implicit none</code>, there is the need to use a <code>subroutine</code> rather than a <code>function</code> construct for the QR decomposition because it has two output variables. Not a big deal again, but to be fair, it does add another minor layer of abstraction due to the language semantics rather than that of the subject being studied.</p>
</section>
<section id="fortran-may-have-a-slight-edge-but-i-swept-some-things-under-the-rug" class="level2">
<h2 class="anchored" data-anchor-id="fortran-may-have-a-slight-edge-but-i-swept-some-things-under-the-rug"><code>Fortran</code> may have a slight edge, but I swept some things under the rug…</h2>
<p>In the end, when it comes to teaching the basics of numerical linear algebra, <code>Python</code> and <code>Fortran</code> are not that different. And in that regard, neither is <code>Julia</code> which I really like as well. The main advantages I see of using <code>Fortran</code> for this task however are:</p>
<ul>
<li><strong>1-based indexing</strong> : in my experience, the 0-based indexing in <code>Python</code> leads to so many off-by-one erros driving the students crazy. Because linear algebra textbooks naturally use 1-based indexing, having to translate everything in your head to 0-based indices is a huge cognitive burden on top of a subject already demanding enough. You might get used to it eventually, but it’s a painful process impeding the learning outcomes.</li>
<li><strong>Strong typing</strong> : combined with <code>implicit none</code>, having to declare the type, dimension and input or output nature of every variable you use might seem cumbersome at first. But it forces students to pause and ponder to identify which is which. Sure this is an effort, but it is worth it. Learning is not effortless and this effort forces you to have a somewhat better understanding of a numerical algorithm before even starting to implement it. Which I think is a good thing.</li>
<li><strong>Clear delineation of the constructs</strong> : at least during the first few weeks, having to rely only on visual clues to identify where does a loop ends in <code>Python</code> seems to be quite complicated for a non-negligible fraction of the students I have. In that respect, the <code>do</code>/<code>enddo</code> construct in <code>Fortran</code> is much more explicit and probably easier to grasp.</li>
</ul>
<p>Obvisouly, I’m not expecting educators worldwide to switch back to <code>Fortran</code> overnight, nor is it necessarily desirable. The advantages I see are non-negligible from my perspective but certainly not enough by themselves. There are many other things that need to be taken into account. <code>Python</code> is a very generalist language. You can do so much more than just numerical computing so it makes complete sense to have it in the classroom. The ecosystem is incredibly vast and the interactive nature definitely has its pros. Notebooks such as <code>Jupyter</code> can be incredible teaching tools (although they come with their own problems in term good coding practices). So are the <code>Pluto</code> notebooks in <code>Julia</code>.</p>
<p><code>Fortran</code> is good at one thing: enabling computational scientists and engineers to write high-performing mathematical models without all the intricacies of equally peformant but more CS-oriented languages such as <code>C</code> or <code>C++</code>. Sure enough, the modern <code>Fortran</code> ecosystem is orders of magnitude smaller than <code>Python</code>, and targetted toward numerical computing almost exclusively. And the <code>Julia</code> one is fairly impressive. But the community is working on it (see the <a href="https://fortran-lang.org/">fortran-lang website</a> or the <a href="https://fortran-lang.discourse.group/">Fortran discourse</a> if you don’t trust me). The bad rep of <code>Fortran</code> is unjustified, particularly for teaching purposes. Many of its detractors have hardly been exposed to anything else than <code>FORTRAN 77</code>. And it’s true that, by current standards, most of <code>FORTRAN 77</code> codes are terrible sphagetti codes making extensive use of implicit typing and incomprehensible <code>goto</code> statements. Even I, as a <code>Fortran</code> programmer, acknowledge it. But that’s no longer what <code>Fortran</code> is since the 1990’s, and certainly not today!</p>
<!--Include social share buttons-->
<!-- 
AddToAny check more: https://www.addtoany.com/buttons/for/website 
Using includes will make edits easier, will only need to add or remove button here if needed.
https://quarto.org/docs/authoring/includes.html
-->
<div class="a2a_kit a2a_kit_size_32 a2a_default_style">
<p><a class="a2a_dd" href="https://www.addtoany.com/share"></a> <a class="a2a_button_linkedin"></a> <a class="a2a_button_bluesky"></a> <a class="a2a_button_facebook"></a> <a class="a2a_button_copy_link"></a> <a class="a2a_button_email"></a></p>
</div>
<script async="" src="https://static.addtoany.com/menu/page.js"></script>


</section>

 ]]></description>
  <category>blog</category>
  <guid>https://loiseaujc.github.io/posts/blog-title/fortran_vs_python.html</guid>
  <pubDate>Mon, 08 Sep 2025 22:00:00 GMT</pubDate>
  <media:content url="https://upload.wikimedia.org/wikipedia/commons/thumb/0/07/Fortran_acs_cover.jpeg/250px-Fortran_acs_cover.jpeg" medium="image" type="image/jpeg"/>
</item>
<item>
  <title>PySINDy: A comprehensive Python package for robust sparse system identification</title>
  <dc:creator>Alan A. Kaptanoglu</dc:creator>
  <dc:creator>Brian M. de Silva</dc:creator>
  <dc:creator>Urban Fasel</dc:creator>
  <dc:creator>Kadierdan Kaheman</dc:creator>
  <dc:creator>Andy J. Goldschmidt</dc:creator>
  <dc:creator>Jared Callaham</dc:creator>
  <dc:creator>Charles B. Delahunt</dc:creator>
  <dc:creator>Zachary G. Nicolaou</dc:creator>
  <dc:creator>Kathleen Champion</dc:creator>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <dc:creator>J. Nathan Kutz</dc:creator>
  <dc:creator>Steven L. Brunton</dc:creator>
  <link>https://loiseaujc.github.io/posts/paper-title/pysindy.html</link>
  <description><![CDATA[ 




<!--Include academic icons or bottons-->
<!-- 
Load Academicons v1: https://jpswalsh.github.io/academicons/
-->
<p><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css"></p>
<p><link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"></p>
<!---
The following code are needed to show dimension citation and altmetrics.
https://api.altmetric.com/embeds.html
https://badge.dimensions.ai/
--->
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script>
<script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.plu.mx/widget-popup.js"></script>
<section id="abstract" class="level2">
<h2 class="anchored" data-anchor-id="abstract">Abstract</h2>
<p>Automated data-driven modeling, the process of directly discovering the governing equations of a system from data, is increasingly being used across the scientific community. <code>PySINDy</code> is a Python package that provides tools for applying the sparse identification of nonlinear dynamics (SINDy) approach to data-driven model discovery. In this major update to <code>PySINDy</code>, we implement several advanced features that enable the discovery of more general differential equations from noisy and limited data. The library of candidate terms is extended for the identification of actuated systems, partial differential equations (PDEs), and implicit differential equations. Robust formulations, including the integral form of SINDy, and ensembling techniques, are also implemented to improve performance for real-word data. Finally, we provide a range of new optimization algorithms, including several sparse regression techniques and algorithms to enforce and promote inequality constraints and stability. Together, these updates enable entirely new SINDy model discovery capabilities that have not been reported in the literature, such as constrained PDE identification and ensembling with different sparse regression optimizers.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<p>Published <a href="https://joss.theoj.org/papers/10.21105/joss.09623">paper</a></p>
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script><div class="altmetric-embed" data-badge-popover="right" data-badge-type="donut" data-doi="10.21105/joss.03994"></div>

<span class="__dimensions_badge_embed__" data-doi="10.21105/joss.03994" data-hide-zero-citations="true" data-style="small_circle"></span><script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@article{a._kaptanoglu2022,
  author = {A. Kaptanoglu, Alan and M. de Silva, Brian and Fasel, Urban
    and Kaheman, Kadierdan and J. Goldschmidt, Andy and Callaham, Jared
    and B. Delahunt, Charles and G. Nicolaou, Zachary and Champion,
    Kathleen and Loiseau, Jean-Christophe and Nathan Kutz, J. and L.
    Brunton, Steven},
  title = {PySINDy: {A} Comprehensive {Python} Package for Robust Sparse
    System Identification},
  journal = {Journal of Open Source Software},
  volume = {7},
  number = {69},
  pages = {3994},
  date = {2022-01-29},
  url = {https://joss.theoj.org/papers/10.21105/joss.03994},
  doi = {10.21105/joss.03994},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-a._kaptanoglu2022" class="csl-entry quarto-appendix-citeas">
A. Kaptanoglu, Alan, Brian M. de Silva, Urban Fasel, Kadierdan Kaheman,
Andy J. Goldschmidt, Jared Callaham, Charles B. Delahunt, et al. 2022.
<span>“PySINDy: A Comprehensive Python Package for Robust Sparse System
Identification.”</span> <em>Journal of Open Source Software</em> 7 (69):
3994. <a href="https://doi.org/10.21105/joss.03994">https://doi.org/10.21105/joss.03994</a>.
</div></div></section></div> ]]></description>
  <category>paper</category>
  <guid>https://loiseaujc.github.io/posts/paper-title/pysindy.html</guid>
  <pubDate>Fri, 28 Jan 2022 23:00:00 GMT</pubDate>
  <media:content url="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQTcztYR1fxZqI2zk6pNGRiiR7Q-FIJCIStOg2TwQh0FA&amp;s" medium="image"/>
</item>
<item>
  <title>Constrained sparse Galerkin regression</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <dc:creator>Steven L. Brunton</dc:creator>
  <link>https://loiseaujc.github.io/posts/paper-title/galerkin.html</link>
  <description><![CDATA[ 




<!--Include academic icons or bottons-->
<!-- 
Load Academicons v1: https://jpswalsh.github.io/academicons/
-->
<p><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css"></p>
<p><link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"></p>
<!---
The following code are needed to show dimension citation and altmetrics.
https://api.altmetric.com/embeds.html
https://badge.dimensions.ai/
--->
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script>
<script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.plu.mx/widget-popup.js"></script>
<section id="abstract" class="level2">
<h2 class="anchored" data-anchor-id="abstract">Abstract</h2>
<p>The sparse identification of nonlinear dynamics (SINDy) is a recently proposed data-driven modelling framework that uses sparse regression techniques to identify nonlinear low-order models. With the goal of low-order models of a fluid flow, we combine this approach with dimensionality reduction techniques (e.g.&nbsp;proper orthogonal decomposition) and extend it to enforce physical constraints in the regression, e.g.&nbsp;energy-preserving quadratic nonlinearities. The resulting models, hereafter referred to as Galerkin regression models, incorporate many beneficial aspects of Galerkin projection, but without the need for a high-fidelity solver to project the Navier-Stokes equations. Instead, the most parsimonious nonlinear model is determined that is consistent with observed measurement data and satisfies necessary constraints. Galerkin regression models also readily generalize to include higher-order nonlinear terms that model the effect of truncated modes. The effectiveness of such an approach is demonstrated on two canonical flow configurations: the two-dimensional flow past a circular cylinder and the shear-driven cavity flow. For both cases, the accuracy of the identified models compare favourably against reduced-order models obtained from a standard Galerkin projection procedure. Finally, the entire code base for ou constrained sparse Galerkin regression algorithm is freely available online.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<p>Published <a href="https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/constrained-sparse-galerkin-regression/0E18A4A55FF5AC1401D236C0E4D1CAAE">paper</a></p>
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script><div class="altmetric-embed" data-badge-popover="right" data-badge-type="donut" data-doi="10.1017/jfm.2017.823"></div>

<span class="__dimensions_badge_embed__" data-doi="10.1017/jfm.2017.823" data-hide-zero-citations="true" data-style="small_circle"></span><script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@article{loiseau2018,
  author = {Loiseau, Jean-Christophe and L. Brunton, Steven},
  title = {Constrained Sparse {Galerkin} Regression},
  journal = {Journal of Fluid Mechanics},
  volume = {67},
  pages = {42-67},
  date = {2018-03-10},
  url = {https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/constrained-sparse-galerkin-regression/0E18A4A55FF5AC1401D236C0E4D1CAAE},
  doi = {10.1017/jfm.2017.823},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-loiseau2018" class="csl-entry quarto-appendix-citeas">
Loiseau, Jean-Christophe, and Steven L. Brunton. 2018.
<span>“Constrained Sparse Galerkin Regression.”</span> <em>Journal of
Fluid Mechanics</em> 67 (March): 42–67. <a href="https://doi.org/10.1017/jfm.2017.823">https://doi.org/10.1017/jfm.2017.823</a>.
</div></div></section></div> ]]></description>
  <category>paper</category>
  <guid>https://loiseaujc.github.io/posts/paper-title/galerkin.html</guid>
  <pubDate>Tue, 09 Jan 2018 23:00:00 GMT</pubDate>
  <media:content url="https://static.cambridge.org/covers/FLM_0_838_0/cover.jpg?send-default-cover=false" medium="image"/>
</item>
<item>
  <title>Investigation of the roughness-induced transition: global stability analyses and direct numerical simulations</title>
  <dc:creator>Jean-Christophe Loiseau</dc:creator>
  <dc:creator>Jean-Christophe Robinet</dc:creator>
  <dc:creator>Stefania Cherubini</dc:creator>
  <dc:creator>Emmanuel Leriche</dc:creator>
  <link>https://loiseaujc.github.io/posts/paper-title/roughness.html</link>
  <description><![CDATA[ 




<!--Include academic icons or bottons-->
<!-- 
Load Academicons v1: https://jpswalsh.github.io/academicons/
-->
<p><link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/jpswalsh/academicons@1/css/academicons.min.css"></p>
<p><link rel="stylesheet" href="http://maxcdn.bootstrapcdn.com/font-awesome/4.3.0/css/font-awesome.min.css"></p>
<!---
The following code are needed to show dimension citation and altmetrics.
https://api.altmetric.com/embeds.html
https://badge.dimensions.ai/
--->
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script>
<script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>
<script type="text/javascript" src="//cdn.plu.mx/widget-popup.js"></script>
<section id="abstract" class="level2">
<h2 class="anchored" data-anchor-id="abstract">Abstract</h2>
<p>The linear global instability and resulting transition to turbulence induced by an isolated cylindrical roughness element of height <img src="https://latex.codecogs.com/png.latex?h"> and diameter <img src="https://latex.codecogs.com/png.latex?d"> immersed within an incompressible boundary layer flow along a flat plate is investigated using the joint application of direct numerical simulations and fully three-dimensional global stability analyses. For the range of parameters investigated, base flow computations show that the roughness element induces a wake composed of a central low-speed region surrounded by a three-dimensional shear layer and a pair of low- and high-speed streaks on each of its sides. Results from the global stability analyses highlight the unstable nature of the central low-speed region and its crucial importance in the laminar-turbulent transition process. It is able to sustain two different global instabilities: a sinuous and a varicose one. Each of these globally unstable modes is related to a different physical mechanism. While the varicose mode has its root in the instability of the whole three-dimensional shear layer surrounding the central low-speed region, the sinuous instability turns out to be similar to the von Kàrmàn instability in the two-dimensional cylinder wake and has its root in the lateral shear layers of the separated zone. The aspect ratio of the roughness element plays a key role in the selection of the dominant instability: whereas the flow over thin cylindrical roughness elements transitions due to a sinuous instability of the near-wake region, for larger roughness elements the varicose instability of the central low-speed region turns out to be the dominant one. Direct numerical simulations of the flow past an aspect ratio <img src="https://latex.codecogs.com/png.latex?%5Ceta%20=%201"> roughness element sustaining only the sinuous instability have revealed that the bifurcation occuring in this particular case is supercritical. Finally, comparison of the transition thresholds predicted by global linear stability analyses with von Doenhoff-Braslow transition diagram provides qualitatively good agreement.</p>
</section>
<section id="links" class="level2">
<h2 class="anchored" data-anchor-id="links">Links</h2>
<p>Published <a href="https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/constrained-sparse-galerkin-regression/0E18A4A55FF5AC1401D236C0E4D1CAAE">paper</a></p>
<script type="text/javascript" src="https://d1bxh8uas1mnw7.cloudfront.net/assets/embed.js"></script><div class="altmetric-embed" data-badge-popover="right" data-badge-type="donut" data-doi="10.1017/jfm.2014.589"></div>

<span class="__dimensions_badge_embed__" data-doi="10.1017/jfm.2014.589" data-hide-zero-citations="true" data-style="small_circle"></span><script async="" src="https://badge.dimensions.ai/badge.js" charset="utf-8"></script>


</section>

<div id="quarto-appendix" class="default"><section class="quarto-appendix-contents" id="quarto-citation"><h2 class="anchored quarto-appendix-heading">Citation</h2><div><div class="quarto-appendix-secondary-label">BibTeX citation:</div><pre class="sourceCode code-with-copy quarto-appendix-bibtex"><code class="sourceCode bibtex">@article{loiseau2014,
  author = {Loiseau, Jean-Christophe and Robinet, Jean-Christophe and
    Cherubini, Stefania and Leriche, Emmanuel},
  title = {Investigation of the Roughness-Induced Transition: Global
    Stability Analyses and Direct Numerical Simulations},
  journal = {Journal of Fluid Mechanics},
  volume = {760},
  pages = {175-211},
  date = {2014-11-10},
  url = {https://www.cambridge.org/core/journals/journal-of-fluid-mechanics/article/abs/investigation-of-the-roughnessinduced-transition-global-stability-analyses-and-direct-numerical-simulations},
  doi = {10.1017/jfm.2014.589},
  langid = {en}
}
</code></pre><div class="quarto-appendix-secondary-label">For attribution, please cite this work as:</div><div id="ref-loiseau2014" class="csl-entry quarto-appendix-citeas">
Loiseau, Jean-Christophe, Jean-Christophe Robinet, Stefania Cherubini,
and Emmanuel Leriche. 2014. <span>“Investigation of the
Roughness-Induced Transition: Global Stability Analyses and Direct
Numerical Simulations.”</span> <em>Journal of Fluid Mechanics</em> 760
(November): 175–211. <a href="https://doi.org/10.1017/jfm.2014.589">https://doi.org/10.1017/jfm.2014.589</a>.
</div></div></section></div> ]]></description>
  <category>paper</category>
  <guid>https://loiseaujc.github.io/posts/paper-title/roughness.html</guid>
  <pubDate>Mon, 03 Nov 2014 23:00:00 GMT</pubDate>
  <media:content url="https://static.cambridge.org/covers/FLM_0_760_0/cover.jpg?send-default-cover=false" medium="image"/>
</item>
</channel>
</rss>
