<?xml version="1.0" encoding="UTF-8"?>

<rss version="2.0"
  xmlns:content="http://purl.org/rss/1.0/modules/content/"
  xmlns:dc="http://purl.org/dc/elements/1.1/"
  xmlns:media="http://search.yahoo.com/mrss/"
  xmlns:atom="http://www.w3.org/2005/Atom"
  xmlns:georss="http://www.georss.org/georss">

  <channel>
    <title>
      <![CDATA[  Klafyvel  ]]>
    </title>
    <link> https://klafyvel.me </link>
    <description>
      <![CDATA[  Hugo Levy-Falk&#39;s web-page  ]]>
    </description>
    <atom:link
      href="https://klafyvel.me/feed.xml"
      rel="self"
      type="application/rss+xml" />


<item>
  <title>
    <![CDATA[  Building a proper archiving method for my things, episode 1  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/archiving-1/index.html </link>
  <guid> https://klafyvel.me/blog/articles/archiving-1/index.html </guid>
  <description>
    <![CDATA[  I&#39;m looking for a solution I like for archiving my things.  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  <p>That&#39;s a topic I&#39;ve been wanting to dive in for a long time. As I&#39;m getting older, I start to have accumulated some amount of files accross, often salvaged from device to device, that are somewhat dear to me. In a way, I want to archive properly my things for the same reasons <a href="https://bookshelf.thequinn.fr/quinns-journaling-guide">this blogpost by my friend cookie</a> convinced me to start journaling: later in time, I want to be able to remember how things were for me.</p>
<h1 id="where_do_i_start_from">Where do I start from?</h1>
<p>I&#39;ve got data scattered among multiple devices:</p>
<ul>
<li><p>My laptop &#39;ewilan&#39;,</p>
</li>
<li><p>My two external hard-drives, &#39;shae&#39; and, uh it does not have a name except &#39;TOSHIBA&#39;,</p>
</li>
<li><p>My server &#39;klafyvel.me&#39;, that has some various backups and hosts my emails,</p>
</li>
<li><p>My phone &#40;an android&#41;,</p>
</li>
<li><p>Some old broken android phone where I have pictures I&#39;d like to salvage someday,</p>
</li>
<li><p>an iPad I won at a hackathon that I do not use a lot, but still.</p>
</li>
</ul>
<p>Most of the things are on ewilan and the two hard drives, and those are what I&#39;ll focus on first. </p>
<h1 id="merging_the_two_drives">Merging the two drives</h1>
<p>Because of the limited available disk space on my laptop, I&#39;ve been chaotically moving stuff to those drives. In principle, things should be duplicated on these two... but I&#39;ve been doing it by hand, and it&#39;s a mess. I&#39;ve been doing some cleanup on the TOSHIBA hard drive, because I use it less than shae, and removed as many things I could &#40;movies I&#39;d already watched, useless old projects...&#41;.</p>
<p>Next, I needed to actually decide what to do with the remaining file: copying them if they were not already on shae, ignoring them otherwise. I am sure there are plenty of smart Linux commands to do that, but I&#39;m dumb and lazy, so I wrote <a href="">a Julia script</a> to do it the way I want. Essentially: </p>
<ul>
<li><p>it copies a file if the corresponding file does not exist on the target,</p>
</li>
<li><p>it does not copy a file whose path already exists on the target, but whose content is identical</p>
</li>
<li><p>it copies to a renamed path files that are redundant but different,</p>
</li>
<li><p>it outputs a CSV file that tells me what it did &#40;or plans to do if running in dry mode&#41;,</p>
</li>
<li><p>it can use the output of a dry run to actually move the files, which means I can have a look at what it&#39;s going to do before breaking everything.</p>
</li>
</ul>
<p>Thanks to <a href="https://github.com/timholy/ProgressMeter.jl">ProgressMeter.jl</a> I also have nice outputs.</p>
<pre><code class="language-bash">✓ Indexing /run/media/klafyvel/TOSHIBA EXT/Vidéos files...    Time: 0:00:01
◑ Processing files...    Time: 0:01:25
  decision:  keep
  file:      TPS/interviews/son/MONO-021.wav
  reason:    Same file at same location</code></pre>
<p>The process is quite long, so grab a book if you&#39;re gonna use the script. For my video folder, it took 42 minutes. The nice thing with having a CSV file outputed is I can have some statistics, who doesn&#39;t like statistics? And here you can see that indeed, I had a lot of duplicated stuff in my video folder:</p>
<pre><code class="language-julia">Row │ decision  count
     │ String7   Int64
─────┼─────────────────
   1 │ keep       2646
   2 │ copy          2
   3 │ ignore        1</code></pre>
 ]]>
  </content:encoded>
    
  <pubDate>Sat, 27 Jul 2024 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>

<item>
  <title>
    <![CDATA[  I am defending my PhD thesis on the 24/01&#33;  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/soutenance/en/index.html </link>
  <guid> https://klafyvel.me/blog/articles/soutenance/en/index.html </guid>
  <description>
    <![CDATA[  I am defending my PhD thesus on the 24/01&#33;  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  <p>I will be defending my PhD thesis on the 24ᵗʰ of January, at <strong>ENS Paris-Saclay</strong> in amphitheater <strong>1Z14</strong>.</p>
<p>The title is &quot;<em>Optical Spectroscopy of Graphene Quantum Dots and Halide Perovskite Nanocrystals</em>&quot;, and the defense will be in english. This work has been directed by <a href="http://tjoli.free.fr/emmanuelle-deleporte/">Emmanuelle Deleporte</a>, co-directed by <a href="https://www.lumin.universite-paris-saclay.fr/fr/node/30">Fabien Bretenaker</a>, co-advised by <a href="https://www.gemac.uvsq.fr/m-damien-garrot">Damien Garrot</a>, and in collaboration with <a href="https://www.lumin.universite-paris-saclay.fr/fr/node/32">Jean-Sébastien Lauret</a>.</p>
<p>To help me organize the event, <strong>can you fill this <a href="https://framadate.org/qYxCydi3MOi63Pnh">ce framadate</a> if you want to join the &quot;pot&quot; after the defense?</strong></p>
<hr />
<h1 id="on_this_page">On this page...</h1>
<div class="franklin-toc"><ol><li>On this page...</li><li>Abstract</li><li>How can I come?</li><li>Zoom link.</li><li>I have a question&#33;</li></ol></div>
<hr />
<h1 id="abstract">Abstract</h1>
<p>This work focuses on the optical spectroscopy of two classes of materials using fluorescence microscopy at room temperature.</p>
<p>First, halide perovskites, a class of semiconductors that have known a surge in interest in the last ten years because of their outstanding optoelectronic properties, making them a promising platform for photovoltaic applications, but also light emission in diodes, lasers, and quantum devices. These crystalline materials consist of corner-sharing octahedra with a metallic ion at the center, often lead, and halide ions at the corners: Cl, Br, or I. A cation completes the structure. It is either organic, for example, methylammonium &#40;MA&#41; or formamidinium, or inorganic, for example, cesium. In the context of light emission, halide perovskites are an excellent choice to address the problem of the green gap, that is, the lack of efficient emitters in the green region of the optical spectrum, because of the possibility to tune their band gap thanks to an informed choice of the halide during the synthesis. Moreover, because the synthesis is done at room temperature and involves soft chemistry steps, they are promising for industrial applications. The synthesis and characterization of CsPbBr₃ nanocrystals emitting in the optical spectrum&#39;s green region using a new reprecipitation-based method is reported. In particular, the nanocrystals&#39; high calibration and good stability are highlighted.</p>
<p>The second part of this study is about graphene quantum dots. Those low-dimensional objects allow the opening of the band gap of graphene, making them fluorescent. These emitters are promising because their atomically-thin structure and tunability make them suitable for realizing nano-sensors. Building on the recently studied structure-properties relationship of rod-shaped graphene quantum dots, a thorough single-molecule study of highly fluorescent graphene quantum dots with 96 \(sp^2\) carbon atoms is reported. The excellent purity of the samples was highlighted. The study of the time dynamics of those single-photon emitters in a polystyrene matrix allowed estimating the characteristic times of the transient dynamic of the quantum dots.</p>
<p>Finally, the third part reports the study of the graphene quantum dots on a perovskite surface. The surface of perovskites is of peculiar interest for the realization of devices with these semiconductors, making it an interesting playground to use graphene quantum dots. To that end, the quantum dots were deposited on a millimetric MAPbBr₃ single-crystal surface.</p>
<ul>
<li><p>As thin films deposited on the perovskite, the graphene quantum dots present photophysics compatible with the formation of excimers.</p>
</li>
<li><p>As the concentration of quantum dots on the surface is lowered, diffraction-limited spots are observed. The time-domain study of the photoluminescence reveals jumps between discrete states of the system.</p>
</li>
<li><p>The frequency-domain investigation of the intensity of photoluminescence of these diffraction-limited emitters is dominated by 1/f noise, which highly contrasts the stable, shot-noise-dominated dynamics of the single emitters when studied in a polystyrene matrix.</p>
</li>
</ul>
<h1 id="how_can_i_come">How can I come?</h1>
<ul>
<li><p>To come to ENS, see <a href="https://ens-paris-saclay.fr/lecole/venir-lecole">here</a>;</p>
</li>
<li><p>To find the amphitheater, see <a href="https://ens-paris-saclay.fr/sites/default/files/2020-09/Plan&#37;20ENS&#37;20Paris-Saclay.pdf">the plan</a>. The amphitheater is on floor no. 1 &#40;above the &quot;mezzanine&quot;&#41; in the north building.</p>
</li>
</ul>
<h1 id="zoom_link">Zoom link.</h1>
<p>A zoom link is available&#33; Send me a message if you want it.</p>
<h1 id="i_have_a_question">I have a question&#33;</h1>
<p>Send me an <a href="mailto:hugo.levy-falk@universite-apris-saclay.fr">email</a>&#33;</p>
 ]]>
  </content:encoded>
    
  <pubDate>Tue, 09 Jan 2024 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>

<item>
  <title>
    <![CDATA[  Je soutiens ma thèse le 24/01&#33;  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/soutenance/fr/index.html </link>
  <guid> https://klafyvel.me/blog/articles/soutenance/fr/index.html </guid>
  <description>
    <![CDATA[  Je soutiens ma thèse le 24/01&#33;  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  <p>Le <strong>24/01/2024</strong>, je soutiendrai ma thèse <strong>à l&#39;ENS Paris-Saclay</strong> dans l&#39;amphithéâtre <strong>1Z14</strong>.</p>
<p>La présentation s&#39;intitule &quot;<em>Optical Spectroscopy of Graphene Quantum Dots and Halide Perovskite Nanocrystals</em>&quot; et se fera en anglais. Ce travail de thèse a été réalisé sous la direction d&#39;<a href="http://tjoli.free.fr/emmanuelle-deleporte/">Emmanuelle Deleporte</a>, la co-direction de <a href="https://www.lumin.universite-paris-saclay.fr/fr/node/30">Fabien Bretenaker</a>, le co-encadrement de <a href="https://www.gemac.uvsq.fr/m-damien-garrot">Damien Garrot</a>, et en collaboration avec <a href="https://www.lumin.universite-paris-saclay.fr/fr/node/32">Jean-Sébastien Lauret</a>.</p>
<p>Afin de m&#39;aider à organiser la soutenance, <strong>pouvez-vous remplir <a href="https://framadate.org/qYxCydi3MOi63Pnh">ce framadate</a> si vous souhaitez venir au pot de thèse?</strong></p>
<p><a href="../en">English version is here.</a></p>
<hr />
<h1 id="sur_cette_page">Sur cette page</h1>
<div class="franklin-toc"><ol><li>Sur cette page</li><li>Résumé de la thèse</li><li>Comment venir ?</li><li>Suivre la présentation à distance.</li><li>Je n&#39;ai jamais assisté à une soutenance de thèse, ça se passe comment ?</li><li>J&#39;ai d&#39;autres questions &#33;</li></ol></div>
<hr />
<h1 id="résumé_de_la_thèse">Résumé de la thèse</h1>
<p>Ce travail se concentre sur la spectroscopie optique de deux classes de matériaux en utilisant la microscopie de fluorescence à température ambiante.</p>
<p>Tout d&#39;abord, les pérovskites halogénées, une classe de semi-conducteurs qui ont connu un regain d&#39;intérêt au cours des dix dernières années en raison de leurs propriétés optoélectroniques exceptionnelles, ce qui en fait une plate-forme prometteuse pour les applications photovoltaïques, mais aussi pour l&#39;émission de lumière dans les diodes, les lasers et les dispositifs quantiques. Ces matériaux cristallins sont constitués d&#39;octaèdres dont les sommets sont partagés. Un ion métallique est positionné au centre, souvent du plomb, et des ions halogénures aux sommets : Cl, Br ou I. Un cation complète la structure. Il est soit organique, par exemple le méthylammonium &#40;MA&#41; ou le formamidinium, soit inorganique, par exemple le césium. Dans le contexte de l&#39;émission de lumière, les pérovskites halogénées constituent un excellent choix pour résoudre le problème du <em>green gap</em>, c&#39;est-à-dire le manque d&#39;émetteurs efficaces dans la région verte du spectre optique, en raison de la possibilité d&#39;ajuster leur bande interdite grâce à un choix éclairé de l&#39;halogénure lors de la synthèse. De plus, comme la synthèse se fait à température ambiante et implique des étapes de chimie simples, ils sont prometteurs pour les applications industrielles. La synthèse et la caractérisation de nanocristaux de CsPbBr₃ émettant dans la région verte du spectre optique à l&#39;aide d&#39;une nouvelle méthode basée sur la précipitation est rapportée. En particulier, la calibration élevé et la bonne stabilité des nanocristaux sont mis en évidence.</p>
<p>La deuxième partie de cette étude porte sur les boîtes quantiques de graphène. Ces objets de faible dimension permettent d&#39;ouvrir la bande interdite du graphène, ce qui les rend fluorescents. Ces émetteurs sont prometteurs parce que leur structure atomiquement fine et leur accordabilité les rendent aptes à réaliser des nanocapteurs. En s&#39;appuyant sur la relation structure-propriétés récemment étudiée des boîtes quantiques de graphène rectangulaires, une étude approfondie au niveau de l&#39;objet unique de ces boîtes quantiques hautement fluorescentes avec 96 atomes de carbone \(sp^2\) est rapportée. L&#39;excellente pureté des échantillons a été mise en évidence. L&#39;étude de la dynamique temporelle de ces émetteurs de photons uniques dans une matrice de polystyrène a permis d&#39;estimer les temps caractéristiques de la dynamique transitoire des points quantiques.</p>
<p>Enfin, la troisième partie rapporte l&#39;étude des points quantiques de graphène sur une surface de pérovskite. La surface des pérovskites présente un intérêt particulier pour la réalisation de dispositifs avec ces semi-conducteurs, ce qui en fait un terrain de jeu intéressant pour l&#39;utilisation des boîtes quantiques de graphène. À cette fin, les boîtes quantiques ont été déposés sur la surface de monocristaux millimétriques de MAPbBr₃.</p>
<ul>
<li><p>En tant que films minces déposés sur la pérovskite, les boîtes quantiques de graphène présentent une photophysique compatible avec la formation d&#39;excimères.</p>
</li>
<li><p>Lorsque la concentration de boîtes quantiques sur la surface est réduite, des taches limitées par la diffraction sont observées. L&#39;étude de la photoluminescence dans le domaine temporel révèle des sauts entre des états discrets du système.</p>
</li>
<li><p>L&#39;étude dans le domaine des fréquences de l&#39;intensité de la photoluminescence de ces émetteurs limités par la diffraction est dominée par le bruit en 1/f, ce qui contraste fortement avec la dynamique stable, dominée par le bruit de grenaille, des émetteurs uniques lorsqu&#39;ils sont étudiés dans une matrice de polystyrène.</p>
</li>
</ul>
<h1 id="comment_venir">Comment venir ?</h1>
<ul>
<li><p>Pour se rendre à l&#39;ENS, sur le plateau de Saclay, voir <a href="https://ens-paris-saclay.fr/lecole/venir-lecole">ici</a>;</p>
</li>
<li><p>Pour trouver l&#39;amphithéâtre dans l&#39;école, on peut utiliser <a href="https://ens-paris-saclay.fr/sites/default/files/2020-09/Plan&#37;20ENS&#37;20Paris-Saclay.pdf">le plan</a>. L&#39;amphithéâtre se trouve au premier étage &#40;au dessus de l&#39;étage mezzanine&#41; dans le bâtiment nord.</p>
</li>
</ul>
<h1 id="suivre_la_présentation_à_distance">Suivre la présentation à distance.</h1>
<p>Il y a un lien zoom disponible&#33; Envoyez-moi un message si vous souhaitez suivre la présentation par zoom.</p>
<h1 id="je_nai_jamais_assisté_à_une_soutenance_de_thèse_ça_se_passe_comment">Je n&#39;ai jamais assisté à une soutenance de thèse, ça se passe comment ?</h1>
<p>La soutenance de thèse est un examen universitaire qui permet d&#39;accéder au grade de Docteur. En France, sauf exception, la soutenance est publique. </p><div class="message  is-link">
<div class="message-header">
<p> Question</p>
</div>
<div class="message-body">
  Comment se déroule la soutenance ? 
</div>
</div><p>La matinée devrait se passer comme ceci:</p>
<ul>
<li><p>9h30: Début de la soutenance. Présentation de mes travaux de thèse pendant environ 45 minutes,</p>
</li>
<li><p>10h30: Début des questions du jury, en commençant par les deux rapporteurs. Cette partie peut durer environ 1h30 à 2h,</p>
</li>
<li><p>12h~12h30: Le jury se retire pour délibérer, cela prend généralement une vingtaine de minutes,</p>
</li>
<li><p>Après la délibération: le président du jury annonce le résultat de l&#39;examen, et il y a un petit discours pour remercier les personnes impliquées dans la soutenance,</p>
</li>
<li><p>Et pour terminer, il y aura un pot ouvert à toutes et tous&#33;</p>
</li>
</ul><div class="message  is-link">
<div class="message-header">
<p> Question</p>
</div>
<div class="message-body">
  Qui compose le jury ? 
</div>
</div><p>Les membres du jury avec une voix délibérative incluent deux rapporteurs, un examinateur, et un président de jury. Le président est élu parmi les membres du jury avec une voix délibérative qui ne sont pas rapporteurs. Le jury est complété par les membres qui ne disposent pas de voix délibérative: mes encadrants et JS Lauret qui est invité.</p><div class="message  is-link">
<div class="message-header">
<p> Question</p>
</div>
<div class="message-body">
  Quand peut-on entrer/sortir de la salle ? 
</div>
</div><p>La soutenance doit commencer à 9h30, donc il vaut mieux arriver un peu avant. ;&#41;  Une partie du public peut sortir après la présentation et avant les questions pour revenir après la délibération s&#39;ils le souhaitent.</p>
<h1 id="jai_dautres_questions">J&#39;ai d&#39;autres questions &#33;</h1>
<p>Envoyez-moi un <a href="mailto:hugo.levy-falk@universite-paris-saclay.fr">mail</a>/message/SMS/pigeon voyageur, je répondrai au mieux &#33;</p>
 ]]>
  </content:encoded>
    
  <pubDate>Tue, 09 Jan 2024 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>

<item>
  <title>
    <![CDATA[  A nice approximation of the norm of a 2D vector.  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/approximate-euclidian-norm/index.html </link>
  <guid> https://klafyvel.me/blog/articles/approximate-euclidian-norm/index.html </guid>
  <description>
    <![CDATA[  A nice approximation of the norm of a 2D vector.  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  <p>While wandering on the internet, I stumbled upon <a href="http://www.azillionmonkeys.com/qed/sqroot.html#distance">Paul Hsieh&#39;s blog-post</a>, where he demonstrates a way to approximate the norm of a vector without any call to the <code>sqrt</code> function. Let&#39;s see if I can reproduce the steps to derive this.</p>
<hr />
<h1 id="table_of_contents">Table of contents</h1>
<div class="franklin-toc"><ol><li>Table of contents</li><li>Setting-up the scene.</li><li>Finding a lower bound to the norm.</li><li>Finding an upper bound to the norm.</li><li>Choosing the best approximation for the norm.</li><li>Conclusion</li></ol></div>
<hr />
<h1 id="setting-up_the_scene">Setting-up the scene.</h1>
<p>Calculating the norm of a vector \((x,y)\), or a complex number \(x+iy\) means calculating \(\sqrt{x^2+y^2}\). Without loss of generality, we can set \(\sqrt{x^2+y^2}=1\). If we draw this, we get the following.</p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration1.svg" alt="The &#40;x, y&#41; pairs with a euclidean norm of 1."> 
<figcaption> The (x, y) pairs with a euclidean norm of 1.</figcaption>
</figure><h1 id="finding_a_lower_bound_to_the_norm">Finding a lower bound to the norm.</h1>
<p>Now, the issue with the norm is that the \(\sqrt{}\) operation is expensive to compute. That&#39;s why we would like another way to approximate the norm. A first idea is to look at other norms available, indeed, what we have called &quot;norm&quot; so far is actually the 2-norm, also named <em>euclidean norm</em>. Let&#39;s have a look at two other norms : the infinity norm and the Manhattan norm.</p>
<p>Infinity norm is :</p>
\[
\lVert(x,y)\rVert_\infty = \max(x,y)
\]
<p>Manhattan norm is :</p>
\[
\lVert(x,y)\rVert_1 = |x|+|y|
\]
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration2.svg" alt="The &#40;x, y&#41; pairs with a euclidean norm of 1, an infinity norm of 1 or a Manhattan norm of 1."> 
<figcaption> The (x, y) pairs with a euclidean norm of 1, an infinity norm of 1 or a Manhattan norm of 1.</figcaption>
</figure><p>Now we see the Manhattan norm is indeed a lower bound for the 2-norm, even if it&#39;s rough. The Infinity norm, however, is too high. But that is not an issue, we could simply scale it up so that it is always higher than the 2-norm. The scaling factor is chosen, such as the yellow curve tangent to the circle. For that, we need it to be equal to \(\cos\frac{\pi}{4}=\frac{1}{\sqrt{2}}\).</p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration3.svg" alt="We now have a nice lower bound of the euclidean norm&#33;"> 
<figcaption> We now have a nice lower bound of the euclidean norm!</figcaption>
</figure><p>We have a lower bound&#33; By choosing the closest to the circle between the yellow and green curves, we get an octagon that is very close to the circle. We can define the upper bound of the circle with a function \(f\) such as:</p>
\[
f(x,y) = \max\left(\max(x,y), \frac{1}{\sqrt{2}}(|x|+|y|)\right)
\]
<p>Note that this is different from Paul&#39;s article. You <strong>do</strong> need to take the maximum value of the two norms to select the points that are closest to the center. Generally speaking, for two norms, if one&#39;s value is higher than the other, then the former will be drawn closer to the origin when plotting the \(\text{norm}(x,y)=1\) curve.</p>
<p>To trace this function, note that Manhattan and infinity norms isolines cross when \(|y|=1\) and \(|x| = \sqrt{2}-1\) or \(|x|=1\) and \(|y| = \sqrt{2}-1\).</p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration4.svg" alt="The lower bound of the norm outlined."> 
<figcaption> The lower bound of the norm outlined.</figcaption>
</figure><h1 id="finding_an_upper_bound_to_the_norm">Finding an upper bound to the norm.</h1>
<p>The first idea you can get from the lower bound we found is to scale it up so that the octagon corners touch the circle.</p>
<p>To do so, we need to find the 2-norm of one of the corners and divide \(f\) by it.</p>
<p>Let&#39;s take the one at \(x=1\), \(y=\sqrt{2}-1\). We have:</p>
\[
\begin{align}
\sqrt{x^2+y^2} &=& \sqrt{1 + \left(\sqrt{2}-1\right)^2}\\
&=& \sqrt{1 + 2 - 2\sqrt{2} + 1}\\
&=& \sqrt{4 - 2\sqrt{2}}
\end{align}
\]
<p>Thus, the upper-bound for the 2-norm with the octagon method is \(\sqrt{4 - 2\sqrt{2}}f(x,y)\):</p>
\[
f(x,y) \leq \sqrt{x^2+y^2} \leq \sqrt{4 - 2\sqrt{2}}f(x,y)
\]
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration5.svg" alt="The upper and lower bounds of the norm outlined."> 
<figcaption> The upper and lower bounds of the norm outlined.</figcaption>
</figure><h1 id="choosing_the_best_approximation_for_the_norm">Choosing the best approximation for the norm.</h1>
<p>Now, we could stick to Paul Hsieh&#39;s choice of taking the middle between the lower and the upper bounds, and it will probably be fine. But come on, let&#39;s see if it is the <em>best</em> choice. 😉</p>
<p>Formally, the problem is to find a number \(a\in[0,1]\) such as \(g\) defined as follows is the closest possible to the norm-2.</p>
\[
\begin{align}
g(x,y,a) &=& (1-a)f(x,y)+\frac{a}{\sqrt{4 - 2\sqrt{2}}}f(x,y)\\
&=& \left((1-a) + a\sqrt{4 - 2\sqrt{2}}\right)f(x,y)
\end{align}
\]
<p>Let&#39;s plot this function for various values of \(a\). To make things easier, I will &quot;unroll&quot; the circle, and plot the norms against \(\theta\), the angle between our vector and the \(x\) axis.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration6.svg" alt="Various possible approximations for the norm."> 
<figcaption> Various possible approximations for the norm.</figcaption>
</figure><p>As expected, we can continuously vary our approximation between the upper and lower bounds. Notice that these functions are periodic and even. We can thus focus on the first half period to minimize the error. The first half period is when the vector is at the first octagon vertices, starting from the \(x\) axis and circling anti-clockwise.</p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration7.svg" alt="Zooming in the part of the unit circle that is interesting for calculating the error."> 
<figcaption> Zooming in the part of the unit circle that is interesting for calculating the error.</figcaption>
</figure><p>To minimize the error with our approximation, we want to minimize the square error. That is:</p>
\[
\begin{align}
e(a) &=& \int_0^{\arctan\left(\sqrt{2}-1\right)}(g(x,y,a)-1)^2\text{d}\theta
\end{align}
\]
<p>Thankfully, the expression of \(f(x,y)\) and thus of \(g(x,y,a)\) should simplify a lot on the given interval. You can see on schematic above that on this interval we have, \(f(x,y)=max(|x|,|y|)=|x|=x=\cos\theta\). We can thus rewrite \(e(a)\) as follows.</p>
\[
\begin{align}
e(a) &=& \int_0^{\arctan\left(\sqrt{2}-1\right)}(g(x,y,a)-1)^2\text{d}\theta\\
&=& \int_0^{\arctan\left(\sqrt{2}-1\right)}\left(\left(1-a + a\sqrt{4-2\sqrt{2}}\right)\cos\theta-1\right)^2\text{d}\theta\\
&=& \int_0^{\arctan\left(\sqrt{2}-1\right)}\left(h(a)\cos\theta-1\right)^2\text{d}\theta
\end{align}
\]
<p>Where \(h(a)=\left(1-a + a\sqrt{4-2\sqrt{2}}\right)\) and \(\arctan\left(\sqrt{2}-1\right)=\frac{\pi}{8}\).</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration8.svg" alt="Square error against θ."> 
<figcaption> Square error against θ.</figcaption>
</figure><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration9.svg" alt="Sum square error against a."> 
<figcaption> Sum square error against a.</figcaption>
</figure><p>As we can see from these plots, there is a minimal error, and though 0.5 is a reasonable choice for \(a\), we can do slightly better around 0.3.</p>
<p>We can explicitly calculate \(e(a)\). Let \(h(a)=(1+a(A-1))\). We have</p>
\[
\begin{align}
e(a) &=& \int_0^{\pi/8}(h(a)\cos\theta-1)^2\text{d}\theta\\
&=&h^2(a)\int_0^{\pi/8}\cos^2\theta\text{d}\theta-2h(a)\int_0^{\pi/8}\cos\theta\text{d}\theta + \frac{\pi}{8}\\
&=& h^2(a)B-2h(a)\sin\frac{\pi}{8} + \frac{\pi}{8}
\end{align}\]
<p>Where \(B=\frac{\pi}{16}+\frac{1}{4\sqrt2}\). Thus, we look for the position of the minimum, that is where \(e'(a)=0\).</p>
\[
\begin{align}
0 &=& 2B(A-1)(1+a(A-1))-\sin\frac{\pi}{8}\\
0 &=& 2B(A-1)(1+a(A-1)) - \frac{A}{2\sqrt2}\\
a &=& \left(\frac{A}{2B\sqrt2}-1\right)\times\frac{1}{A-1}\\
a &\approx& 0.311
\end{align}
\]
<p>Not that far from 0.3&#33;</p><p>The maximum deviation from the result is then \(\max_\theta{|h(a)\cos\theta-1|}\). Looking for that maximum is like looking for the maximum of \(\left(h(a)\cos\theta-1\right)^2\). Long story short, the maxima can only occur on the boundaries of the allowed domain for \(\theta\), that is \(\theta=0\) or \(\theta=\pi/8\), meaning</p>
\[
\max_\theta{|h(a)\cos\theta-1|} = \max\left(h(a)-1, \left|h(a)\frac{\sqrt{2-\sqrt{2}}}{2}-1\right|\right)
\]
<p>With our choice for \(a\), we get \(h(a)\approx 1.026\), so the maximum deviation is 0.052. That is, we have at most a 5.3&#37; deviation from the norm-2&#33;</p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration10.svg" alt="Our best approximation for the euclidean norm, with the calculated maximum errors."> 
<figcaption> Our best approximation for the euclidean norm, with the calculated maximum errors.</figcaption>
</figure><h1 id="conclusion">Conclusion</h1>
<p>That was a fun Sunday project&#33; Originally this was intended to be included in a longer blog-post that is yet to be finished, but I figured it was interesting enough to have its own post. The take-home message being, you can approximate the Euclidean norm of a vector with:</p>
\[
\begin{align}
\text{norm}(x,y) &=& \frac{\sqrt{2-\sqrt{2}}}{\frac{\pi}{8}+\frac{1}{2\sqrt{2}}}\max\left(\max(|x|,|y|), \frac{1}{\sqrt{2}}(|x|+|y|)\right)\\
&\approx& 1.026\max\left(\max(|x|,|y|), \frac{1}{\sqrt{2}}(|x|+|y|)\right)
\end{align}
\]
<p>You&#39;ll get at most a 5.3&#37; error. This is a bit different from what&#39;s proposed on <a href="http://www.azillionmonkeys.com/qed/sqroot.html#distance">Paul Hsieh&#39;s blog-post</a>. Unless I made a mistake, there might be a typo on his blog&#33;</p>
<p>If you are interested in playing with the code used to generate the figures in this article, have a look at the <a href="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/notebook.jl">companion notebook</a>&#33;</p>
<p>As always, if you have any question, or want to add something to this post, you can leave me comment or ping me on <a href="https://twitter.com/klafyvel">Twitter</a> or <a href="https://mastodon.social/@klafyvel">Mastodon</a>.</p>
 ]]>
  </content:encoded>
    
  <pubDate>Sun, 30 Oct 2022 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>

<item>
  <title>
    <![CDATA[  How I over-engineered a Fast Fourier Transform for Arduino.  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/fft-arduino/index.html </link>
  <guid> https://klafyvel.me/blog/articles/fft-arduino/index.html </guid>
  <description>
    <![CDATA[  How I over-engineered a Fast Fourier Transform for Arduino.  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  <p>Everything began with me wanting to implement the Fast Fourier Transform &#40;FFT&#41; on my Arduino Uno for a side project. The first thing you do in such case is asked your favorite search engine for existing solutions. If <a href="https://www.google.com/search?client&#61;firefox-b-d&amp;q&#61;arduino&#43;FFT">you google &quot;arduino FFT&quot;</a> one of the first result will be related to this instructable: <a href="https://www.instructables.com/ApproxFFT-Fastest-FFT-Function-for-Arduino/"><em>ApproxFFT: The Fastest FFT Function for Arduino</em></a>. As you can imagine, this could only tickle my interest: there was an existing solution to my problem, and the title suggested that it was the fastest available&#33; And thus, on April 18ᵗʰ 2021,<sup id="fnref:date">[1]</sup> I started a journey that would bring me to write my own tutorial on implementing the FFT in Julia, learn AVR Assembly and write a blog post about it, about one year and a half later.</p>
<p>There is a <a href="https://github.com/Klafyvel/AVR-FFT">companion GitHub repository</a> where you can retrieve all the codes presented in this article.</p><div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  This is the long version of the story. If you are only interested in nice plots showing the speed and the accuracy of my proposed solution, please head to the dedicated instructable : <a href="https://www.instructables.com/Faster-Than-the-Fastest-FFT-for-Arduino/">Faster than the Fastest FFT for Arduino</a> &#33; 
</div>
</div><p><table class="fndef" id="fndef:date">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">Yes, <a href="https://support.mozilla.org/en-US/questions/937585#answer-369869">I went through my Firefox history database</a> to find this date.</td>
    </tr>
</table>
 <hr /></p>
<h1 id="table_of_contents">Table of contents</h1>
<div class="franklin-toc"><ol><li>Table of contents</li><li>Why reinvent the wheel?<ol><li>Because I did not know how to implement the FFT.</li><li>Because I thought it was possible to do better.<ol><li>In-place or out-of-place algorithm?</li></ol></li><li>Trigonometry can be <em>blazingly fast</em>. 🚀🚀🚀 🔥🔥</li></ol></li><li>Interlude: some tooling for debugging.<ol><li>Using <code>arduino-cli</code> to upload your code.</li><li>Don&#39;t bother with communication protocols over Serial.</li></ol></li><li>Fast, accurate FFT, and other floating-point trickeries.<ol><li>A first dummy implementation of the FFT.</li><li>Forbidden occult arts are fun. 😈</li><li>Approximate floating-point FFT.</li></ol></li><li>How fixed-point arithmetic came to the rescue.<ol><li>Fixed-point multiplication.</li><li>Controlled result growth.</li><li>Trigonometry is demanding.</li><li>Saturating additions. &#40;a.k.a. &quot;Trigonometry is demanding&quot; returns.&#41;</li><li>Calculating modules with a chainsaw.</li><li>16 bits fixed-point FFT.</li><li>8 bits fixed-point FFT.</li><li>Implementing fixed-point FFT for longer inputs</li></ol></li><li>Benchmarking all these solutions.</li><li>Closing thoughts.</li></ol></div>
<hr />
<h1 id="why_reinvent_the_wheel">Why reinvent the wheel?</h1>
<p>As I said in the introduction, I explicitly researched an implementation of the FFT because I did not want to implement my own. So what changed my mind ?</p>
<h2 id="because_i_did_not_know_how_to_implement_the_fft">Because I did not know how to implement the FFT.</h2>
<p>Let&#39;s start with the obvious: abhilash_patel&#39;s instructable is a <strong>Great</strong> instructable. It is part of a series of instructables on implementing the FFT on Arduino, and this is his fastest accurate implementation. The instructable does a great job at explaining the big ideas behind it, with not only appropriate, but also good-looking illustrations. That is why I decided to read his code, to be certain of my good understanding of it.</p>
<p>And that is the exact moment I entered an infinite spiral. Not because the code was bad, even though it could use some indenting, but because I did not understand how it achieves its purpose. To my own disappointment, I realized that maybe I did not know how to implement an FFT. Sure, I had my share of lectures on the Fourier Transform, and on the Fast Fourier Transform, but the lecturers only showed us how the FFT was an algorithm with a very nice complexity through its recursive definition. But what I was looking at did not even remotely look like what I expected to see.</p>
<p>So I did what seemed the most sensible thing to me at the time: I spent nights reading Wikipedia pages and obscure articles on 2000s looking website to understand how the FFT was <em>actually</em> implemented. </p>
<p>About one month later, on May 23ʳᵈ, I started writing a tutorial on zestedesavoir.com : <a href="https://zestedesavoir.com/tutoriels/3939/jouons-a-implementer-une-transformee-de-fourier-rapide/">&quot;Jouons à implémenter une transformée de Fourier rapide &#33;&quot;</a>,  a sloppy translation of which is also available on <a href="https://klafyvel.me/blog/articles/fft-julia/">my blog</a>. My goal here was to write down what I had learned throughout the month, and it helped me clarify the math behind the implementation. Today, I use it as a reference when I have doubts on the implementation. </p>
<p>With this newly acquired knowledge on FFT implementations, I was ready to have another look at @abhilash_patel&#39;s code.</p>
<h2 id="because_i_thought_it_was_possible_to_do_better">Because I thought it was possible to do better.</h2>
<p>As I said, I was now capable of understanding the code provided by @abhilash_patel. And there I found two low-hanging fruits:</p>
<ul>
<li><p>The program was weirdly mixing in-place and out-of-place algorithm,</p>
</li>
<li><p>The trigonometry computation was inefficient.</p>
</li>
</ul>
<p>Let me state more clearly what I mean here.</p>
<h3 id="in-place_or_out-of-place_algorithm">In-place or out-of-place algorithm?</h3>
<p>The FFT can either be implemented <em>in-place</em> or <em>out-of-place</em>. Implementing <em>out-of-place</em> of course allows you to keep the input data unchanged by the computation. However, the <em>in-place</em> algorithm offers several key advantages, the first, obvious, one being that it only requires the amount of space needed to store the input array.</p>
<p>This might not be obvious, but it also works for real-valued signals. Indeed, one might think that if you have an array of, say, <code>float</code> representing such a signal, its FFT would require twice the amount of space since the Fourier transform is complex-valued. The trick here is to use a key property of the Fourier transform : the Fourier transform of a real-valued signal, knowing the positive-frequencies part is enough. You can see the full explanation in my <a href="https://klafyvel.me/blog/articles/fft-julia/#the_special_case_of_a_real_signal">blog post on implementing the FFT in Julia</a>.</p>
<p>This would help me get an FFT implementation that can run on more than 256 data points on my Arduino Uno, which the original instructable implementation cannot.<sup id="fnref:sizerequirement">[2]</sup></p>
<table class="fndef" id="fndef:sizerequirement">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Even though the code used for the benchmark cannot. This is not due to a memory size issue, but to the variable types I used for my buffers &#40;<code>uint8_t</code>&#41;. I think you can understand this would be easily fixed to run the FFT on bigger samples, and since I was especially interested in benchmarks in time, I allowed myself that.</td>
    </tr>
</table><h3 id="trigonometry_can_be_blazingly_fast">Trigonometry can be <em>blazingly fast</em>. 🚀🚀🚀 🔥🔥</h3>
<p>I believe this is where the biggest improvement in benchmark-time originates from. <a href="https://www.instructables.com/ApproxFFT-Fastest-FFT-Function-for-Arduino/#step2">Step 2 of the original instructable</a> details how to use a kind of look-up table to compute very quickly the trigonometry functions. This is an efficient method if you have to implement a fast cosine or a fast sine function. However, using such a method for the FFT means forgetting a very interesting property of the algorithm : the angles for which trigonometry calculations is required do not appear at random <strong>at all</strong>. In fact for each recursion step of the algorithm, they increase by a constant amount, and always start from the same angle : 0.</p>
<p>This arithmetical progression of the angle allows using a simple, yet efficient formula for calculating the next sine and cosine :</p>
\[\begin{aligned}\cos(\theta + \delta) &= \cos\theta - [\alpha \cos\theta +
\beta\sin\theta]\\\sin(\theta + \delta) &= \sin\theta - [\alpha\sin\theta -
\beta\cos\theta]\end{aligned}\]
<p>With \(\alpha = 2\sin^2\left(\frac{\delta}{2}\right),\;\beta=\sin\delta\).</p>
<p>I have included the derivation of these formulas in <a href="https://klafyvel.me/blog/articles/fft-julia/#optimization_of_trigonometric_functions">the relevant section of my tutorial</a>.</p>
<p>As I said, this is most likely the biggest source of improvement in execution time, as trigonometry computation-time instantaneously becomes negligible using this trick.</p>
<h1 id="interlude_some_tooling_for_debugging">Interlude: some tooling for debugging.</h1>
<p>I am a big fan of the <a href="https://julialang.org/">Julia programming language</a>. It is my main programming tool at work, and I also use it for my hobbies. However, I believe the tips given in this section are easily transportable to other programming languages.</p>
<p>The main idea here is that when you start working with arrays of data, good old <code>Serial.println</code> is not usable anymore. Because you cannot simply evaluate the correctness of your results at a simple glance, you want to use higher level tools, such as statistical analysis or plotting libraries. And since you are also likely to want to upload your code to the Arduino often, it is convenient to be able to upload it programmatically.</p>
<p>This machinery allows testing all the different implementations in a reproducible way. All the examples given in this article are calculated on the following input signal.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/test_signal.png" alt="Input signal used in the tests below."> 
<figcaption> Input signal used in the tests below.</figcaption>
</figure><h2 id="using_arduino-cli_to_upload_your_code">Using <code>arduino-cli</code> to upload your code.</h2>
<p>At the time I started this project, the <a href="https://docs.arduino.cc/software/ide-v2">new Arduino IDE</a>  wasn&#39;t available yet. If you have ever used the <code>1.x</code> versions of the IDE, then you know why one would like to avoid the old IDE. Thankfully, there is a command-line utility that allows uploading code from your terminal: <a href="https://arduino.github.io/arduino-cli/0.28/"><code>arduino-cli</code></a>. If you take a look at the GitHub <a href="https://github.com/Klafyvel/AVR-FFT">repository</a>, you&#39;ll notice a Julia script, which purpose is to upload code to the Arduino and retrieve the results of computations and benchmarks. The upload part is simply a system call to <code>arduino-cli</code>.</p>
<pre><code class="language-julia">function upload_code&#40;directory&#41;
    build &#61; joinpath&#40;workdir, directory, &quot;build&quot;&#41;
    ino &#61; joinpath&#40;workdir, directory, directory * &quot;.ino&quot;&#41;    build_command &#61; &#96;arduino-cli compile -b arduino:avr:uno -p &#36;portname --build-path &quot;&#36;build&quot; -u -v &quot;&#36;ino&quot;&#96;
    run&#40;pipeline&#40;build_command, stdout&#61;&quot;log_arduino-cli.txt&quot;, stderr&#61;&quot;log_arduino-cli.txt&quot;&#41;&#41;
end</code></pre>
<h2 id="dont_bother_with_communication_protocols_over_serial">Don&#39;t bother with communication protocols over Serial.</h2>
<p>At first, I was tempted to use some fancy communication protocols for the serial link. This is not useful in our case, because you can simply reset the Arduino programmatically to ensure the synchronization of the computer and the development board, and then exchange raw binary data.</p>
<p>Resetting is done <a href="https://stackoverflow.com/a/21082531">using the DTR pin of the port</a>.  In Julia, you can do this like this using the <a href="https://github.com/JuliaIO/LibSerialPort.jl"><code>LibSerialPort.jl</code></a> library:</p>
<pre><code class="language-julia">function reset_arduino&#40;&#41;
    LibSerialPort.open&#40;portname, baudrate&#41; do sp
        @info &quot;Resetting Arduino&quot;
        # Reset the Arduino
        set_flow_control&#40;sp, dtr&#61;SP_DTR_ON&#41;
        sleep&#40;0.1&#41;
        set_flow_control&#40;sp, dtr&#61;SP_DTR_OFF&#41;
        sp_flush&#40;sp, SP_BUF_INPUT&#41;
        sp_flush&#40;sp, SP_BUF_OUTPUT&#41;
    end
end</code></pre>
<p>Because your computer can now reset the Arduino at will, you can easily ensure the synchronization of your board. That means the benchmark script knows when to read data from the Arduino. </p>
<p>Then, the Arduino would send data to the computer like this:</p>
<pre><code class="language-cpp">Serial.write&#40;&#40;byte*&#41;data, sizeof&#40;fixed_t&#41;*N&#41;;</code></pre>
<p>This way, the array <code>data</code> is sent directly through the serial link as a stream of raw bytes. We don&#39;t bother with any form of encoding.</p>
<p>On the computer side, you can easily read the incoming data:</p>
<pre><code class="language-julia">data &#61; zeros&#40;retrieve_datatype, n_read&#41;
read&#33;&#40;sp, data&#41;</code></pre>
<p>Where <code>sp</code> is an object created by <code>LibSerialPort.jl</code> when opening a port.</p>
<p>You can then happily analyze your data, it&#39;s <a href="https://dataframes.juliadata.org/stable/"><code>DataFrames.jl</code></a> and <a href="https://docs.makie.org/stable/"><code>Makie.jl</code></a> time &#33;</p>
<h1 id="fast_accurate_fft_and_other_floating-point_trickeries">Fast, accurate FFT, and other floating-point trickeries.</h1>
<p>My first approach was to re-use as much as I could the code I wrote for my FFT tutorial in Julia. That&#39;s why I started working with floating-point arithmetic. This also was convenient because it kept away some issues like overflowing numbers, that I had to address once I started working with fixed-point arithmetic.</p>
<h2 id="a_first_dummy_implementation_of_the_fft">A first dummy implementation of the FFT.</h2>
<p>As I said, my first implementation was a simple, stupid translation of one of the codes presented in my Julia tutorial. I did not even bother with writing optimized trigonometry functions, I just wanted something that worked as a basis for other implementations. The code is fairly simple and can be viewed <a href="https://github.com/Klafyvel/AVR-FFT/blob/main/ExactFFT/ExactFFT.ino">here</a>.</p>
<p>As expected, this gives almost error-free results.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/results_ExactFFT.svg" alt="Module of approximate floating-point FFT on Arduino. Comparison with reference implementation."> 
<figcaption> Module of approximate floating-point FFT on Arduino. Comparison with reference implementation.</figcaption>
</figure><h2 id="forbidden_occult_arts_are_fun">Forbidden occult arts are fun. 😈</h2>
<p>Now let&#39;s move on to more interesting stuffs. The first obvious improvement you can make on the base implementation is fast trigonometry, and that&#39;s what yields the biggest improvement in terms of speed. Then, I decided to mess around with IEEE-754 to write my own approximate routines for float multiplication, halving and modulus calculation. The idea is always the same: treat IEEE-754 representation of a floating-point number as its logarithm. This does give <a href="https://github.com/Klafyvel/AVR-FFT/blob/72410901891639147376c9a900ef97132eb6e807/FloatFFT/FloatFFT.ino#L346-L376">weird-looking implementations</a> though. I have written several posts on Zeste-de-Savoir explaining how all these work. It is in French, but I trust you can make DeepL run&#33;</p>
<ul>
<li><p><a href="https://zestedesavoir.com/billets/4153/approximer-rapidement-le-carre-dun-nombre-flottant/">&quot;Approximer rapidement le carré d&#39;un nombre flottant&quot;</a> explains how to square a number using its floating-point representation.</p>
</li>
<li><p><a href="https://zestedesavoir.com/billets/4199/ieee-754-quand-votre-code-prend-la-float/">&quot;IEEE 754: Quand votre code prend la float&quot;</a> explains how the IEEE-754 representation of a number looks alike it&#39;s logarithm.</p>
</li>
<li><p><a href="https://zestedesavoir.com/billets/4226/multiplications-avec-arduino-jetons-nous-a-la-float/">&quot;Multiplications avec Arduino: jetons-nous à la float&quot;</a> explains how the approximate multiplication of two floating-point numbers can be efficiently calculated.</p>
</li>
</ul>
<h2 id="approximate_floating-point_fft">Approximate floating-point FFT.</h2>
<p>Without further delay, here is a sneak preview of the result I got with the approximate floating-point FFT. For a full benchmark, you will have to wait for the end of this article&#33; The code is available <a href="https://github.com/Klafyvel/AVR-FFT/blob/main/FloatFFT/FloatFFT.ino">here</a>.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/results_FloatFFT.svg" alt="Module of approximate floating-point FFT on Arduino. Comparison with reference implementation."> 
<figcaption> Module of approximate floating-point FFT on Arduino. Comparison with reference implementation.</figcaption>
</figure><h1 id="how_fixed-point_arithmetic_came_to_the_rescue">How fixed-point arithmetic came to the rescue.</h1>
<p>Rather than endlessly optimizing the floating-point implementation, I decided to change my approach. The main motivation being: <strong>Floats are actually overkill for our purpose</strong>. Indeed, they have the ability to represent numbers with a good relative precision over enormous ranges. However, when calculating FFTs the range output variables may cover can indeed vary, but not that much. And most importantly, it varies <strong>predictably</strong>. This means a <strong>fixed-point</strong> representation can be used. Also, because of their amazing properties Floats actually take a lot of space in the limited RAM available on a microcontroller. And finally, I want to be able to run FFTs on signal read from Arduino&#39;s ADC. If my program can deal with <code>int</code>-like data types, then it&#39;ll spare me the trouble of converting from integers to floating-points.</p>
<h2 id="fixed-point_multiplication">Fixed-point multiplication.</h2>
<p>I first played with the idea of implementing a fixed-point FFT because I realized the <a href="http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf#_OPENTOPIC_TOC_PROCESSING_d94e3581">AVR instruction set</a> gives us the <code>fmul</code> instruction, dedicated to multiplying fixed-point numbers. This means we can use it to have a speed-efficient implementation of the multiplication, that should even beat the custom <code>float</code> one.</p>
<p>I wrote a <a href="https://zestedesavoir.com/billets/4258/en-periode-de-canicule-une-idee-fixe-economiser-la-float/">blog-post</a> on Zeste-de-Savoir &#40;in French&#41; on implementing the fixed-point multiplication. It is based on the proposed implementation in the AVR instruction set manual.</p>
<pre><code class="language-cpp">/* Signed fractional multiply of two 16-bit numbers with 32-bit result. */
fixed_t fixed_mul&#40;fixed_t a, fixed_t b&#41; &#123;
  fixed_t result;
  asm &#40;
      // We need a register that&#39;s always zero
      &quot;clr r2&quot; &quot;\n\t&quot;
      &quot;fmuls &#37;B&#91;a&#93;,&#37;B&#91;b&#93;&quot; &quot;\n\t&quot; // Multiply the MSBs
      &quot;movw &#37;A&#91;result&#93;,__tmp_reg__&quot; &quot;\n\t&quot; // Save the result
      &quot;fmul &#37;A&#91;a&#93;,&#37;A&#91;b&#93;&quot; &quot;\n\t&quot; // Multiply the LSBs
      &quot;adc &#37;A&#91;result&#93;,r2&quot; &quot;\n\t&quot; // Do not forget the carry
      &quot;movw r18,__tmp_reg__&quot; &quot;\n\t&quot; // The result of the LSBs multipliplication is stored in temporary registers
      &quot;fmulsu &#37;B&#91;a&#93;,&#37;A&#91;b&#93;&quot; &quot;\n\t&quot; // First crossed product
                                  // This will be reported onto the MSBs of the temporary registers and the LSBs
                                  // of the result registers. So the carry goes to the result&#39;s MSB.
      &quot;sbc &#37;B&#91;result&#93;,r2&quot; &quot;\n\t&quot;
      // Now we sum the cross product
      &quot;add r19,__tmp_reg__&quot; &quot;\n\t&quot;
      &quot;adc &#37;A&#91;result&#93;,__zero_reg__&quot; &quot;\n\t&quot;
      &quot;adc &#37;B&#91;result&#93;,r2&quot; &quot;\n\t&quot;
      &quot;fmulsu &#37;B&#91;b&#93;,&#37;A&#91;a&#93;&quot; &quot;\n\t&quot; // Second cross product, same as first.
      &quot;sbc &#37;B&#91;result&#93;,r2&quot; &quot;\n\t&quot;
      &quot;add r19,__tmp_reg__&quot; &quot;\n\t&quot;
      &quot;adc &#37;A&#91;result&#93;,__zero_reg__&quot; &quot;\n\t&quot;
      &quot;adc &#37;B&#91;result&#93;,r2&quot; &quot;\n\t&quot;
      &quot;clr __zero_reg__&quot; &quot;\n\t&quot;
      :
      &#91;result&#93;&quot;&#43;r&quot;&#40;result&#41;:
      &#91;a&#93;&quot;a&quot;&#40;a&#41;,&#91;b&#93;&quot;a&quot;&#40;b&#41;:
      &quot;r2&quot;,&quot;r18&quot;,&quot;r19&quot;
  &#41;;
  return result;
&#125;</code></pre>
<p>Obviously, you can also create the same function for 8-bits fixed-point arithmetic.</p>
<pre><code class="language-cpp">fixed8_t fixed_mul_8_8&#40;fixed8_t a, fixed8_t b&#41; &#123;
  fixed8_t result;  asm &#40;
    &quot;fmuls &#37;&#91;a&#93;,&#37;&#91;b&#93;&quot; &quot;\n\t&quot;
    &quot;mov &#37;&#91;result&#93;,__zero_reg__&quot; &quot;\n\t&quot;
    &quot;clr __zero_reg__&quot; &quot;\n\t&quot;
    :
    &#91;result&#93;&quot;&#43;r&quot;&#40;result&#41;:
    &#91;a&#93;&quot;a&quot;&#40;a&#41;,&#91;b&#93;&quot;a&quot;&#40;b&#41;
  &#41;;
  return result;
&#125;</code></pre>
<p>As you can see, this requires writing some assembly code because the <code>fmul</code> instruction is not directly accessible from C. However, even though it is fairly simple, this limits the implementation to AVR platforms. You might still get some reasonably efficient code by implementing everything in pure C, and extend the implementation to other platforms.</p>
<h2 id="controlled_result_growth">Controlled result growth.</h2>
<p>As I said before, the FFT grows predictably. First, we can see that the final Fourier transform is bounded. Recall that the FFT is an algorithm to compute the Discrete Fourier Transform &#40;DFT&#41;, which is written:</p>
\[\begin{aligned}
X[k] &=& \sum_{n=0}^{N-1}x[n]e^{-2i\pi nk/N}
\end{aligned}\]
<p>Where \(X\) is the discrete Fourier transform of the input signal \(x\) of size \(N\). From that we have:</p>
\[\begin{aligned}
|X[k]| &\leq \left|\sum_{n=0}^{N-1}x[n]e^{-2i\pi nk/N}\right|\\
&\leq \sum_{n=0}^{N-1}\left|x[n]e^{-2i\pi nk/N}\right| \\
&\leq \sum_{n=0}^{N-1}\left|x[n]\right|\\
&\leq N\times\max_n|x[n]|
\end{aligned}\]
<p>In our case, because we use the <code>Q0f7</code> fixed point format, the input signal \(x\) is in the range \([-1,1]\). That means the components of the DFT are within range \([-N,N]\). Note that these bounds are attained for some signals, <em>e.g.</em> a constant input.</p>
<p>With that, we know how to scale the result of the FFT so that it can be stored. But what about the intermediary steps ? How do we ensure that the intermediary values stay within range? You may recall from <a href="https://klafyvel.me/blog/articles/fft-julia/#analysis_of_the_first_implementation">the blog post explaining  FFT</a> this kind of &quot;butterfly&quot; diagrams:</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/radix2_inv.png" alt="Butterfly diagram of an FFT on 8 points input signal. Each column represents a 
  step in the algorithm, and each line is a case of the array. The various
  polygons identify cases that are part of the same subdivision of the array,
  and the arrows show how we combine them to go the next step of the
  algorithm."> 
<figcaption> Butterfly diagram of an FFT on 8 points input signal. Each column represents a 
  step in the algorithm, and each line is a case of the array. The various
  polygons identify cases that are part of the same subdivision of the array,
  and the arrows show how we combine them to go the next step of the
  algorithm.</figcaption>
</figure><p>This diagram also shows you that each step of the algorithm actually performs some FFTs on input signals of smaller sizes. That means our bounding rule applies for intermediary signals, given that we plug the right size of input signal in the formula&#33; Notice how at each step, corresponding sub-FFTs have a size of \(2^{i}\), where \(i\) is the number of the step, starting at 0. That basically means that if we scale down the signal between each step by dividing it by a factor of two, we will keep the signal bounded in \([-1,1]\) at each step&#33;</p>
<p>Note that this does not mean we get the optimal scale for every input signal. For example, signals which are poorly periodic would have a lot of low module Fourier coefficients, and would not fully take advantage of the scale offered by our representation. I did some tests scaling the array only when it was needed, and did not notice many changes in terms of execution times, so that&#39;s something you might want to explore if your project requires it.</p>
<h2 id="trigonometry_is_demanding">Trigonometry is demanding.</h2>
<blockquote>
<p>If all you have is a hammer, everything looks like a nail.</p>
<p>~ <a href="https://en.wikipedia.org/wiki/Law_of_the_instrument#Abraham_Maslow">Abraham Maslow</a></p>
</blockquote>
<p>Once I had fixed-point arithmetic working, I started wanting to use it everywhere. But I quickly encountered an issue: trigonometry stopped working.</p>
<p>The reason is simple, 8-bits precision is not enough for trigonometry calculations when we approach the small angles. The key point here, is that the precision needed for fixed-point calculation of trigonometry functions depends on the size of the input array. Recall from section Trigonometry can be blazingly fast. 🚀🚀🚀 🔥🔥 that we need to precompute values for \(\alpha\) and \(\beta\), where</p>
\[\alpha = 2\sin^2\left(\frac{\delta}{2}\right),\quad\beta=\sin\delta\]
<p>And \(\delta\) is the angle increment by which we want to increase the angle of the complex number we are summing with in the FFT. This angle depends on \(N\), the total length of the input array, and is equal to \(\frac{2\pi}{N}\). That means we need to be able to represent at least \(2\sin^2\frac{\pi}{N}\) for trigonometry to work. For \(N=256\), this is approximately equal to \(0.000301\). Unfortunately, the lowest number one can represent using <code>Q0f7</code> fixed point representation, that is with 7 bits in the fractional part, is \(2^{-7}=0.0078125\). That is why even for the 8 bit fixed point FFT, trigonometry calculations are performed using 16 bits fixed point arithmetic.</p>
<p>This limit on trigonometry also explains why the code presented here is not usable &quot;as is&quot; for very long arrays. Indeed, while 512 cases-long arrays could be handled using 16-bits trigonometry, the theoretical limit for an Arduino Uno would be 1024 cases-long arrays &#40;because RAM is 2048 bytes, and we need some space for temporary variables&#41;, and that would require 32-bits trigonometry, which I did not implement.</p>
<h2 id="saturating_additions_aka_trigonometry_is_demanding_returns">Saturating additions. &#40;a.k.a. &quot;Trigonometry is demanding&quot; returns.&#41;</h2>
<p>One other issue with trigonometry I did not see coming is its sensitivity to overflow. Since there is basically no protection against it, overflowing a fixed-point representation of a number flips the sign. In the case of trigonometry this is especially annoying, because that means we add a \(\pi\) phase error for even the slightest error when values are close to one. And trust me, it took me some time to understand where the error was coming from. </p>
<p>To mitigate this, I had to implement my own addition, that saturates to one instead of flipping the sign when overflow happens. The trick here is to use the status register &#40;<code>SREG</code>&#41; of the microcontroller to detect overflow. Again this requires doing the addition in assembly, as the check needs to happen right after the addition was performed, and there is no way to tell what the compiler might do between the addition and the actual check. </p>
<p>Checking overflow is done using the <a href="http://ww1.microchip.com/downloads/en/devicedoc/atmel-0856-avr-instruction-set-manual.pdf#_OPENTOPIC_TOC_PROCESSING_d94e3581"><code>brvc</code></a> instruction &#40;<em>Branch if Overflow Cleared</em>&#41;, and the function for 16-bits saturating addition goes like this:</p>
<pre><code class="language-cpp">/* Fixed point addition with saturation to ±1. */
fixed_t fixed_add_saturate&#40;fixed_t a, fixed_t b&#41; &#123;
  fixed_t result;
  asm &#40;
      &quot;movw &#37;A&#91;result&#93;, &#37;A&#91;a&#93;&quot; &quot;\n\t&quot;
      &quot;add &#37;A&#91;result&#93;,&#37;A&#91;b&#93;&quot; &quot;\n\t&quot; 
      &quot;adc &#37;B&#91;result&#93;,&#37;B&#91;b&#93;&quot; &quot;\n\t&quot; 
      &quot;brvc fixed_add_saturate_goodbye&quot; &quot;\n\t&quot;
      &quot;subi &#37;B&#91;result&#93;, 0&quot; &quot;\n\t&quot;
      &quot;brmi fixed_add_saturate_plus_one&quot; &quot;\n\t&quot;
      &quot;fixed_add_saturate_minus_one:&quot; &quot;\n\t&quot; 
      &quot;ldi &#37;B&#91;result&#93;,0x80&quot; &quot;\n\t&quot;
      &quot;ldi &#37;A&#91;result&#93;,0x00&quot; &quot;\n\t&quot;
      &quot;jmp fixed_add_saturate_goodbye&quot; &quot;\n\t&quot;
      &quot;fixed_add_saturate_plus_one:&quot; &quot;\n\t&quot;
      &quot;ldi &#37;B&#91;result&#93;,0x7f&quot; &quot;\n\t&quot;
      &quot;ldi &#37;A&#91;result&#93;,0xff&quot; &quot;\n\t&quot;
      &quot;fixed_add_saturate_goodbye:&quot; &quot;\n\t&quot;
      :
      &#91;result&#93;&quot;&#43;d&quot;&#40;result&#41;:
      &#91;a&#93;&quot;r&quot;&#40;a&#41;,&#91;b&#93;&quot;r&quot;&#40;b&#41;
  &#41;;  return result;
&#125;</code></pre>
<p>One might be tempted to use this routine for every single addition performed in the program. This is actually useless, since additions in the actual FFT algorithm will not overflow thanks to scaling, if they are done in a sensible order &#40;check the code if you want to see how&#33;&#41;.</p>
<h2 id="calculating_modules_with_a_chainsaw">Calculating modules with a chainsaw.</h2>
<p>After a lot of wandering on the Internets, I ended up using <a href="http://www.azillionmonkeys.com/qed/sqroot.html#distance">Paul Hsieh&#39;s technique for computing approximate modules of vectors</a>. However, while writing this article I discovered some mistakes and things that could be improved in his article, so I ended up writing <a href="https://klafyvel.me/blog/articles/approximate-euclidian-norm/">a dedicated article on this</a>, showing how you can minimize the mean square error, and get at most a 5.3&#37; error.</p>
<p>The main idea is that you can approximate the unit circle using a set of well-chosen octagons. That reminds me of what a rough cylinder carved using a chainsaw might look like, hence the name of this section.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/approximate-euclidian-norm/code/output/illustration5.svg" alt="One of the figures of the article on approximating the norm. Look at how this look like something carved using a chainsaw&#33;"> 
<figcaption> One of the figures of the article on approximating the norm. Look at how this look like something carved using a chainsaw!</figcaption>
</figure><h2 id="16_bits_fixed-point_fft">16 bits fixed-point FFT.</h2>
<p>Enough small talk, time for some action&#33; You can find <a href="https://github.com/Klafyvel/AVR-FFT/blob/main/Fixed16FFT/Fixed16FFT.ino">here</a> the code for 16-bits fixed-point FFT. The benchmark is available at the end of this article, but in the meantime here is the error comparison against reference implementation.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/results_Fixed16FFT.svg" alt="Calculated module of the Fourier transform of the input signal using
16-bits fixed-points arithmetic for various input signal lengths. Comparison with reference implementation."> 
<figcaption> Calculated module of the Fourier transform of the input signal using
16-bits fixed-points arithmetic for various input signal lengths. Comparison with reference implementation.</figcaption>
</figure><h2 id="8_bits_fixed-point_fft">8 bits fixed-point FFT.</h2>
<p>And now the fastest FFT on Arduino that I implemented, the 8-bits fixed-point FFT&#33; As for previous implementations, you can find the code <a href="https://github.com/Klafyvel/AVR-FFT/blob/main/Fixed8FFT/Fixed8FFT.ino">here</a>. Below is a comparison of the calculated module of the FFT against a reference implementation.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/results_Fixed8FFT.svg" alt="Calculated module of the Fourier transform of the input signal using
8-bits fixed-points arithmetic for various input signal lengths. Comparison with reference implementation."> 
<figcaption> Calculated module of the Fourier transform of the input signal using
8-bits fixed-points arithmetic for various input signal lengths. Comparison with reference implementation.</figcaption>
</figure><h2 id="implementing_fixed-point_fft_for_longer_inputs">Implementing fixed-point FFT for longer inputs</h2>
<p>The Arduino Uno has 2048 bytes of RAM. But because this implementation of the FFT needs an input array whose length is a power of two, and because you need some space for variables,<sup id="fnref:determined">[3]</sup> the limit would be a 1024 bytes long FFT. But the code presented here would have to be modified a bit &#40;not that much&#41;. From where I am standing I see two major issues:</p>
<ol>
<li><p>As discussed previously, trigonometry would need 32-bits arithmetic. That means you would need to implement the multiplication and saturating addition for those numbers.</p>
</li>
<li><p>The buffers are single bytes right now, so you would need to upgrade them to 16-bits buffers.</p>
</li>
</ol>
<p>Once those two issues, and the inevitable hundreds of other issues I did not think of are addressed, I don&#39;t see why one could not perform FFT on 1024 bytes-long input arrays.</p>
<table class="fndef" id="fndef:determined">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">Although I am sure a <em>very</em> determined person would be able to fit all the temporary variables in registers and calculate a 2048 bytes-long FFT. <strong>Do it, I vouch for you, you beautiful nerd&#33;</strong></td>
    </tr>
</table><h1 id="benchmarking_all_these_solutions">Benchmarking all these solutions.</h1>
<p>I won&#39;t go into the details of how I do the benchmarks here, it&#39;s basically just using the Arduino <code>micros&#40;&#41;</code> function. I present here only two benchmarks: how much time is required to run the FFT, and how &quot;bad&quot; the result is, measured with the <a href="https://en.wikipedia.org/wiki/Mean_squared_error">mean squared error</a>. Now, this is not the perfect way to measure the error made by the algorithm, so I do encourage you to have a look at the different comparison plots above. You will also notice that <code>ApproxFFT</code> seems to perform poorly in terms of error for small-sized input arrays. This is because it does not compute the result for frequency 0, so the error is probably over-estimated. Overall, I think it is safe to say that <code>ApproxFFT</code> and <code>Fixed16FFT</code> introduce the same amount of errors in the calculation. Notice how <code>ExactFFT</code> is <em>literally</em> billions times more precise than the other FFT algorithms. For 8-bits algorithms, the <a href="https://en.wikipedia.org/wiki/Quantization_&#40;signal_processing&#41;#Noise_and_error_characteristics">quantization</a> mean squared error is \({}^1/{}_3 LSB^2\approx2\times10^{-5}\), which means there are still sources of error introduced in the algorithm other than simple quantization. The same goes for <code>ApproxFFT</code> and <code>Fixed16FFT</code>, where the quantization error is approximately \(3\times10^{-10}\).</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/error_comparison.svg" alt="Mean-square error benchmark. The y-axis has a logarithmic scale, so you can see how much better &#96;ExactFFT&#96; performs&#33;"> 
<figcaption> Mean-square error benchmark. The y-axis has a logarithmic scale, so you can see how much better `ExactFFT` performs!</figcaption>
</figure><p>Execution time is where my implementations truly shine. Indeed, you can see that for 256 bytes-long input array, <code>Fixed8FFT</code> only needs about 12 ms to compute the FFT, when it takes 52ms for <code>ApproxFFT</code> to do the same. And if you need the same level of precision as what <code>ApproxFFT</code> offers, you can use <code>Fixed16FFT</code>, which only needs about 30ms to perform the computation. It&#39;s worth noticing that <code>FloatFFT</code> is not far behind, with only 67ms needed to compute the 256 bytes FFT. Of course Exact FFT takes much longer.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-arduino/execution_time_comparison.svg" alt="Execution time benchmark. &#96;Fixed8FFT&#96; is truly fast&#33;"> 
<figcaption> Execution time benchmark. `Fixed8FFT` is truly fast!</figcaption>
</figure><h1 id="closing_thoughts">Closing thoughts.</h1>
<p>It has been a fun journey&#33; I had a lot of fun and &quot;ha-ha&#33;&quot; moments when debugging all these implementations. As I wrote before, there are ways to improve them, either by making <code>Fixed8FFT</code> able to handle longer input arrays, or writing a custom-made addition for floating-point number to speed-up <code>FloatFFT</code>. I don&#39;t know if I will do it in the near future, as this whole project was just intended to be a small side-project, which ended-up bigger than expected. </p>
<p>As always, feel free to contact me if you need any further detail on this. You can join me on <a href="https://mastodon.social/@klafyvel">mastodon</a>, or on <a href="https://github.com/Klafyvel">GitHub</a>, or even through the comment section below&#33; In the meantime, have fun with your projects. :&#41;</p>
 ]]>
  </content:encoded>
    
  <pubDate>Sat, 15 Oct 2022 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>

<item>
  <title>
    <![CDATA[  Modeling a honeycomb grid in FreeCAD  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/freecad-honeycomb/index.html </link>
  <guid> https://klafyvel.me/blog/articles/freecad-honeycomb/index.html </guid>
  <description>
    <![CDATA[  A small tutorial on FreeCAD  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  
<div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  This was originally a <a href="https://twitter.com/klafyvel/status/1555128187964858368">Twitter thread</a>, but it is easier to read here. 
</div>
</div><p>Someone asked me how to make a honeycomb grid in @FreeCADNews. Here&#39;s how I do it, and bonus it&#39;s parametric&#33; ⬇️</p><figure style="text-align=center;">
<video controls>
    <source src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/animated.mp4"
            type="video/mp4">    Sorry, your browser doesn't support embedded videos.
</video>
<figcaption>A nicely animated plate with a honeycomb cut.</figcaption>
</figure><p>Let&#39;s start with a simple plate with four holes. I give a name to each dimension in the sketcher so that I can re-use them later.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-1.jpg" alt="Sketching the plate."> 
<figcaption> Sketching the plate.</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-2.jpg" alt="Extruding it."> 
<figcaption> Extruding it.</figcaption>
</figure>
</p>
<p>Then I create a new body and start sketching on the <code>XY</code> plane. For this example I wanted to constrain the hexagon side, so a bit of trigonometry is needed to get the width of each hexagon. I also decided here that the separation between hexagons would be about 2mm.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-3.jpg" alt="Sketching the first hexagon of the pattern"> 
<figcaption> Sketching the first hexagon of the pattern</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-4.jpg" alt="Extruding it."> 
<figcaption> Extruding it.</figcaption>
</figure>
</p>
<p>The two construction lines will serve as directions to which we repeat the hexagon. Notice how I also link the pad length of the new solid with the plate pad length. Then we head to the <code>Create MultiTransform</code> tool in Part Design, and start a first <code>LinearPattern</code>. We need it a bit longer than the width of the plate since we will duplicate the hexagons sideways. Any &quot;big&quot; number will do, but a bit of trigonometry gives me the exact length.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-5.jpg" alt="Using MultiTransform to expand the pattern to the right."> 
<figcaption> Using MultiTransform to expand the pattern to the right.</figcaption>
</figure><p>Then using another <code>LinearPattern</code> I can complete the line of hexagons. Since our pattern is symmetric I could also have used a symmetry tool. As before I use one of the construction lines for the direction of the pattern.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-6.jpg" alt="Expanding the pattern to the left."> 
<figcaption> Expanding the pattern to the left.</figcaption>
</figure><p>Now I do the other direction&#33; Using another <code>LinearPattern</code>, the second construction line, and a bit of trigonometry &#40;again&#41;.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-7.jpg" alt="Expanding the pattern to the top."> 
<figcaption> Expanding the pattern to the top.</figcaption>
</figure><p>The number of occurrences is given by <code>Length / &lt;&lt;Sketch001&gt;&gt;.hexagon_sep</code> . Freecad will round that to the nearest integer, if you&#39;re not happy with that, you can mess around with ceil and floor. Then, once again I can complete the pattern.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-8.jpg" alt="Expanding the pattern to the bottom."> 
<figcaption> Expanding the pattern to the bottom.</figcaption>
</figure><p>Let&#39;s create another body using the sketcher. It will represent the area where I want the honeycomb pattern to be present. I can re-use the dimensions I set for the base plate using their name.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-9.jpg" alt="Sketching the area where the honeycomb pattern will be cut."> 
<figcaption> Sketching the area where the honeycomb pattern will be cut.</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-10.jpg" alt="Extruding it."> 
<figcaption> Extruding it.</figcaption>
</figure>
</p>
<p>One body remaining&#33; We want some of the hexagons to be full. So let&#39;s create a body representing these. It re-uses the dimensions of the first hexagon.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-11.png" alt="Sketching an hexagon looking exactly like the first one."> 
<figcaption> Sketching an hexagon looking exactly like the first one.</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-12.jpg" alt="Extruding it."> 
<figcaption> Extruding it.</figcaption>
</figure>
</p>
<p>Now I want to repeat the body a certain amount of time to fill some of the hexagons. Once again MultiTransform is our friend.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-13.jpg" alt="Expanding the new hexagon pattern to the right..."> 
<figcaption> Expanding the new hexagon pattern to the right...</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-14.jpg" alt="... then to the left."> 
<figcaption> ... then to the left.</figcaption>
</figure>
</p>
<p>Notice that I used the dimension from the honeycomb pattern to match the correct positions of the hexagon. Also, everything being parametric, I can simply change the number of hexagons by setting the <code>Occurrences</code> parameter of <code>LinearPatter004</code>. At this stage, I have four bodies. I named them <code>main_plate</code>, <code>hexagons</code>, <code>allowed_cut_zone</code> and <code>text_zone</code>. Let&#39;s combine them cleverly using boolean operations&#33;</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-15.jpg" alt="&#96;main_plate&#96;"> 
<figcaption> `main_plate`</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-16.jpg" alt="&#96;hexagons&#96;"> 
<figcaption> `hexagons`</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-17.jpg" alt="&#96;allowed_cut&#96;"> 
<figcaption> `allowed_cut`</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-18.jpg" alt="&#96;text_zone&#96;"> 
<figcaption> `text_zone`</figcaption>
</figure>
</p>
<p>First, let&#39;s remove the text zone from the allowed cut, using <code>PartDesign</code>&#39;s boolean operation.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-19.jpg" alt="Combining &#96;allowed_cut&#96; and &#96;text_zone&#96;."> 
<figcaption> Combining `allowed_cut` and `text_zone`.</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-20.jpg" alt="Resulting geometry."> 
<figcaption> Resulting geometry.</figcaption>
</figure>
</p>
<p>Then I can create the cut zone, which is the intersection between the allowed cut zone and the hexagons.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-21.jpg" alt="Combining the previous geometry with &#96;hexagons&#96;."> 
<figcaption> Combining the previous geometry with `hexagons`.</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-22.jpg" alt="This is the final pattern we want to cut from the original plate."> 
<figcaption> This is the final pattern we want to cut from the original plate.</figcaption>
</figure>
</p>
<p>Finally, I can do the cutting, by taking the difference between the base plate and the cut zone.</p>
<p>
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-23.jpg" alt="Combining the pattern with the original plate."> 
<figcaption> Combining the pattern with the original plate.</figcaption>
</figure>
 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-24.jpg" alt="Resulting cut plate."> 
<figcaption> Resulting cut plate.</figcaption>
</figure>
</p>
<p>I just need to add some text using the Draft workbench... whoops, the text zone is a bit too big, good thing that our model is parametric, so we can easily change its size. 😬</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-25.jpg" alt="What a messy boy I am."> 
<figcaption> What a messy boy I am.</figcaption>
</figure><p>And there you have it&#33;</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/freecad-honeycomb/image-26.jpg" alt="Our nice and clean result."> 
<figcaption> Our nice and clean result.</figcaption>
</figure><p>If you want to mess around with the model, it is available <a href="https://github.com/Klafyvel/FreeCad-Hexagon-showcase">here</a>.</p>
<p>Have fun&#33;</p>
 ]]>
  </content:encoded>
    
  <pubDate>Thu, 04 Aug 2022 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>

<item>
  <title>
    <![CDATA[  Let&#39;s play at implementing a fast Fourier transform&#33;  ]]>
  </title>
  <link> https://klafyvel.me/blog/articles/fft-julia/index.html </link>
  <guid> https://klafyvel.me/blog/articles/fft-julia/index.html </guid>
  <description>
    <![CDATA[  An implementation of the FFT using Julia&#33;  ]]>
  </description>  
  
  <content:encoded>
    <![CDATA[  <p>The Fourier transform is an essential tool in many fields, be it in Physics, Signal Processing, or Mathematics. The method that is probably the most known to calculate it numerically is called the <strong>FFT</strong> for <em>Fast Fourier Transform</em>. In this little tutorial, I propose to try to understand and implement this algorithm in an efficient way. I will use the language <a href="https://julialang.org/">Julia</a>, but it should be possible to follow using other languages such as Python or C. We will compare the results obtained with those given by <a href="https://github.com/JuliaMath/FFTW.jl">the Julia port of the FFTW library</a>.</p>
<p>This tutorial is intended for people who have already had the opportunity to encounter the Fourier transform, but who have not yet implemented it. It is largely based on the third edition of <a href="http://www.numerical.recipes/">Numerical Recipes</a><sup id="fnref:numerical">[1]</sup>, which I encourage you to consult: it is a gold mine.</p><div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  This content was originally publisher on <a href="https://zestedesavoir.com">zestedesavoir.com</a> in French. This is a quick translation &#40;using Deepl and a few manual modifications&#41;. If something seems off please tell me, as it is likely an error coming from the translation step. You can even <a href="https://github.com/Klafyvel/klafypage/issues/new/choose">open an issue</a> on Github, or <a href="https://github.com/Klafyvel/klafypage/compare">create a pull-request</a> to fix the issue &#33; 
</div>
</div>
<div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  This page used to be generated dynamically, but the benchmarks would break every so often because of that. It is now generated statically. The current page was generated with the following julia setup:</p>
<pre><code class="language-julia-repl">julia&gt; versioninfo&#40;&#41;
Julia Version 1.10.4
Commit 48d4fd48430 &#40;2024-06-04 10:41 UTC&#41;
Build Info:
  Official https://julialang.org/ release
Platform Info:
  OS: Linux &#40;x86_64-linux-gnu&#41;
  CPU: 4 × Intel&#40;R&#41; Core&#40;TM&#41; i7-6600U CPU @ 2.60GHz
  WORD_SIZE: 64
  LIBM: libopenlibm
  LLVM: libLLVM-15.0.7 &#40;ORCJIT, skylake&#41;
Threads: 1 default, 0 interactive, 1 GC &#40;on 4 virtual cores&#41;
Environment:
  LD_PRELOAD &#61; /usr/lib64/libstdc&#43;&#43;.so.6
pkg&gt; st
Status &#96;/tmp/jl_AEhNcq/Project.toml&#96;
  &#91;6e4b80f9&#93; BenchmarkTools v1.5.0
  &#91;13f3f980&#93; CairoMakie v0.12.4
  &#91;7a1cc6ca&#93; FFTW v1.8.0
  &#91;65edfddc&#93; SixelTerm v1.3.0</code></pre>
<p>The full code for this article is available <a href="https://klafyvel.me/blog/articles/fft-julia/code.jl">here</a>. 
</div>
</div><p><table class="fndef" id="fndef:numerical">
    <tr>
        <td class="fndef-backref">[1]</td>
        <td class="fndef-content">William H. Press, Saul A. Teukolsky, William T. Vetterling, &amp; Brian P. Flannery. &#40;2007&#41;. Numerical Recipes 3rd Edition: The Art of Scientific Computing &#40;3rd ed.&#41;. Cambridge University Press.</td>
    </tr>
</table>
 <hr /></p>
<h1 id="table_of_contents">Table of contents</h1>
<div class="franklin-toc"><ol><li>Table of contents</li><li>Some reminders on the discrete Fourier transform<ol><li>The Fourier transform</li><li>From the Fourier transform to the discrete Fourier transform</li><li>Calculating the discrete Fourier transform</li><li>Why a fast Fourier transform algorithm?</li></ol></li><li>Implementing the FFT<ol><li>My first FFT</li><li>Analysis of the first implementation</li><li>Calculate the reverse permutation of the bits</li><li>My second FFT</li><li>The special case of a real signal<ol><li>Property 1: Compute the Fourier transform of two real functions at the same time</li></ol></li><li>Property 2 : Compute the Fourier transform of a single function</li><li>Calculation in place</li><li>An FFT for the reals</li><li>Optimization of trigonometric functions</li></ol></li></ol></div>
<hr />
<h1 id="some_reminders_on_the_discrete_fourier_transform">Some reminders on the discrete Fourier transform</h1>
<p>The discrete Fourier transform is a transformation that follows from the Fourier transform and is, as its name indicates, adapted for discrete signals. In this first part I propose to discover how to build the discrete Fourier transform and then understand why the fast Fourier transform is useful.</p>
<h2 id="the_fourier_transform">The Fourier transform</h2>
<p>This tutorial is not intended to present the Fourier transform. However, there are several <a href="https://fr.wikipedia.org/wiki/Transformation_de_Fourier">definitions of the Fourier transform</a> and even within a single domain, several are sometimes used. We will use the following: for a function \(f\), its Fourier transform \(\hat{f}\) is defined by:</p>
\[
\hat{f}(\nu) = \int_{-\infty}^{+\infty}f(x)e^{-2i\nu x}\text{d}x
\]
<h2 id="from_the_fourier_transform_to_the_discrete_fourier_transform">From the Fourier transform to the discrete Fourier transform</h2>
<p>As defined in the previous section, the Fourier transform of a signal is a continuous function of the variable \(\nu\). However, to represent any signal, we can only use a finite number of values. To do this we proceed in four steps:</p>
<ol>
<li><p>We <strong>sample</strong> &#40;or discretize&#41; the signal to analyze. This means that instead of working on the function that associates the value of the signal with the variable \(x\), we will work on a discrete series of values of the signal. In the case of the FFT, we sample with a constant step. For example if we look at a temporal signal like the value of a voltage read on a voltmeter, we could record the value at each <em>tic</em> of a watch.</p>
</li>
<li><p>We <strong>window</strong> the discretized signal. This means that we keep only a finite number of points of the signal.</p>
</li>
<li><p>We sample the Fourier transform of the signal to obtain the discrete Fourier transform.</p>
</li>
<li><p>We window the discrete Fourier transform for storage.</p>
</li>
</ol>
<p>I suggest you to reason on a toy signal which will have the shape of a Gaussian. This makes the reasoning a little simpler because the Fourier transform of a real Gaussian is also a real Gaussian<sup id="fnref:gaussian">[2]</sup>, which simplifies the graphical representations. </p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/signal.svg" alt="The signal which will be used as an example"> 
<figcaption> The signal which will be used as an example</figcaption>
</figure><p>More formally, we have:</p>
\[
f(x) = e^{-x^2},\;\hat{f}(\nu)=\sqrt{\pi}e^{-(\pi\nu)^2}
\]
<p>Let&#39;s first look at the sampling. Mathematically, we can represent the process by the multiplication of the signal \(f\) by a Dirac comb of period \(T\), \(ш_T\). The Dirac comb is defined as follows:</p>
\[
ш_T(x) = \sum_{k=-\infty}^{+\infty}\delta(x-kT)
\]
<p>With \(\delta\) the <a href="https://fr.wikipedia.org/wiki/Distribution_de_Dirac">Dirac distribution</a>. Here is the plot that we can obtain if we represent \(f\) and \(g=ш_T\times f\) together:</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/signal_ech.svg" alt="The signal and the sampled signal."> 
<figcaption> The signal and the sampled signal.</figcaption>
</figure><p>The Fourier transform of the new \(g\) function is written <sup id="fnref:math">[3]</sup> :</p>
\[
\begin{aligned}
\hat{g}(\nu) &= \int_{-\infty}^{+\infty} \sum_{k=-\infty}^{+\infty} \delta(x-kT)
f(x) e^{-2i\pi x \nu} \text{d}x \\
&= \sum_{k=-\infty}^{+\infty}\int_{-\infty}^{+\infty}\delta(x-kT) f(x) e^{-2i\pi
x \nu}\text{d}x \\
&= \sum_{k=-\infty}^{+\infty}f(kT)e^{-2i\pi kT\nu}
\end{aligned}
\]
<p>If we put \(f[k]=f(kT)\) the sampled signal and \(\nu_{text{ech}} = \frac{1}{T}\) the sampling frequency, we have:</p>
\[
\hat{g} = \sum_{k=-\infty}^{+\infty}f[k]e^{-2i\pi k\frac{\nu}{\nu_{\text{ech}}}}
\]
<p>If we plot the Fourier transform of the starting signal \(\hat{f}\) and that of the sampled signal \(\hat{g}\), we obtain the following plot:</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/tf_signal_ech.svg" alt="Fourier transform of the signal and its sampled signal"> 
<figcaption> Fourier transform of the signal and its sampled signal</figcaption>
</figure>
<div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  We notice that the sampling of the signal has led to the periodization of its Fourier transform. This leads to an important property in signal processing: the <a href="https://en.wikipedia.org/wiki/Nyquist&#37;E2&#37;80&#37;93Shannon_sampling_theorem">Nyquist-Shanon criterion</a>, and one of its consequences, spectrum aliasing. I let you consult the Wikipedia article about this if you are interested, but you can have a quick idea of what happens if you draw the previous plot with a too large sampling: the bells of the sampled signal transform overlap. 
<figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/tf_signal_ech_aliasing.svg" alt="Fourier transform of the signal and its sampled signal, illustrating
aliasing."> 
<figcaption> Fourier transform of the signal and its sampled signal, illustrating
aliasing.</figcaption>
</figure>
 
</div>
</div><p>We can then look at the windowing process. There are several methods that each have their advantages, but we will focus here only on the rectangular window. The principle is simple: we only look at the values of \(f\) for \(x\) between \(-x_0\) and \(+x_0\). This means that we multiply the function \(f\) by a gate function \(\Pi_{x_0}\) which verifies:</p>
\[
\Pi_{x_0}(x) =  \begin{aligned}
1 & \;\text{if}\; x\in[-x_0,x_0] \\
0 & \;\text{else}
\end{aligned} 
\]
<p>Graphically, here is how we could represent \(h\) and \(f\) together.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/signal_ech_fen.svg" alt="Signal sampled and windowed"> 
<figcaption> Signal sampled and windowed</figcaption>
</figure><p>Concretely, this is equivalent to limiting the sum of the Dirac comb to a finite number of terms. We can then write the Fourier transform of \(h=Pi_{x_0} \times ш_T \times f\) :</p>
\[
\hat{h}(\nu) = \sum_{k=-k_0}^{+k_0}f[k]e^{-2i\pi k\frac{\nu}{\nu_{\text{ech}}}}
\]<div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  The choice of windowing is not at all trivial, and can lead to unexpected problems if ignored. Here again I advise you to consult <a href="https://en.wikipedia.org/wiki/Window_function">the associated Wikipedia article</a> if needed. 
</div>
</div><p>We can now proceed to the last step: sampling the Fourier transform. Indeed, we can only store a finite number of values on our computer and, as defined, the function \(\hat{h}\) is continuous. We already know that it is periodic, with period \(\nu_{\text{ech}}\), so we can store only the values between \(0\) and \(\nu_{\text{ech}}\). We still have to sample it, and in particular to find the adequate sampling step. It is clear that we want the sampling to be as &quot;fine&quot; as possible, in order not to miss any detail of the Fourier transform&#33; For this we can take inspiration from what happened when we sampled \(f\): its Fourier transform became periodic, with period \(\nu_{\text{ech}}\). Now the inverse Fourier transform &#40;the operation that allows to recover the signal from its Fourier transform&#41; has similar properties to the Fourier transform. This means that if we sample \(\hat{h}\) with a sampling step \(\nu_s\), then its inverse Fourier transform becomes periodic with period \(1/\nu_s\). This gives a low limit on the values that \(\nu_s\) can take &#33; Indeed, if the inverse transform has a period smaller than the width of the window &#40;\(1/\nu_s < 2x_0\)&#41;, then the reconstructed signal taken between \(-x_0\) and \(x_0\) will not correspond to the initial signal \(f\) &#33; </p>
<p>So we choose \(\nu_s = \frac{1}{2x_0}\) to discretize \(\hat{h}\). We use the same process of multiplication by a Dirac comb to discretize. In this way we obtain the Fourier transform of a new function \(l\) :</p>
\[
\begin{aligned}
\hat{l}(\nu) = \sum_{n=-\infty}^{+\infty} \delta(\nu-n\nu_s) \sum_{k=-k_0}^{+k_0}f[k]e^{-2i\pi k\frac{n\nu_s}{\nu_{\text{ech}}}}
\end{aligned}
\]
<p>This notation is a bit complicated, and we can be more interested in \(\hat{l}[n]=\hat{l}(n\nu_s)\) :</p>
\[
\begin{aligned}
\hat{l}[n] = \hat{l}(n\nu_s) &=& \sum_{k=-k_0}^{+k_0}f[k]e^{-2i\pi k\frac{n\nu_s}{\nu_{\text{ech}}}}\\
&=& \sum_{k=0}^{N-1}f[k]e^{-2i\pi k\frac{n}{N}}
\end{aligned}
\]
<p>To get the last line, I re-indexed \(f[k]\) to start at 0, noting \(N\) the number of samples. I then assumed that the window size corresponded to an integer number of samples, i.e. that \(2x_0 = N\times T\), which is rewritten as \(N\times \nu_s = \nu_{\text{ech}}\). This expression is the <strong>discrete Fourier transform</strong> of the signal.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/signal_ech_fen_ech.svg" alt="Sampling the Fourier transform of the sampled signal to obtain the
discrete Fourier transform"> 
<figcaption> Sampling the Fourier transform of the sampled signal to obtain the
discrete Fourier transform</figcaption>
</figure>
<div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  We can see that the sampling frequency does not enter into this equation, and there are many applications where we simply forget that this frequency exists. 
</div>
</div>
<div class="message  is-link">
<div class="message-header">
<p> Question</p>
</div>
<div class="message-body">
  There is one last point to clarify: this discrete transform is defined for an infinite &#40;discrete&#41; number of values of \(n\). How to store it on our computer ? 
</div>
</div><p>This problem is solved quite simply by windowing the discrete Fourier transform. Since the transform has been periodized by the sampling of the starting signal, it is enough to store one period of the transform to store all the information contained in it. The choice which is generally made is to keep all the points between O and \(\nu_{\text{ech}}\). This allows to use only positive \(n\), and one can easily reconstruct the plot of the transform if needed by inverting the first and the second half of the computed transform. In practice &#40;for the implementation&#41;, the discrete Fourier transform is thus given by :</p>
\[
\boxed{
\forall n=0...(N-1),\; \hat{f}[n] = \sum_{k=0}^{N-1}f[k]e^{-2i\pi k\frac{n}{N}}
}
\]
<p>To conclude on our example function, we obtain the following plot: </p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/signal_ech_fen_ech_fen.svg" alt="Windowing of the discrete Fourier transform for
storage"> 
<figcaption> Windowing of the discrete Fourier transform for
storage</figcaption>
</figure><h2 id="calculating_the_discrete_fourier_transform">Calculating the discrete Fourier transform</h2>
<p>So we have at our disposal the expression of the discrete Fourier transform of a signal \(f\):</p>
\[
\hat{f}[n] = \sum_{k=0}^{N-1}f[k]e^{-2i\pi k\frac{n}{N}}
\]
<p>This s the expression of a matrix product which would look like this:</p>
\[
\hat{f} = \mathbf{M} \cdot f
\]
<p>with </p>
\[
\mathbf{M} = \begin{pmatrix} 
1 & 1 & 1 & \dots & 1 \\
1 & e^{-2i\pi 1 \times 1 / N} & e^{-2i\pi 2 \times 1 / N} & \dots & e^{-2i\pi
1\times (N-1)/N} \\
1 & e^{-2i\pi 1 \times 2 \times 1 / N} & e^{-2i\pi 2 \times 2 / N} & \ddots &
\vdots\\
\vdots & \vdots & \ddots & \ddots & e^{e-2i\pi (N-2)\times (N-1) / N}\\
1 & e^{-2i\pi (N-1) \times 1/N} & \dots & e^{e-2i\pi (N-1) \times (N-2) / N} & e^{-2i\pi (N-1)\times (N-1) / N}
\end{pmatrix}
\]
<p>Those in the know will notice that this is a <a href="https://en.wikipedia.org/wiki/Vandermonde_matrix">Vandermonde matrix</a> on the roots of the unit.</p>
<p>So this calculation can be implemented relatively easily&#33;</p>
<pre><code class="language-julia">function naive_dft&#40;x&#41;
  N &#61; length&#40;x&#41;
  k &#61; reshape&#40;0:&#40;N-1&#41;, 1, :&#41;
  n &#61; 0:&#40;N-1&#41;
  M &#61; @. exp&#40;-2im * π * k * n / N&#41;
  M * x
end</code></pre><div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  The macro <code>@.</code> line 5 allows to vectorize the computation of the expression it encompasses &#40;<code>exp&#40;-2im * π * k * n / N&#41;</code>&#41;. Indeed the function <code>exp</code> and the division and multiplication operators are defined for scalars. This macro is used to inform Julia that he should apply the scalar operations term by term. 
</div>
</div><p>And to check that it does indeed give the right result, it is enough to compare it with a reference implementation:</p>
<pre><code class="language-julia">using FFTW</code></pre>
<pre><code class="language-julia">a &#61; rand&#40;1024&#41;
b &#61; fft&#40;a&#41;
c &#61; naive_dft&#40;a&#41;
b ≈ c</code></pre>
<p>The last block evaluates to <code>true</code>, which confirms that we are not totally off the mark&#33;</p><div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  I use the <code>≈</code> operator to compare rather than <code>&#61;&#61;</code> to allow for small differences, especially because of rounding errors on floats. 
</div>
</div><p>However, is this code effective? We can check by comparing the memory footprint and execution speed.</p>
<pre><code class="language-julia">using BenchmarkTools</code></pre>
<pre><code class="language-julia">@benchmark fft&#40;a&#41; setup&#61;&#40;a &#61; rand&#40;1024&#41;&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  18.290 μs … 333.143 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     44.453 μs               ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   47.263 μs ±  12.066 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;                  ▁▇▆█▁▄
  ▂▂▁▁▂▂▁▁▂▂▁▂▁▁▃▆███████▆▄▄▄▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂ ▃
  18.3 μs         Histogram: frequency by time         98.7 μs &lt; Memory estimate: 32.55 KiB, allocs estimate: 6.</code></pre>
<pre><code class="language-julia">@benchmark naive_dft&#40;a&#41; setup&#61;&#40;a &#61; rand&#40;1024&#41;&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 100 samples with 1 evaluation.
 Range &#40;min … max&#41;:  35.577 ms … 571.900 ms  ┊ GC &#40;min … max&#41;:  0.00&#37; … 88.11&#37;
 Time  &#40;median&#41;:     40.820 ms               ┊ GC &#40;median&#41;:     0.00&#37;
 Time  &#40;mean ± σ&#41;:   50.326 ms ±  53.354 ms  ┊ GC &#40;mean ± σ&#41;:  11.18&#37; ± 10.10&#37;    ▂█
  ▅▅██▅▆▆▅▄▄▁▄▁▁▁▄▁▁▄▁▅█▄▆▄▃▃▄▅▅▁▃▁▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃ ▃
  35.6 ms         Histogram: frequency by time         80.2 ms &lt; Memory estimate: 16.03 MiB, allocs estimate: 4.</code></pre><div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  As you can see, the maximum execution time of the reference implementation is two orders of magnitude higher than the average and median execution time. This is due to Julia&#39;s <em>Just in time</em> &#40;JIT&#41; compilation. If we were writing a real Julia library we could consider optimizing our code to compile quickly. We will just ignore the maximum execution time in this tutorial, which is only the compilation time for the first execution of the code. I refer you to <a href="https://juliaci.github.io/BenchmarkTools.jl/dev/">the <code>BenchmarkTools.jl</code> documentation</a> for more information. 
</div>
</div><p>So our implementation is <em>really</em> slow &#40;about 10,000 times&#41; and has a very high memory footprint &#40;about 500 times&#41; compared to the benchmark implementation&#33; To improve this, we will implement the fast Fourier transform.</p>
<h2 id="why_a_fast_fourier_transform_algorithm">Why a fast Fourier transform algorithm?</h2>
<p>Before getting our hands dirty again, let&#39;s first ask the question: is it really necessary to try to improve this algorithm?</p>
<p>Before answering directly, let us look at some applications of the Fourier transform and the discrete Fourier transform.</p>
<p>The Fourier transform has first of all a lot of theoretical applications, whether it is to solve differential equations, in signal processing or in quantum physics. It also has practical applications <a href="https://en.wikipedia.org/wiki/Fourier_optics">in optics</a> and <a href="https://en.wikipedia.org/wiki/Fourier-transform_spectroscopy">in spectroscopy</a>.</p>
<p>The discrete Fourier transform also has many applications, in signal analysis, for data compression, <a href="https://www.youtube.com/watch?v&#61;h7apO7q16V0">multiplication of polynomials</a> or the computation of convolution products. </p>
<p>Our naive implementation of the discrete Fourier transform has a time and memory complexity in \(\mathcal{O}(N^2)\) with \(N\) the size of the input sample, this is due to the storage of the matrix and the computation time of the matrix product. Concretely, if one wished to analyze a sound signal of 3 seconds sampled at 44kHz with data stored on simple precision floats &#40;4 bytes&#41;, it would thus be necessary approximately \(2\times(44000\times3)^2\times 4\approx100\;000\;000\;000\) bytes of memory &#40;a complex number is stored on 2 floats&#41; We can also estimate the time necessary to make this calculation. The median time for 1024 points was 38.367 ms. For our 3 seconds signal, it would take about \(38.867\times\left(\frac{44000\times3}{1024}\right)^2\approx 637\;537\) milliseconds, that is more than 10 minutes &#33;</p>
<p>One can easily understand the interest to reduce the complexity of the calculation. In particular the fast Fourier transform algorithm &#40;used by the reference implementation&#41; has a complexity in \(\mathcal{O}(N\log N)\). According to our <em>benchmark</em>, the algorithm processes a 1024-point input in 23.785µs. It should therefore process the sound signal in about \(23.785\times\frac{44000\times\log(44000\times3)}{1024\times\log1024}\approx 5\;215\) microseconds, that is to say about 120000 times faster than our algorithm. We can really say that the <em>fast</em> of <em>Fast Fourier Transform</em> is not stolen &#33;</p>
<p><table class="fndef" id="fndef:gaussian">
    <tr>
        <td class="fndef-backref">[2]</td>
        <td class="fndef-content">Gaussians are said to be eigenfunctions of the Fourier transform.</td>
    </tr>
</table>
<table class="fndef" id="fndef:math">
    <tr>
        <td class="fndef-backref">[3]</td>
        <td class="fndef-content">It should be justified here that we can invert the sum and integral signs.</td>
    </tr>
</table>
 <hr /></p>
<p>We saw how the discrete Fourier transform was constructed, and then we naively tried to implement it. While this implementation is relatively simple to implement &#40;especially with a language like Julia that facilitates matrix manipulations&#41;, we also saw its limitations in terms of execution time and memory footprint.</p>
<p>It&#39;s time to move on to the FFT itself&#33;</p>
<h1 id="implementing_the_fft">Implementing the FFT</h1>
<p>In this part we will implement the FFT by starting with a simple approach, and then making it more complex as we go along to try to calculate the Fourier transform of a real signal in the most efficient way possible. To compare the performances of our implementations, we will continue to compare with the FFTW implementation.</p>
<h2 id="my_first_fft">My first FFT</h2>
<p>We have previously found the expression of the discrete Fourier transform :</p>
\[
\hat{f}[n] = \sum_{k=0}^{N-1}f[k]e^{-2i\pi k\frac{n}{N}}
\]
<p>The trick at the heart of the FFT algorithm is to notice that if we try to cut this sum in two, separating the even and odd terms, we get &#40;assuming \(N\) is even&#41;, for \(n < N/2\) :</p>
\[
\begin{aligned}
\hat{f}[n] &= \sum_{k=0}^{N}f[k]e^{-2i\pi k\frac{n}{N}}\\
&= \sum_{m=0}^{N/2-1}f[2m]e^{-2i\pi 2m\frac{n}{N}} + \sum_{m=0}^{N/2-1}f[2m+1]e^{-2i\pi (2m+1)\frac{n}{N}}\\
&= \sum_{m=0}^{N/2-1}f[2m]e^{-2i\pi m\frac{n}{N/2}} + e^{-2i\pi n/N}\sum_{m=0}^{N/2-1}f[2m+1]e^{-2i\pi m\frac{n}{N/2}}\\
&= \hat{f}^\text{even}[n] + e^{-2i\pi n/N}\hat{f}^\text{odd}[n]
\end{aligned}
\]
<p>where \(\hat{f}^\text{even}\) and \(\hat{f}^\text{odd}\) are the Fourier transforms of the sequence of even terms of \(f\) and of the sequence of odd terms of \(f\). We can therefore compute the first half of the Fourier transform of \(f\) by computing the Fourier transforms of these two sequences of length \(N/2\) and recombining them. Similarly, if we compute \(\hat{f}[n+N/2]\) we have :</p>
\[
\begin{aligned}
\hat{f}[n+N/2] &= \sum_{m=0}^{N/2-1}f[2m]e^{-2i\pi m\frac{n+N/2}{N/2}} +
e^{-2i\pi(n+N/2)/N}\sum_{m=0}^{N/2-1}f[2m+1]e^{-2i\pi m\frac{n+N/2}{N/2}}\\
&= \sum_{m=0}^{N/2-1}f[2m]e^{-2i\pi m\frac{n}{N/2}} - e^{-2i\pi
n/N}\sum_{m=0}^{N/2-1}f[2m+1]e^{-2i\pi m\frac{n}{N/2}}\\
&= \hat{f}^\text{even}[n] - e^{-2i\pi n/N}\hat{f}^\text{odd}[n]
\end{aligned}
\]
<p>This means that by computing two Fourier transforms of length \(N/2\), we are able to compute two elements of a Fourier transform of length \(N\). Assuming for simplicity that \(N\) is a power of two<sup id="fnref:power2">[4]</sup>, this naturally draws a recursive implementation of the FFT. According to the <a href="https://fr.wikipedia.org/wiki/Master_theorem">master theorem</a>, this algorithm will have complexity \(\mathcal{O}(N\log_2 N)\), which is much better than the first naive algorithm we implemented, which has complexity in \(\mathcal{O}(N^2)\).</p>
<pre><code class="language-julia">function my_fft&#40;x&#41;
  # Stop condition, the TF of an array of size 1 is this same array.
  if length&#40;x&#41; &lt;&#61; 1
    x
  else
    N &#61; length&#40;x&#41;
    # Xᵒ contains the TF of odd terms and Xᵉ that of even terms.
    # The subtlety being that Julia&#39;s tablals start at 1 and not 0.
    Xᵒ &#61; my_fft&#40;x&#91;2:2:end&#93;&#41;
    Xᵉ &#61; my_fft&#40;x&#91;1:2:end&#93;&#41;
    factors &#61; @. exp&#40;-2im * π * &#40;0:&#40;N/2 - 1&#41;&#41; / N&#41;
    &#91;Xᵉ .&#43; factors .* Xᵒ; Xᵉ .- factors .* Xᵒ&#93;
  end
end</code></pre>
<p>We can check as before that code gives a fair result, then compare its runtime qualities with the reference implementation.</p>
<pre><code class="language-julia">@benchmark fft&#40;a&#41; setup&#61;&#40;a &#61; rand&#40;1024&#41;&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  15.262 μs … 67.122 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     16.877 μs              ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   18.290 μs ±  3.125 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;     ▇█▂
  ▁▂▇███▆▄▄▄▃▃▃▃▃▃▃▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  15.3 μs         Histogram: frequency by time        27.9 μs &lt; Memory estimate: 32.55 KiB, allocs estimate: 6.</code></pre>
<pre><code class="language-julia">@benchmark my_fft&#40;a&#41; setup&#61;&#40;a &#61; rand&#40;1024&#41;&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 3399 samples with 1 evaluation.
 Range &#40;min … max&#41;:  983.141 μs … 586.032 ms  ┊ GC &#40;min … max&#41;:  0.00&#37; … 99.48&#37;
 Time  &#40;median&#41;:       1.113 ms               ┊ GC &#40;median&#41;:     0.00&#37;
 Time  &#40;mean ± σ&#41;:     1.464 ms ±  10.067 ms  ┊ GC &#40;mean ± σ&#41;:  17.52&#37; ±  9.04&#37;  ██▅▂▁▄▂▁▂▁                                                    ▁
  ████████████▇▄▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃▄▅ █
  983 μs        Histogram: log&#40;frequency&#41; by time       7.58 ms &lt; Memory estimate: 989.12 KiB, allocs estimate: 10230.</code></pre>
<p>We can see that we have improved the execution time &#40;by a factor of 8&#41; and the memory footprint of the algorithm &#40;by a factor of 13&#41;, without getting closer to the reference implementation.</p>
<h2 id="analysis_of_the_first_implementation">Analysis of the first implementation</h2>
<p>Let&#39;s go back to the previous code: </p>
<pre><code class="language-julia">function my_fft&#40;x&#41;
  # Stop condition, the TF of an array of size 1 is this same array.
  if length&#40;x&#41; &lt;&#61; 1
    x
  else
    N &#61; length&#40;x&#41;
    # Xᵒ contains the TF of odd terms and Xᵉ that of even terms.
    # The subtlety being that Julia&#39;s tablals start at 1 and not 0.
    Xᵒ &#61; my_fft&#40;x&#91;2:2:end&#93;&#41;
    Xᵉ &#61; my_fft&#40;x&#91;1:2:end&#93;&#41;
    factors &#61; @. exp&#40;-2im * π * &#40;0:&#40;N/2 - 1&#41;&#41; / N&#41;
    &#91;Xᵉ .&#43; factors .* Xᵒ; Xᵉ .- factors .* Xᵒ&#93;
  end
end</code></pre>
<p>And let&#39;s try to keep track of the memory allocations. For simplicity, we can assume that we are working on an array of 4 elements, <code>&#91;f&#91;0&#93;, f&#91;1&#93;, f&#91;2&#93;, f&#91;3&#93;&#93;</code>. The first call to <code>my_fft</code> keeps in memory the initial array, then launches the fft on two sub-arrays of size 2: <code>&#91;f&#91;0&#93;, f&#91;2&#93;&#93;</code> and <code>&#91;f&#91;1&#93;, f&#91;3&#93;&#93;</code>, then recursive calls keep in memory before recombining the arrays <code>&#91;f&#91;0&#93;&#93;</code> and <code>&#91;f&#91;2&#93;&#93;</code> then <code>&#91;f&#91;1&#93;&#93;</code> and <code>&#91;f&#91;3&#93;&#93;</code>. At most, we have \(log_2(N)\) arrays allocated with sizes divided by two each time. Not only do these arrays take up memory, but we also waste time allocating them&#33;</p>
<p>However, if we observe the definition of the recurrence we use, at each step \(i\) &#40;i.e. for each array size, \(N/2^i\)&#41;, the sum of the intermediate array sizes is always \(N\). In other words, this gives the idea that we could save all these array allocations and use the same array all the time, provided that we make all the associations of arrays of the same size at the same step.</p>
<p>Schematically we can represent the FFT process for an array with 8 elements as follows:</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/radix2.png" alt="Illustration of the FFT process. The colors indicate if an element is treated as an even array &#40;red&#41; or an odd array &#40;green&#41;. The geometrical shapes allow to associate the elements which are in the same subarray. The multiplicative coefficients applied to the odd elements are also represented. This somewhat complicated diagram is the key to what follows. Feel free to spend some time to understand it."> 
<figcaption> Illustration of the FFT process. The colors indicate if an element is treated as an even array (red) or an odd array (green). The geometrical shapes allow to associate the elements which are in the same subarray. The multiplicative coefficients applied to the odd elements are also represented. This somewhat complicated diagram is the key to what follows. Feel free to spend some time to understand it.</figcaption>
</figure><p>How to read this diagram? Each column corresponds to a depth of the recurrence of our first FFT. The leftmost column corresponds to the deepest recurrence: we have cut the input array enough to arrive at subarrays of size 1. These 8 sub-tables are symbolized by 8 different geometrical shapes. We then go to the next level of recurrence. Each pair of sub-tables of size 1 must be combined to create a sub-table of size 2, which will be stored in the same memory cells as the two sub-tables of size 1. For example, we combine the subarray ▲ that contains \(f[0]\) and the subarray ◆ that contains \(f[4]\) using the formula demonstrated earlier to form the array \([f[0] + f[4], f[0] - f[4]]\), which I call in the following ◆, and store the two values in position 0 and 4. The colors of the arrows allow us to distinguish those bearing a coefficient &#40;which correspond to the treatment we give to the subarray \(\hat{f}^{\text{odd}}\) in the formulas of the previous section&#41;. After having constructed the 4 sub-tables of size 2, we can proceed to a new step of the recurrence to compute two sub-tables of size 4. Finally the last step of the recurrence combines the two subarrays of size 4 to compute the array of size 8 which contains the Fourier transform.</p>
<p>Based on this scheme we can think of having a function whose main loop would calculate successively each column to arrive at the final result. In this way, all the calculations are performed on the same array and the number of allocations is minimized&#33; There is however a problem: we see that the \(\hat{f}[k]\) do not seem to be ordered at the end of the process. </p>
<p>In reality, these \(\hat{f}[k]\) are ordered via a <a href="https://en.wikipedia.org/wiki/Bit-reversal_permutation">reverse bit permutation</a>. This means that if we write the indices \(k\) in binary, then reverse this writing &#40;the MSB becoming the LSB<sup id="fnref:MSB">[5]</sup>&#41;, we obtain the index at which \(\hat{f}[k]\) is found after the FFT algorithm. The permutation process is described by the following table in the case of a calculation on 8 elements.</p>
<table><tr><th align="right">\(k\)</th><th align="right">Binary representation of \(k\)</th><th align="right">Reverse binary representation</th><th align="right">Index of \(\hat{f}[k]\)</th></tr><tr><td align="right">0</td><td align="right">000</td><td align="right">000</td><td align="right">0</td></tr><tr><td align="right">1</td><td align="right">001</td><td align="right">100</td><td align="right">4</td></tr><tr><td align="right">2</td><td align="right">010</td><td align="right">010</td><td align="right">2</td></tr><tr><td align="right">3</td><td align="right">011</td><td align="right">110</td><td align="right">6</td></tr><tr><td align="right">4</td><td align="right">100</td><td align="right">001</td><td align="right">1</td></tr><tr><td align="right">5</td><td align="right">101</td><td align="right">101</td><td align="right">5</td></tr><tr><td align="right">6</td><td align="right">110</td><td align="right">011</td><td align="right">3</td></tr><tr><td align="right">7</td><td align="right">111</td><td align="right">111</td><td align="right">7</td></tr></table>
<p>If we know how to calculate the reverse permutation of the bits, we can simply reorder the array at the end of the process to obtain the right result. However, before jumping on the implementation, it is interesting to look at what happens if instead we reorder the input array <em>via</em> this permutation.</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/radix2_inv.png" alt="Diagram of the FFT with a permuted input. The colors and symbols are the same as in the first illustration"> 
<figcaption> Diagram of the FFT with a permuted input. The colors and symbols are the same as in the first illustration</figcaption>
</figure><p>We can see that by proceeding in this way we have a simple ordering of the sub-tables. Since in any case it will be necessary to proceed to a permutation of the table, it is interesting to do it before the calculation of the FFT.</p>
<h2 id="calculate_the_reverse_permutation_of_the_bits">Calculate the reverse permutation of the bits</h2>
<p>We must therefore begin by being able to calculate the permutation. It is possible to perform the permutation in place simply once we know which elements to exchange. Several methods exist to perform the permutation, and a search in Google Scholar will give you an overview of the wealth of approaches.</p>
<p>We can use a little trick here: since we are dealing only with arrays whose size is a power of 2, we can write the size \(N\) as \(N=2^p\). This means that the indices can be stored on \(p\) bits. We can then simply calculate the permuted index <em>via</em> binary operations. For example if \(p=10\) then the index \(797\) could be represented as: <code>1100011101</code>. </p>
<p>We can separate the inversion process in several steps. First we exchange the 5 most significant bits and the 5 least significant bits. Then on each of the half-words we invert the two most significant bits and the two least significant bits &#40;the central bits do not change&#41;. Finally on the two bits words that we have just exchanged, we exchange the most significant bit and the least significant bit.</p>
<p>An example of implementation would be the following:</p>
<pre><code class="language-julia">bit_reverse&#40;::Val&#123;10&#125;, num&#41; &#61; begin
  num &#61; &#40;&#40;num&amp;0x3e0&#41;&gt;&gt;5&#41;|&#40;&#40;num&amp;0x01f&#41;&lt;&lt;5&#41;
  num &#61; &#40;&#40;num&amp;0x318&#41;&gt;&gt;3&#41;|&#40;num&amp;0x084&#41;|&#40;&#40;num&amp;0x063&#41;&lt;&lt;3&#41;
  &#40;&#40;num&amp;0x252&#41;&gt;&gt;1&#41;|&#40;num&amp;0x084&#41;|&#40;&#40;num&amp;0x129&#41;&lt;&lt;1&#41;
end</code></pre>
<p>An equivalent algorithm can be applied for all values of \(p\), you just have to be careful not to change the central bits anymore when you have an odd number of bits in a half word. In the following there is an example for several word lengths.</p><div class="message  ">
<div class="message-header">
<p> </p>
</div>
<div class="message-body">
</p>
<pre><code class="language-julia">bit_reverse&#40;::Val&#123;64&#125;, num&#41; &#61; bit_reverse&#40;Val&#40;32&#41;, &#40;num&amp;0xffffffff00000000&#41;&gt;&gt;32&#41;|&#40;bit_reverse&#40;Val&#40;32&#41;, num&amp;0x00000000ffffff&#41;&lt;&lt;32&#41;
bit_reverse&#40;::Val&#123;32&#125;, num&#41; &#61; bit_reverse&#40;Val&#40;16&#41;, &#40;num&amp;0xffff0000&#41;&gt;&gt;16&#41;|&#40;bit_reverse&#40;Val&#40;16&#41;, num&amp;0x0000ffff&#41;&lt;&lt;16&#41;
bit_reverse&#40;::Val&#123;16&#125;, num&#41; &#61; bit_reverse&#40;Val&#40;8&#41;, &#40;num&amp;0xff00&#41;&gt;&gt;8&#41;|&#40;bit_reverse&#40;Val&#40;8&#41;, num&amp;0x00ff&#41;&lt;&lt;8&#41;
bit_reverse&#40;::Val&#123;8&#125;, num&#41; &#61; bit_reverse&#40;Val&#40;4&#41;, &#40;num&amp;0xf0&#41;&gt;&gt;4&#41;|&#40;bit_reverse&#40;Val&#40;4&#41;, num&amp;0x0f&#41;&lt;&lt;4&#41;
bit_reverse&#40;::Val&#123;4&#125;, num&#41; &#61;bit_reverse&#40;Val&#40;2&#41;, &#40;num&amp;0xc&#41;&gt;&gt;2&#41;|&#40;bit_reverse&#40;Val&#40;2&#41;, num&amp;0x3&#41;&lt;&lt;2&#41;
bit_reverse&#40;::Val&#123;3&#125;, num&#41; &#61; &#40;&#40;num&amp;0x1&#41;&lt;&lt;2&#41;|&#40;&#40;num&amp;0x4&#41;&gt;&gt;2&#41;|&#40;num&amp;0x2&#41;
bit_reverse&#40;::Val&#123;2&#125;, num&#41; &#61; &#40;&#40;num&amp;0x2&#41;&gt;&gt;1 &#41;|&#40;&#40;num&amp;0x1&#41;&lt;&lt;1&#41;
bit_reverse&#40;::Val&#123;1&#125;, num&#41; &#61; num</code></pre>
<p>
</div>
</div><p>Then we can do the permutation itself. The algorithm is relatively simple: just iterate over the array, calculate the inverted index of the current index and perform the inversion. The only subtlety is that the inversion must be performed only once per index of the array, so we discriminate by performing the inversion only if the current index is lower than the inverted index.</p>
<pre><code class="language-julia">function reverse_bit_order&#33;&#40;X, order&#41;
  N &#61; length&#40;X&#41;
  for i in 0:&#40;N-1&#41;
    j &#61; bit_reverse&#40;order, i&#41;
    if i&lt;j
      X&#91;i&#43;1&#93;,X&#91;j&#43;1&#93;&#61;X&#91;j&#43;1&#93;,X&#91;i&#43;1&#93;
    end
  end
  X
end</code></pre>
<h2 id="my_second_fft">My second FFT</h2>
<p>We are now sufficiently equipped to start a second implementation of the FFT. The first step will be to compute the reverse bit permutation. Then we will be able to compute the Fourier transform following the scheme shown previously. To do this we will store the size \(n_1\) of the sub-arrays and the number of cells \(n_2\) in the global array that separate two elements of the same index in the sub-arrays. The implementation can be done as follows:</p>
<pre><code class="language-julia">function my_fft_2&#40;x&#41;
  N &#61; length&#40;x&#41;
  order &#61; Int&#40;log2&#40;N&#41;&#41;
  @inbounds reverse_bit_order&#33;&#40;x, Val&#40;order&#41;&#41;
  n₁ &#61; 0
  n₂ &#61; 1
  for i&#61;1:order # i done the number of the column we are in.
    n₁ &#61; n₂ # n₁ &#61; 2ⁱ-¹
    n₂ *&#61; 2 # n₂ &#61; 2ⁱ
    
    step_angle &#61; -2π/n₂
    angle &#61; 0
    for j&#61;1:n₁ # j is the index in Xᵉ and Xᵒ
      factors &#61; exp&#40;im*angle&#41; # z &#61; exp&#40;-2im*π*&#40;j-1&#41;/n₂&#41;
      angle &#43;&#61; step_angle # a &#61; -2π*&#40;j&#43;1&#41;/n₂
      
      # We combine the element j of each group of subarrays
      for k&#61;j:n₂:N
        @inbounds x&#91;k&#93;, x&#91;k&#43;n₁&#93; &#61; x&#91;k&#93; &#43; factors * x&#91;k&#43;n₁&#93;, x&#91;k&#93; - factors * x&#91;k&#43;n₁&#93;
      end
    end
  end
  x  
end</code></pre><div class="message  is-info">
<div class="message-header">
<p> Information</p>
</div>
<div class="message-body">
  There are two small subtleties due to Julia: arrays start numbering at 1, and we use the <code>@inbounds</code> macro to speed up the code a bit by disabling array overflow checks. 
</div>
</div><p>We can again measure the performance of this implementation. To keep the comparison fair, the <code>fft&#33;</code> function should be used instead of <code>fft</code>, as it works in place.</p>
<pre><code class="language-julia">@benchmark fft&#33;&#40;a&#41; setup&#61;&#40;a &#61; rand&#40;1024&#41; |&gt; complex&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  16.501 μs … 156.742 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     20.942 μs               ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   24.874 μs ±   8.820 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;    █▇▁
  ▂▇████▅▄▄▄▃▂▅▄▂▃▂▂▂▂▂▂▂▂▂▃▃▃▃▃▃▃▃▃▃▃▂▂▂▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  16.5 μs         Histogram: frequency by time           52 μs &lt; Memory estimate: 304 bytes, allocs estimate: 4.</code></pre>
<pre><code class="language-julia">@benchmark my_fft_2&#40;a&#41; setup&#61;&#40;a &#61; rand&#40;1024&#41; .|&gt; complex&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  46.957 μs … 152.061 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     50.283 μs               ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   55.079 μs ±  10.720 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;   ▅ █▃▁▆▂ ▄  ▃  ▂   ▁    ▂    ▁    ▁                          ▁
  ██▇██████████████▇▇█▇▇▇▇█▇▆▆██▅▅▅▆█▆▆▆▇▇██▇▇█▇▆▆▆▇▅▇▅▅▆▅▅▅▅▆ █
  47 μs         Histogram: log&#40;frequency&#41; by time      98.7 μs &lt; Memory estimate: 0 bytes, allocs estimate: 0.</code></pre>
<p>We have significantly improved our execution time and memory footprint. We can see that there are zero bytes allocated &#40;this means that the compiler does not need to store the few intermediate variables in RAM&#41;, and that the execution time is close to that of the reference implementation.</p>
<h2 id="the_special_case_of_a_real_signal">The special case of a real signal</h2>
<p>So far we have reasoned about complex signals, which use two floats for storage. However in many situations we work with real value signals. Now in the case of a real signal, we know that \(\hat{f}\) verifies \(\hat{f}(-\nu) = \overline{\hat{f}(\nu)}\). This means that half of the values we calculate are redundant. Although we calculate the Fourier transform of a real signal, the result can be a complex number. In order to save storage space, we can think of using this half of the array to store complex numbers. For this, two properties will help us.</p>
<h3 id="property_1_compute_the_fourier_transform_of_two_real_functions_at_the_same_time">Property 1: Compute the Fourier transform of two real functions at the same time</h3>
<p>If we have two real signals \(f\) and \(g\), we can define the complex signal \(h=f+ig\). We then have:</p>
\[
\hat{h}[k] = \sum_{n=0}^{N-1}(f[n]+ig[n])e^{-2i\pi kn/N}
\]
<p>We can notice that </p>
\[
\begin{aligned}
\overline{\hat{h}[N-k]} &= \overline{\sum_{n=0}^{N-1}(f[n]+ig[n])e^{-2i\pi (N-k)n/N}}\\
&=\sum_{n=0}^{N-1}(f[n]-ig[n])e^{-2i\pi kn/N}
\end{aligned}
\]
<p>Combining the two we have</p>
\[
\begin{aligned}
\hat{f}[k] &= \frac{1}{2}(\hat{h}[k] + \overline{\hat{h}[N-k]})\\
\hat{g}[k] &= -\frac{i}{2}(\hat{h}[k] - \overline{\hat{h}[N-k]})\\
\end{aligned}
\]
<h3 id="property_2_compute_the_fourier_transform_of_a_single_function">Property 2 : Compute the Fourier transform of a single function</h3>
<p>The idea is to use the previous property by using the signal of the even and the odd elements. In other words for \(k=0...N/2-1\) we have \(h[k]=f[2k]+if[2k+1]\).</p>
<p>Then we have:</p>
\[
\begin{aligned}
\hat{f}^{\text{even}}[k] &= \sum_{n=0}^{N/2-1}f[2n]e^{-2i\pi kn/(N/2)}\\
\hat{f}^{\text{odd}}[k] &= \sum_{n=0}^{N/2-1}f[2n+1]e^{-2i\pi kn/(N/2)}
\end{aligned}
\]
<p>We can recombine the two partial transforms. For \(k=0...N/2-1\) :</p>
\[
\begin{aligned}
\hat{f}[k] &= \hat{f}^{\text{even}}[k] + e^{-2i\pi k/N}\hat{f}^{\text{odd}}[k]\\
\hat{f}[k+N/2] &= \hat{f}^{\text{even}}[k] - e^{-2i\pi k/N}\hat{f}^{\text{odd}}[k]
\end{aligned}
\]
<p>Using the first property, we then have:</p>
\[
\begin{aligned}
\hat{f}[k] &= \frac{1}{2}(\hat{h}[k] + \overline{\hat{h}[N/2-k]}) - \frac{i}{2}(\hat{h}[k] - \overline{\hat{h}[N/2-k]})e^{-2i\pi k/N} \\
\hat{f}[k+N/2] &= \frac{1}{2}(\hat{h}[k] + \overline{\hat{h}[N/2-k]}) + \frac{i}{2}(\hat{h}[k] - \overline{\hat{h}[N/2-k]})e^{-2i\pi k/N}
\end{aligned}
\]
<h3 id="calculation_in_place">Calculation in place</h3>
<p>The array \(h\), which is presented previously, is complex-valued. However the input signal is real-valued and twice as long. The trick is to use two cells of the initial array to store a complex element of \(h\). It is useful to do the calculations with complex numbers before starting to write code. For the core of the FFT, if we note \(x_i\) the array at step \(i\) of the main loop, we have:</p>
\[
\begin{aligned}
\text{Re}(x_{i+1}[k]) &= \text{Re}(x_{i}[k]) + \text{Re}(e^{-2i\pi j/n_2})\text{Re}(x_i[k+n_1]) - \text{Im}(e^{-2i\pi j/n_2})\text{Im}(x_i[k+n_1])\\
\text{Re}(x_{i+1}[k]) &= \text{Re}(x_{i}[k]) + \text{Re}(e^{-2i\pi j/n_2})\text{Re}(x_i[k+n_1]) - \text{Im}(e^{-2i\pi j/n_2})\text{Im}(x_i[k+n_1])\\\\
\text{Re}(x_{i+1}[k+n_1]) &= \text{Re}(x_{i}[k]) - \text{Re}(e^{-2i\pi j/n_2})\text{Re}(x_i[k+n_1]) + \text{Im}(e^{-2i\pi j/n_2})\text{Im}(x_i[k+n_1])\\
\text{Re}(x_{i+1}[k+n_1]) &= \text{Re}(x_{i}[k]) - \text{Re}(e^{-2i\pi j/n_2})\text{Re}(x_i[k+n_1]) + \text{Im}(e^{-2i\pi j/n_2})\text{Im}(x_i[k+n_1])\\
\end{aligned}
\]
<p>With the organization we choose, we can replace \(\text{Re}(x[k])\) with \(x[2k]\) and \(\text{Im}(x[k])\) with \(x[2k+1]\). We also note that we can replace \(\text{Re}(x[k+n_1])\) with \(x[2(k+n_1)]\) or even better \(x[2k+n_2]\).</p>
<p>The last step is the recombination of \(h\) to find the final result. The formula in property 2 is rewritten after an unpleasant but uncomplicated calculation:</p>
\[
\begin{aligned}
\text{Re}(\hat{x}[k]) &= 1/2 \times (\text{Re}(h[k]) + \text{Re}(h[N/2-k]) +
\text{Im}(h[k])\text{Re}(e^{-2i\pi k/N}) + \text{Re}(h[k])\text{Im}(e^{-2i\pi
k/N})... \\&...+ \text{Im}(h[N/2-k])\text{Re}(e^{-2i\pi k/N}) -
\text{Re}(h[N/2-k])\text{Im}(e^{-2i\pi k/N})\\
\text{Im}(\hat{x}[k]) &= 1/2 \times (\text{Im}(h[k]) - \text{Im}(h[N/2-k]) -
\text{Re}(h[k])\text{Re}(e^{-2i\pi k/N}) + \text{Im}(h[k])\text{Im}(e^{-2i\pi
k/N})...\\&... + \text{Re}(h[N/2-k])\text{Re}(e^{-2i\pi k/N}) + \text{Im}(h[N/2-k])\text{Im}(e^{-2i\pi k/N})
\end{aligned}
\]
<p>There is a particular case where this formula does not work: when \(k=0\) we leave the array \(h\) which contains only \(N/2\) elements. However we can use the symmetry of the Fourier Transform to see that \(h[N/2]=h[0]\). The case \(k=0\) then simplifies enormously:</p>
\[
\begin{aligned}
\text{Re}(\hat{x}[0]) &= \text{Re}(h[0]) + \text{Im}(h[0])\\
\text{Im}(\hat{x}[0]) &= 0
\end{aligned}
\]
<p>To perform the calculation in place, it is useful to be able to calculate \(\hat{x}[N/2-k]\) at the same time that we calculate \(\hat{x}[k]\). Reusing the previous results and the fact that \(e^{-2i\pi(N/2-k)/N}=-e^{2i\pi k/N}\), we find:</p>
\[
\begin{aligned}
\text{Re}(\hat{x}[N/2-k]) &= 1/2 \times \Big(\text{Re}(h[N/2-k]) + \text{Re}(h[k]) -
\text{Im}(h[N/2-k]]\text{Re}(e^{-2i\pi k/N})...\\&... +
\text{Re}(h[N/2-k])\text{Im}(e^{-2i\pi k/N}) -
\text{Im}(h[k])\text{Re}(e^{-2i\pi k/N}) - \text{Re}(h[k])\text{Im}(e^{-2i\pi
k/N})\Big)\\\text{Im}(\hat{x}[N/2-k]) &= 1/2 \times \Big(\text{Im}(h[N/2-k]) - \text{Im}(h[k]) +
\text{Re}(h[N/2-k])\text{Re}(e^{-2i\pi k/N})...\\&... +
\text{Im}(h[N/2-k])\text{Im}(e^{-2i\pi k/N}) -
\text{Re}(h[k])\text{Re}(e^{-2i\pi k/N}) + \text{Im}(h[k])\text{Im}(e^{-2i\pi
k/N})\Big)
\end{aligned}
\]
<p>After this little unpleasant moment, we are ready to implement a new version of the FFT&#33;</p>
<h2 id="an_fft_for_the_reals">An FFT for the reals</h2>
<p>Since the actual computation of the FFT is done on an array that is half the size of the input array, we need a function to compute the inverted index on 9 bits to be able to continue testing on 1024 points.</p>
<pre><code class="language-julia">bit_reverse&#40;::Val&#123;9&#125;, num&#41; &#61; begin
  num &#61; &#40;&#40;num&amp;0x1e0&#41;&gt;&gt;5&#41;|&#40;num&amp;0x010&#41;|&#40;&#40;num&amp;0x00f&#41;&lt;&lt;5&#41;
  num &#61; &#40;&#40;num&amp;0x18c&#41;&gt;&gt;2&#41;|&#40;num&amp;0x010&#41;|&#40;&#40;num&amp;0x063&#41;&lt;&lt;2&#41;
  &#40;&#40;num&amp;0x14a&#41;&gt;&gt;1&#41;|&#40;num&amp;0x010&#41;|&#40;&#40;num&amp;0x0a5&#41;&lt;&lt;1&#41;
end</code></pre><div class="message  ">
<div class="message-header">
<p> </p>
</div>
<div class="message-body">
  To complete the other methods of <code>bit_reverse</code> we can use the following implementations:</p>
<pre><code class="language-julia">bit_reverse&#40;::Val&#123;31&#125;, num&#41; &#61; begin
bit_reverse&#40;Val&#40;15&#41;, num&amp;0x7fff0000&gt;&gt;16&#41;| &#40;num&amp;0x00008000&#41; |&#40;bit_reverse&#40;Val&#40;7&#41;,num&amp;0x00007fff&#41;&lt;&lt;16&#41;
end
bit_reverse&#40;::Val&#123;15&#125;, num&#41; &#61; bit_reverse&#40;Val&#40;7&#41;, &#40;num&amp;0x7f00&#41;&gt;&gt;8&#41;| &#40;num&amp;0x0080&#41;|&#40;bit_reverse&#40;Val&#40;7&#41;,num&amp;0x007f&#41;&lt;&lt;8&#41;
bit_reverse&#40;::Val&#123;7&#125;, num&#41; &#61; bit_reverse&#40;Val&#40;3&#41;, &#40;num&amp;0x70&#41;&gt;&gt;4 &#41;| &#40;num&amp;0x08&#41; |&#40;bit_reverse&#40;Val&#40;3&#41;, num&amp;0x07&#41;&lt;&lt;4&#41;</code></pre>
<p>
</div>
</div><p>To take into account the specificities of the representation of the complexes we use, we implement a new version of <code>reverse_bit_order</code>.</p>
<pre><code class="language-julia">function reverse_bit_order_double&#33;&#40;x, order&#41;
  N &#61; length&#40;x&#41;
  for i in 0:&#40;N÷2-1&#41;
    j &#61; bit_reverse&#40;order, i&#41;
    if i&lt;j
      # swap real part
      x&#91;2*i&#43;1&#93;,x&#91;2*j&#43;1&#93;&#61;x&#91;2*j&#43;1&#93;,x&#91;2*i&#43;1&#93;
      # swap imaginary part
      x&#91;2*i&#43;2&#93;,x&#91;2*j&#43;2&#93;&#61;x&#91;2*j&#43;2&#93;,x&#91;2*i&#43;2&#93;
    end
  end
  x
end</code></pre>
<p>This leads to the new FFT implementation.</p>
<pre><code class="language-julia">function my_fft_3&#40;x&#41;
  N &#61; length&#40;x&#41; ÷ 2
  order &#61; Int&#40;log2&#40;N&#41;&#41;
  @inbounds reverse_bit_order_double&#33;&#40;x, Val&#40;order&#41;&#41;
  
  n₁ &#61; 0
  n₂ &#61; 1
  for i&#61;1:order # i done the number of the column we are in.
    n₁ &#61; n₂ # n₁ &#61; 2ⁱ-¹
    n₂ *&#61; 2 # n₂ &#61; 2ⁱ
    
    step_angle &#61; -2π/n₂
    angle &#61; 0
    for j&#61;1:n₁ # j is the index in Xᵉ and Xᵒ
      re_factor &#61; cos&#40;angle&#41;
      im_factor &#61; sin&#40;angle&#41;
      angle &#43;&#61; step_angle # a &#61; -2π*j/n₂
      
      # We combine element j from each group of subarrays
      @inbounds for k&#61;j:n₂:N
        re_xₑ &#61; x&#91;2*k-1&#93;
        im_xₑ &#61; x&#91;2*k&#93;
        re_xₒ &#61; x&#91;2*&#40;k&#43;n₁&#41;-1&#93;
        im_xₒ &#61; x&#91;2*&#40;k&#43;n₁&#41;&#93;
        x&#91;2*k-1&#93; &#61; re_xₑ &#43; re_factor*re_xₒ - im_factor*im_xₒ
        x&#91;2*k&#93; &#61; im_xₑ &#43; im_factor*re_xₒ &#43; re_factor*im_xₒ
        x&#91;2*&#40;k&#43;n₁&#41;-1&#93; &#61; re_xₑ - re_factor*re_xₒ &#43; im_factor*im_xₒ
        x&#91;2*&#40;k&#43;n₁&#41;&#93; &#61; im_xₑ - im_factor*re_xₒ - re_factor*im_xₒ      
      end
    end
  end
  # We build the final version of the TF
  # N half the size of x
  # Special case n&#61;0
  x&#91;1&#93; &#61; x&#91;1&#93; &#43; x&#91;2&#93;
  x&#91;2&#93; &#61; 0  
  
  step_angle &#61; -π/N
  angle &#61; step_angle
  @inbounds for n&#61;1:&#40;N÷2&#41;
    re_factor &#61; cos&#40;angle&#41;
    im_factor &#61; sin&#40;angle&#41;
    re_h &#61; x&#91;2*n&#43;1&#93;
    im_h &#61; x&#91;2*n&#43;2&#93;
    re_h_sym &#61; x&#91;2*&#40;N-n&#41;&#43;1&#93;
    im_h_sym &#61; x&#91;2*&#40;N-n&#41;&#43;2&#93;
    x&#91;2*n&#43;1&#93; &#61; 1/2*&#40;re_h &#43; re_h_sym &#43; im_h*re_factor &#43; re_h*im_factor &#43; im_h_sym*re_factor - re_h_sym*im_factor&#41;
    x&#91;2*n&#43;2&#93; &#61; 1/2*&#40;im_h - im_h_sym - re_h*re_factor &#43; im_h*im_factor &#43; re_h_sym*re_factor &#43; im_h_sym*im_factor&#41;
    x&#91;2*&#40;N-n&#41;&#43;1&#93; &#61; 1/2*&#40;re_h_sym &#43; re_h - im_h_sym*re_factor &#43; re_h_sym*im_factor - im_h*re_factor - re_h*im_factor&#41;
    x&#91;2*&#40;N-n&#41;&#43;2&#93; &#61; 1/2*&#40;im_h_sym - im_h &#43; re_h_sym*re_factor &#43; im_h_sym*im_factor - re_h*re_factor &#43; im_h*im_factor&#41;
    angle &#43;&#61; step_angle
  end
  x
end</code></pre>
<p>We can now check the performance of the new implementation:</p>
<pre><code class="language-julia">@benchmark fft&#33;&#40;x&#41; setup&#61;&#40;x &#61; rand&#40;1024&#41; .|&gt; complex&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  16.628 μs … 455.827 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     18.664 μs               ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   19.799 μs ±   5.338 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;      ▃██▅▁ ▂▂
  ▁▁▂▃█████▆██▆▃▃▃▂▂▂▃▃▃▂▂▂▂▂▂▂▂▂▂▁▁▁▂▂▁▁▁▁▂▂▂▂▁▁▁▂▂▂▁▁▁▁▁▁▁▁▁ ▂
  16.6 μs         Histogram: frequency by time         29.3 μs &lt; Memory estimate: 304 bytes, allocs estimate: 4.</code></pre>
<pre><code class="language-julia">@benchmark my_fft_3&#40;x&#41; setup&#61;&#40;x &#61; rand&#40;1024&#41;&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  29.024 μs … 129.484 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     33.435 μs               ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   34.648 μs ±   5.990 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;   ▅ █  ▁      ▅
  ▁█▁█▄▁█▄▂▇█▂▁█▄▂▁▄▂▁▁▃▁▁▁▁▃▁▁▁▁▁▂▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁ ▂
  29 μs           Histogram: frequency by time         59.8 μs &lt; Memory estimate: 0 bytes, allocs estimate: 0.</code></pre>
<p>This is a very good result&#33;</p>
<h2 id="optimization_of_trigonometric_functions">Optimization of trigonometric functions</h2>
<p>If we analyze the execution of <code>my_fft_3</code> using Julia&#39;s <em>profiler</em>, we can see that most of the time is spent computing trigonometric functions and creating the <code>StepRange</code> objects used in <code>for</code> loops. The second problem can be easily circumvented by using <code>while</code> loops. For the first one, in <em>Numerical Recipes</em> we can read &#40;section 5.4 &quot;<em>Recurrence Relations and Clenshaw&#39;s Recurrence Formula</em>&quot;, page 219 of the third edition&#41;:</p>
<blockquote>
<p>If your program&#39;s running time is dominated by evaluating trigonometric functions, you are probably doing something wrong.  Trig functions whose arguments form a linear sequence \(\theta = \theta_0 + n\delta, n=0,1,2...\) ,  are efficiently calculated by the recurrence </p>
</blockquote>
\[\begin{aligned}\cos(\theta + \delta) &= \cos\theta - [\alpha \cos\theta + \beta\sin\theta]\\\sin(\theta + \delta) &= \sin\theta - [\alpha\sin\theta - \beta\cos\theta]\end{aligned}\]
<blockquote>
<p>Where \(\alpha\) and \(\beta\) are the precomputed coefficients \(\alpha = 2\sin^2\left(\frac{\delta}{2}\right),\;\beta=\sin\delta\)</p>
</blockquote><div class="message  ">
<div class="message-header">
<p> </p>
</div>
<div class="message-body">
  This can be shown using the classical trigonometric identities:</p>
\[
\begin{aligned}
\cos(\theta+\delta) =& \cos\theta\cos\delta - \sin\theta\sin\delta\\
=& \cos\theta\left[2\cos^2\frac{\delta}{2} - 1\right] - \sin\theta\sin\delta\\
=& \cos\theta\left[2(1-\sin^2\frac{\delta}{2}) - 1\right] - \sin\theta\sin\delta\\
=& \cos\theta - [\underbrace{\sin^2\frac{\delta}{2}}_{=\alpha}\cos\theta + \underbrace{\sin\delta}_{=\beta}\sin\theta]
\end{aligned}
\]
<p>And with \(\sin x = \cos(x-\frac{\pi}{2})\), we have directly the second formula. 
</div>
</div><p>This relation is also interesting in terms of numerical stability. We can directly implement a final version of our FFT using these relations.</p>
<pre><code class="language-julia">function my_fft_4&#40;x&#41;
  N &#61; length&#40;x&#41; ÷ 2
  order &#61; Int&#40;log2&#40;N&#41;&#41;
  @inbounds reverse_bit_order_double&#33;&#40;x, Val&#40;order&#41;&#41;
  
  n₁ &#61; 0
  n₂ &#61; 1
  
    i&#61;1
  while i&lt;&#61;order # i done the number of the column we are in.
    n₁ &#61; n₂ # n₁ &#61; 2ⁱ-¹
    n₂ *&#61; 2 # n₂ &#61; 2ⁱ
    
    step_angle &#61; -2π/n₂
    α &#61; 2sin&#40;step_angle/2&#41;^2
    β &#61; sin&#40;step_angle&#41;
    cj &#61; 1
    sj &#61; 0
    j &#61; 1
    while j&lt;&#61;n₁ # j is the index in Xᵉ and Xᵒ
      # We combine the element j from each group of subarrays
      k &#61; j
      @inbounds while k&lt;&#61;N
        re_xₑ &#61; x&#91;2*k-1&#93;
        im_xₑ &#61; x&#91;2*k&#93;
        re_xₒ &#61; x&#91;2*&#40;k&#43;n₁&#41;-1&#93;
        im_xₒ &#61; x&#91;2*&#40;k&#43;n₁&#41;&#93;
        x&#91;2*k-1&#93; &#61; re_xₑ &#43; cj*re_xₒ - sj*im_xₒ
        x&#91;2*k&#93; &#61; im_xₑ &#43; sj*re_xₒ &#43; cj*im_xₒ
        x&#91;2*&#40;k&#43;n₁&#41;-1&#93; &#61; re_xₑ - cj*re_xₒ &#43; sj*im_xₒ
        x&#91;2*&#40;k&#43;n₁&#41;&#93; &#61; im_xₑ - sj*re_xₒ - cj*im_xₒ       
        
        k &#43;&#61; n₂
      end
      # We compute the next cosine and sine.
      cj, sj &#61; cj - &#40;α*cj &#43; β*sj&#41;, sj - &#40;α*sj-β*cj&#41;
      j&#43;&#61;1
    end
    i &#43;&#61; 1
  end
  # We build the final version of the TF
  # N half the size of x
  # Special case n&#61;0
  x&#91;1&#93; &#61; x&#91;1&#93; &#43; x&#91;2&#93;
  x&#91;2&#93; &#61; 0  
  
  step_angle &#61; -π/N
  α &#61; 2sin&#40;step_angle/2&#41;^2
  β &#61; sin&#40;step_angle&#41;
  cj &#61; 1
  sj &#61; 0
  j &#61; 1
  @inbounds while j&lt;&#61;&#40;N÷2&#41;
    # We calculate the cosine and sine before the main calculation here to compensate for the first
    # step of the loop that was skipped.
    cj, sj &#61; cj - &#40;α*cj &#43; β*sj&#41;, sj - &#40;α*sj-β*cj&#41;
    
    re_h &#61; x&#91;2*j&#43;1&#93;
    im_h &#61; x&#91;2*j&#43;2&#93;
    re_h_sym &#61; x&#91;2*&#40;N-j&#41;&#43;1&#93;
    im_h_sym &#61; x&#91;2*&#40;N-j&#41;&#43;2&#93;
    x&#91;2*j&#43;1&#93; &#61; 1/2*&#40;re_h &#43; re_h_sym &#43; im_h*cj &#43; re_h*sj &#43; im_h_sym*cj - re_h_sym*sj&#41;
    x&#91;2*j&#43;2&#93; &#61; 1/2*&#40;im_h - im_h_sym - re_h*cj &#43; im_h*sj &#43; re_h_sym*cj &#43; im_h_sym*sj&#41;
    x&#91;2*&#40;N-j&#41;&#43;1&#93; &#61; 1/2*&#40;re_h_sym &#43; re_h - im_h_sym*cj &#43; re_h_sym*sj - im_h*cj - re_h*sj&#41;
    x&#91;2*&#40;N-j&#41;&#43;2&#93; &#61; 1/2*&#40;im_h_sym - im_h &#43; re_h_sym*cj &#43; im_h_sym*sj - re_h*cj &#43; im_h*sj&#41;
    
    j &#43;&#61; 1
  end
  x
end</code></pre>
<p>We can check that we always get the right result: </p>
<pre><code class="language-julia">a &#61; rand&#40;1024&#41;
b &#61; fft&#40;a&#41;
c &#61; my_fft_4&#40;a&#41;
real.&#40;b&#91;1:end÷2&#93;&#41; ≈ c&#91;1:2:end&#93; &amp;&amp; imag.&#40;b&#91;1:end÷2&#93;&#41; ≈ c&#91;2:2:end&#93;</code></pre>
<pre><code class="language-julia">true</code></pre>
<p>In terms of performance, we finally managed to outperform the reference implementation&#33;</p>
<pre><code class="language-julia">@benchmark fft&#33;&#40;x&#41; setup&#61;&#40;x &#61; rand&#40;1024&#41; .|&gt; complex&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  16.991 μs … 660.685 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     19.090 μs               ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   20.367 μs ±   7.360 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;     ▁▂▁▂▇█▃
  ▃▆████████▆▅▅▄▄▄▄▃▃▄▄▃▃▃▃▂▃▃▃▃▂▂▃▃▂▂▂▃▃▃▃▃▃▂▃▃▃▃▂▂▂▂▂▂▂▂▂▂▁▂ ▃
  17 μs           Histogram: frequency by time         31.9 μs &lt; Memory estimate: 304 bytes, allocs estimate: 4.</code></pre>
<pre><code class="language-julia">@benchmark my_fft_4&#40;x&#41; setup&#61;&#40;x &#61; rand&#40;1024&#41;&#41;</code></pre>
<pre><code class="language-julia">BenchmarkTools.Trial: 10000 samples with 1 evaluation.
 Range &#40;min … max&#41;:  12.025 μs … 77.621 μs  ┊ GC &#40;min … max&#41;: 0.00&#37; … 0.00&#37;
 Time  &#40;median&#41;:     12.654 μs              ┊ GC &#40;median&#41;:    0.00&#37;
 Time  &#40;mean ± σ&#41;:   13.781 μs ±  2.731 μs  ┊ GC &#40;mean ± σ&#41;:  0.00&#37; ± 0.00&#37;  █ █▂▃▅ ▅▃ ▅▁ ▂▃  ▄  ▁▃▁ ▁▄▂ ▂ ▄▂                            ▂
  █▇████▅██▁██▃██▃▆██▆███▇███▄████▄▄▁▃▄▄▅▄▁▄▄▅▃▁▄▄▃▄▄▃▄▄▄▄▁▄▅ █
  12 μs        Histogram: log&#40;frequency&#41; by time      25.4 μs &lt; Memory estimate: 0 bytes, allocs estimate: 0.</code></pre>
<p><table class="fndef" id="fndef:power2">
    <tr>
        <td class="fndef-backref">[4]</td>
        <td class="fndef-content">In practice we can always reduce to this case by stuffing zeros.</td>
    </tr>
</table>
<table class="fndef" id="fndef:MSB">
    <tr>
        <td class="fndef-backref">[5]</td>
        <td class="fndef-content">MSB and LSB are the acronyms of <em>Most Significant Bit</em> and <em>Least Significant Bit</em>. In a number represented on \(n\) bits, the MSB is the bit that carries the information on the highest power of 2 &#40;\(2^{n-1}\)&#41; while the LSB carries the information on the lowest power of 2 &#40;\(2^0\)&#41;. Concretely the MSB is the leftmost bit of the binary representation of a number, while the LSB is the rightmost.</td>
    </tr>
</table>
</p>
<hr />
<p>If we compare the different implementations proposed in this tutorial as well as the two reference implementations, and then plot the median values of execution time, memory footprint and number of allocations, we obtain the following plot:</p><figure style="text-align=center;">
 <img src="https://klafyvel.me/assets/blog/articles/fft-julia/benchmark.svg" alt="Benchmark of the different solutions: median
values."> 
<figcaption> Benchmark of the different solutions: median
values.</figcaption>
</figure><p>I added the function <code>FFTW.rfft</code> which is supposed to be optimized for real. We can see that in reality, unless you work on very large arrays, it does not bring much performance.</p>
<p>We can see that the last versions of the algorithm are very good in terms of number of allocations and memory footprint. In terms of execution time, the reference implementation ends up being faster on very large arrays.</p>
<p>How can we explain these differences, especially between our latest implementation and the implementation in FFTW? Some elements of answer:</p>
<ol>
<li><p>FFTW solves a much larger problem. Indeed our implementation is &quot;naive&quot; for example in the sense that it can only work on input arrays whose size is a power of two. And even then, only those for which we have taken the trouble to implement a method of the <code>bit_reverse</code> function. The reverse bit permutation problem is a bit more complicated to solve in the general case. Moreover FFTW performs well on many types of architectures, offers discrete Fourier transforms in multiple dimensions etc... If you are interested in the subject, I recommend <a href="https://www.researchgate.net/publication/2986439_The_Design_and_implementation_of_FFTW3">this article</a><sup id="fnref:fftw">[6]</sup> which presents the internal workings of FFTW.</p>
</li>
<li><p>The representation of the complex numbers plays in our favor. Indeed, we avoid our implementation to do any conversion, this is seen in particular in the test codes where we take care of recovering the real part and the imaginary part of the transform:</p>
</li>
</ol>
<pre><code class="language-julia">real.&#40;b&#91;1:end÷2&#93;&#41; ≈ c&#91;1:2:end&#93; &amp;&amp; imag.&#40;b&#91;1:end÷2&#93;&#41; ≈ c&#91;2:2:end&#93;</code></pre>
<pre><code class="language-julia">true</code></pre>
<ol start="3">
<li><p>Our algorithm was not thought of with numerical stability in mind. This is an aspect that could still be improved. Also, we did not test it on anything other than noise. However, the following block presents some tests that suggest that it &quot;behaves well&quot; for some test functions.</p>
</li>
</ol><div class="message  ">
<div class="message-header">
<p> </p>
</div>
<div class="message-body">
</p>
<pre><code class="language-julia">function test_signal&#40;s&#41;
b &#61; fft&#40;s&#41;
c &#61; my_fft_4&#40;s&#41;
real.&#40;b&#91;1:end÷2&#93;&#41; ≈ c&#91;1:2:end&#93; &amp;&amp; imag.&#40;b&#91;1:end÷2&#93;&#41; ≈ c&#91;2:2:end&#93;
endt &#61; range&#40;-10, 10; length&#61;1024&#41;
y &#61; @. exp&#40;-t^2&#41;
noise &#61; rand&#40;1024&#41;
test_signal&#40;y .&#43; noise&#41;</code></pre>
<pre><code class="language-julia">true</code></pre>
<pre><code class="language-julia">t &#61; range&#40;-10, 10; length&#61;1024&#41;
y &#61; @. sin&#40;t&#41;
noise &#61; rand&#40;1024&#41;
test_signal&#40;y .&#43; noise&#41;</code></pre>
<pre><code class="language-julia">true</code></pre>
<p>
</div>
</div><p>These simplifications and special cases allow our implementation to gain a lot in speed. This makes the implementation of FFTW all the more remarkable, as it still performs very well&#33;</p>
<table class="fndef" id="fndef:fftw">
    <tr>
        <td class="fndef-backref">[6]</td>
        <td class="fndef-content">Frigo, Matteo &amp; Johnson, S.G.. &#40;2005&#41;. The Design and implementation of FFTW3. Proceedings of the IEEE. 93. 216 - 231. 10.1109/JPROC.2004.840301.</td>
    </tr>
</table><hr />
<p>At the end of this tutorial I hope to have helped you to understand the mechanisms that make the FFT computation work, and to have shown how to implement it efficiently, modulo some simplifications. Personally, writing this tutorial has allowed me to realize the great qualities of FFTW, the reference implementation, that I use every day in my work&#33;</p>
<p>This should allow you to understand that for some use cases, it can be interesting to implement and optimize your own FFT. An application that has been little discussed in this tutorial is the calculation of convolution products. An efficient method when convolving signals of comparable length is to do so by multiplying the two Fourier transforms and then taking the inverse Fourier transform. In this case, since the multiplication is done term by term, it is not necessary that the Fourier transform is ordered. One could therefore imagine a special implementation that would skip the reverse bit permutation part.</p>
<p>Another improvement that could be made concerns the calculation of the inverse Fourier transform. It is a very similar calculation &#40;only the multiplicative coefficients change&#41;, and can be a good exercise to experiment with the codes given in this tutorial.</p>
<p>Finally, I want to thank @Gawaboumga, @Næ, @zeqL and @luxera for their feedback on the beta of this tutorial, and @Gabbro for the validation on <a href="https://zestedesavoir.com">zestedesavoir.com</a>&#33;</p>
 ]]>
  </content:encoded>
    
  <pubDate>Sat, 12 Feb 2022 00:00:00 +0000</pubDate>  
  
  
  <atom:author>
    <atom:name>Hugo Levy-Falk</atom:name>
  </atom:author>
        
</item>
</channel></rss>