<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.3.2">Jekyll</generator><link href="https://www.wedesoft.de/atom.xml" rel="self" type="application/atom+xml" /><link href="https://www.wedesoft.de/" rel="alternate" type="text/html" /><updated>2026-06-03T21:20:34+01:00</updated><id>https://www.wedesoft.de/atom.xml</id><title type="html">Jan Wedekind</title><subtitle>Software engineering, game development, simulation.</subtitle><author><name>Jan Wedekind</name></author><entry><title type="html">Machine learning using Clojure, libpython-clj2, and Pytorch</title><link href="https://www.wedesoft.de/ai/2026/05/26/mlp/" rel="alternate" type="text/html" title="Machine learning using Clojure, libpython-clj2, and Pytorch" /><published>2026-05-26T00:00:00+01:00</published><updated>2026-05-26T00:00:00+01:00</updated><id>https://www.wedesoft.de/ai/2026/05/26/mlp</id><content type="html" xml:base="https://www.wedesoft.de/ai/2026/05/26/mlp/"><![CDATA[<p><em>(Cross posting article published at <a href="https://clojurecivitas.org/mlp/main.html">Clojure Civitas</a>)</em></p>

<h2 id="motivation">Motivation</h2>

<p>The Clojure programming language has desirable properties for software engineering such as immutability and strong support for implementing parallel algorithms.
Currently Python is popular for machine learning due to Pytorch and other machine learning libraries targeting the Python programming language.
However using <a href="https://github.com/clj-python/libpython-clj">libpython-clj2</a> one can invoke Pytorch for machine learning from within Clojure.</p>

<p>This article demonstrates a few basics of machine learning using the $y=x^2$ parabola function as an example.</p>

<h2 id="import-pytorch">Import PyTorch</h2>

<p>The PyTorch library is quite comprehensive, is free software, and you can find a lot of documentation on how to use it.
The default version of <a href="https://pypi.org/project/torch/">PyTorch on pypi.org</a> comes with CUDA (Nvidia) GPU support.
There are also <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/3rd-party/pytorch-install.html#use-a-wheels-package">PyTorch wheels provided by AMD</a> which come with <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/quick-start.html">ROCm</a> support.
Here we are going to use a CPU version of PyTorch which is a much smaller install.</p>

<p>You need to install <a href="https://www.python.org/">Python 3.10</a> or later.
For package management we are going to use the <a href="https://docs.astral.sh/uv/">uv</a> package manager.
The following <em>pyproject.toml</em> file is used to install PyTorch and NumPy.</p>

<figure class="highlight"><pre><code class="language-toml" data-lang="toml"><span class="nn">[project]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"mlp"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">description</span> <span class="p">=</span> <span class="s">"Provision Pytorch CPU"</span>
<span class="py">authors</span> <span class="p">=</span> <span class="p">[</span><span class="err">{</span> <span class="py">name</span><span class="p">=</span><span class="s">"Jan Wedekind"</span><span class="p">,</span> <span class="py">email</span><span class="p">=</span><span class="s">"jan@wedesoft.de"</span> <span class="err">}</span><span class="p">]</span>
<span class="py">requires-python</span> <span class="p">=</span> <span class="py">"&gt;</span><span class="p">=</span><span class="mf">3.10</span><span class="err">.</span><span class="mi">0</span><span class="s">"</span><span class="err">
</span><span class="py">dependencies</span> <span class="p">=</span> <span class="p">[</span>
    <span class="s">"numpy"</span><span class="p">,</span>
    <span class="s">"torch"</span><span class="p">,</span>
<span class="p">]</span>

<span class="nn">[tool.uv]</span>
<span class="py">python-preference</span> <span class="p">=</span> <span class="s">"only-system"</span>

<span class="nn">[tool.uv.sources]</span>
<span class="nn">torch</span> <span class="o">=</span> <span class="p">{</span> <span class="py">index</span> <span class="p">=</span> <span class="s">"pytorch"</span> <span class="p">}</span>
<span class="nn">numpy</span> <span class="o">=</span> <span class="p">{</span> <span class="py">index</span> <span class="p">=</span> <span class="s">"pytorch"</span> <span class="p">}</span>

<span class="nn">[[tool.uv.index]]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"pytorch"</span>
<span class="py">url</span> <span class="p">=</span> <span class="s">"https://download.pytorch.org/whl/cpu"</span>

<span class="nn">[build-system]</span>
<span class="py">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s">"setuptools"</span><span class="p">,</span> <span class="s">"wheel"</span><span class="p">]</span>
<span class="py">build-backend</span> <span class="p">=</span> <span class="s">"setuptools.build_meta"</span></code></pre></figure>

<p>Note that we are specifying a custom repository index to get the CPU-only version of PyTorch.
Also we are using the system version of Python to prevent <em>uv</em> from trying to install its own version which lacks the <em>_cython</em> module.
To freeze the dependencies and create a <em>uv.lock</em> file, you need to run</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">uv lock</code></pre></figure>

<p>You can install the dependencies using</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">uv <span class="nb">sync</span></code></pre></figure>

<p>In order to access PyTorch from Clojure you need to run the <code class="language-plaintext highlighter-rouge">clj</code> command via <code class="language-plaintext highlighter-rouge">uv</code>:</p>

<figure class="highlight"><pre><code class="language-bash" data-lang="bash">uv run clj</code></pre></figure>

<p>Now you should be able to import the Python modules using <em>require-python</em>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">tablecloth.api</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">tc</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">scicloj.tableplot.v1.plotly</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">plotly</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">libpython-clj2.require</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">require-python</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">libpython-clj2.python</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">py.-</span><span class="p">)</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">py</span><span class="p">])</span><span class="w">

</span><span class="p">(</span><span class="nf">require-python</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">builtins</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">python</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">torch</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.nn</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">nn</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.utils.data</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">data</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.nn.functional</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">F</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.optim</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">optim</span><span class="p">])</span></code></pre></figure>

<h2 id="the-training-data">The training data</h2>

<p>First we are going to set the random seed for reproducibility of this article.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">torch/manual_seed</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span></code></pre></figure>

<p>We are going to sample a small set of points from the interval $[-2, +2]$ and add Gaussian noise to the labels.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">extent</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/sub</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/mul</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/rand</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="mi">1</span><span class="p">])</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">extent</span><span class="p">))</span><span class="w"> </span><span class="n">extent</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/add</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/mul</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">features</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/mul</span><span class="w"> </span><span class="n">noise</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/randn</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="mi">1</span><span class="p">]))))</span><span class="w">
</span><span class="p">(</span><span class="nf">torch/squeeze</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span><span class="c1">; tensor([ 1.0305, -0.8828, -0.3877,  0.9387, -1.8829,  1.1994, -0.4115,  1.0175,</span><span class="w">
</span><span class="c1">;          0.2780, -0.2449,  0.5547,  0.0987,  0.7305, -0.7794, -0.1458, -0.1801,</span><span class="w">
</span><span class="c1">;          0.2899, -0.0080,  1.7483,  0.6224, -0.7448, -1.2079, -0.3352, -0.8627,</span><span class="w">
</span><span class="c1">;         -0.6409,  0.0958,  1.1923,  1.0871, -1.9551,  1.2398,  0.5587,  1.8971,</span><span class="w">
</span><span class="c1">;          1.3201, -1.8223, -1.9016, -0.9647,  1.7562, -0.3331,  0.8559, -0.9294,</span><span class="w">
</span><span class="c1">;          1.9624, -0.8462,  1.4998,  0.0237, -1.0536,  1.0280, -1.0616,  0.5882,</span><span class="w">
</span><span class="c1">;         -0.5775, -0.2193, -1.9228, -0.9536,  1.0853, -0.4862,  1.9921,  1.6032,</span><span class="w">
</span><span class="c1">;         -0.0936, -1.3350,  1.2179,  0.6207, -1.2928,  1.2991,  1.2142,  1.7738])</span><span class="w">
</span><span class="p">(</span><span class="nf">torch/squeeze</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
</span><span class="c1">; tensor([ 1.0556e+00,  5.2343e-01,  1.2828e-03,  6.2985e-01,  3.4926e+00,</span><span class="w">
</span><span class="c1">;          1.4368e+00,  5.8765e-01,  1.0379e+00, -9.8686e-02,  1.3654e-02,</span><span class="w">
</span><span class="c1">;          5.8658e-02, -1.9808e-01,  4.1831e-01,  4.6745e-01,  1.2016e-01,</span><span class="w">
</span><span class="c1">;         -2.1315e-01, -4.2587e-02,  2.5008e-02,  2.8932e+00,  5.7028e-01,</span><span class="w">
</span><span class="c1">;          1.9615e-01,  1.3339e+00,  1.5529e-01,  7.0422e-01,  4.7441e-01,</span><span class="w">
</span><span class="c1">;         -1.1632e-01,  1.1612e+00,  1.3648e+00,  3.5603e+00,  1.4195e+00,</span><span class="w">
</span><span class="c1">;          3.8496e-01,  4.0967e+00,  1.9081e+00,  3.6182e+00,  3.8203e+00,</span><span class="w">
</span><span class="c1">;          7.0220e-01,  3.4306e+00, -9.2481e-02,  5.0070e-01,  1.1418e+00,</span><span class="w">
</span><span class="c1">;          4.1850e+00,  8.6712e-01,  2.2237e+00, -3.7243e-02,  5.8463e-01,</span><span class="w">
</span><span class="c1">;          9.0184e-01,  7.5752e-01,  6.2636e-02,  5.5197e-01, -9.1986e-02,</span><span class="w">
</span><span class="c1">;          4.0185e+00,  1.1135e+00,  1.2291e+00,  3.1262e-01,  4.1024e+00,</span><span class="w">
</span><span class="c1">;          2.4624e+00,  6.4830e-01,  1.7238e+00,  1.4800e+00,  8.5044e-01,</span><span class="w">
</span><span class="c1">;          1.1763e+00,  2.1373e+00,  1.4997e+00,  3.2313e+00])</span></code></pre></figure>

<p>Next we are going to combine features and labels into a PyTorch dataset.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">dataset</span><span class="w"> </span><span class="p">(</span><span class="nf">data/TensorDataset</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="p">))</span></code></pre></figure>

<p>In order to check that the model generalises well, one usually splits up the available data into a training, development and test set.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">train-size</span><span class="w"> </span><span class="p">(</span><span class="nb">int</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.6</span><span class="w"> </span><span class="n">n</span><span class="p">)))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">dev-size</span><span class="w"> </span><span class="p">(</span><span class="nb">int</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.2</span><span class="w"> </span><span class="n">n</span><span class="p">)))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">test-size</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="n">train-size</span><span class="w"> </span><span class="n">dev-size</span><span class="p">))</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">random_split</code> function is used to randomly split the dataset.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">splits</span><span class="w"> </span><span class="p">(</span><span class="nf">data/random_split</span><span class="w"> </span><span class="n">dataset</span><span class="w"> </span><span class="p">[</span><span class="n">train-size</span><span class="w"> </span><span class="n">dev-size</span><span class="w"> </span><span class="n">test-size</span><span class="p">]))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">train-ds</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">splits</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">dev-ds</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">splits</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">test-ds</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">splits</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span></code></pre></figure>

<p>The datasets are used in the following way:</p>
<ul>
  <li><em>training data</em> is used to train the model</li>
  <li><em>development data</em> is used to check that the model is not overfitting or underfitting</li>
  <li><em>test data</em> is used to report the performance of the model</li>
</ul>

<p>Next we are going to use PyTorch DataLoaders to split training and development data into mini-batches.
The data loaders are also used to shuffle the data sets.
Shuffling can be quite important for stability of the training process.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">train-data-loader</span><span class="w"> </span><span class="p">(</span><span class="nf">data/DataLoader</span><span class="w"> </span><span class="n">train-ds</span><span class="w"> </span><span class="no">:batch_size</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:shuffle</span><span class="w"> </span><span class="n">true</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="w"> </span><span class="p">(</span><span class="nf">data/DataLoader</span><span class="w"> </span><span class="n">dev-ds</span><span class="w"> </span><span class="no">:batch_size</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:shuffle</span><span class="w"> </span><span class="n">true</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">test-data-loader</span><span class="w"> </span><span class="p">(</span><span class="nf">data/DataLoader</span><span class="w"> </span><span class="n">test-ds</span><span class="w"> </span><span class="no">:batch_size</span><span class="w"> </span><span class="n">test-size</span><span class="w"> </span><span class="no">:shuffle</span><span class="w"> </span><span class="n">true</span><span class="p">))</span></code></pre></figure>

<h2 id="the-model">The Model</h2>

<p>In Python you can implement a small neural network with an input layer, two hidden layers, and an output layer as follows:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">class</span> <span class="nc">ParabolaNet</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">n_hidden</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">().</span><span class="nf">__init__</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc1</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="mi">1</span><span class="p">,</span> <span class="n">n_hidden</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc2</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="n">n_hidden</span><span class="p">,</span> <span class="n">n_hidden</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc3</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="n">n_hidden</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>

    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">fc1</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="nf">sigmoid</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="nf">dropout</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">p</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">dropout_rate</span><span class="p">,</span> <span class="n">training</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">training</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">fc2</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="nf">sigmoid</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">F</span><span class="p">.</span><span class="nf">dropout</span><span class="p">(</span><span class="n">x</span><span class="p">,</span> <span class="n">p</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">dropout_rate</span><span class="p">,</span> <span class="n">training</span><span class="o">=</span><span class="n">self</span><span class="p">.</span><span class="n">training</span><span class="p">)</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">fc3</span><span class="p">(</span><span class="n">x</span><span class="p">)</span>
        <span class="k">return</span> <span class="n">x</span></code></pre></figure>

<p>Using <em>libpython-clj2</em> one can create the Python class from within Clojure.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ParabolaNet</span><span class="w">
  </span><span class="p">(</span><span class="nf">py/create-class</span><span class="w">
    </span><span class="s">"ParabolaNet"</span><span class="w"> </span><span class="p">[</span><span class="n">nn/Module</span><span class="p">]</span><span class="w">
    </span><span class="p">{</span><span class="s">"__init__"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="n">dropout-rate</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">nn/Module</span><span class="w"> </span><span class="n">__init__</span><span class="w"> </span><span class="n">self</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">py/set-attrs!</span><span class="w">
             </span><span class="n">self</span><span class="w">
             </span><span class="p">{</span><span class="s">"dropout_rate"</span><span class="w"> </span><span class="n">dropout-rate</span><span class="w">
              </span><span class="s">"fc1"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">n-hidden</span><span class="p">)</span><span class="w">
              </span><span class="s">"fc2"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="n">n-hidden</span><span class="p">)</span><span class="w">
              </span><span class="s">"fc3"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="mi">1</span><span class="p">)})</span><span class="w">
           </span><span class="n">nil</span><span class="p">))</span><span class="w">
     </span><span class="s">"forward"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc1</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">F/sigmoid</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">F/dropout</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="no">:p</span><span class="w"> </span><span class="p">(</span><span class="nf">py.-</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">dropout_rate</span><span class="p">)</span><span class="w"> </span><span class="no">:training</span><span class="w"> </span><span class="p">(</span><span class="nf">py.-</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">training</span><span class="p">))</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc2</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">F/sigmoid</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">F/dropout</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="no">:p</span><span class="w"> </span><span class="p">(</span><span class="nf">py.-</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">dropout_rate</span><span class="p">)</span><span class="w"> </span><span class="no">:training</span><span class="w"> </span><span class="p">(</span><span class="nf">py.-</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">training</span><span class="p">))</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc3</span><span class="w"> </span><span class="n">x</span><span class="p">)]</span><span class="w">
             </span><span class="n">x</span><span class="p">)))}))</span></code></pre></figure>

<p>Each arrow represents a weight stored in the fully connected layers fc1, fc2, and fc3.
There are different layer types in PyTorch.
A fully connected layer is the most basic one.
To be able to model non-linear functions, activation functions are used (here: sigmoid).
The model looks like this when using 8 units in each hidden layer.</p>

<p><img src="/pics/network.svg" alt="network to approximate parabola" /></p>

<p>When evaluating a model, you should disable gradient accumulation.
Otherwise gradients will leak into subsequent training steps.
In Python this looks like this:</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">no_grad</span><span class="p">():</span>
    <span class="bp">...</span></code></pre></figure>

<p>In Clojure we can define a macro to disable gradient accumulation:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">without-gradient</span><span class="w">
  </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">body</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">no-grad</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/no_grad</span><span class="p">)]</span><span class="w">
     </span><span class="p">(</span><span class="nf">try</span><span class="w">
       </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">no-grad</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="ss">'__enter__</span><span class="p">)</span><span class="w">
       </span><span class="o">~@</span><span class="n">body</span><span class="w">
       </span><span class="p">(</span><span class="nf">finally</span><span class="w">
         </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">no-grad</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="ss">'__exit__</span><span class="w"> </span><span class="n">nil</span><span class="w"> </span><span class="n">nil</span><span class="w"> </span><span class="n">nil</span><span class="p">)))))</span></code></pre></figure>

<p>Now one can perform a model prediction as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="p">(</span><span class="nf">ParabolaNet</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="nb">eval</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">model</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tensor</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="p">])))</span><span class="w">
</span><span class="c1">; tensor([-0.0908])</span></code></pre></figure>

<p>The following function plots all data points (training, development, and test set) and the model predictions for different inputs.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">plot-model</span><span class="w">
  </span><span class="p">[</span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="n">model</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
    </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="nb">eval</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w">   </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">extent</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">extent</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
          </span><span class="n">y</span><span class="w">   </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">model</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tensor</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">])))</span><span class="w"> </span><span class="n">item</span><span class="p">))</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
          </span><span class="n">ds</span><span class="w">  </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="n">y</span><span class="p">})</span><span class="w">
          </span><span class="n">pts</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">py/-&gt;jvm</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">tolist</span><span class="p">)))</span><span class="w">
                           </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">py/-&gt;jvm</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="n">tolist</span><span class="p">)))})]</span><span class="w">
      </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">ds</span><span class="w">
          </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Model"</span><span class="p">})</span><span class="w">
          </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=dataset</span><span class="w"> </span><span class="n">pts</span><span class="w"> </span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="no">:=name</span><span class="w"> </span><span class="s">"data"</span><span class="p">})</span><span class="w">
          </span><span class="p">(</span><span class="nf">plotly/layer-line</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="no">:=name</span><span class="w"> </span><span class="s">"prediction"</span><span class="p">})))))</span></code></pre></figure>

<p>Before training, the model should just be a random function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-model</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="n">model</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/untrained.png" alt="untrained network" /></p>

<p>Using the test set, we can report the performance of the model.
Note that it is good practice to use a separate test set to report performance, because hyperparameter tuning using the dev set can overfit the model to the dev set.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">report-perf</span><span class="w">
  </span><span class="p">[</span><span class="n">model</span><span class="w"> </span><span class="n">test-data-loader</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
    </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[[</span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">test-data-loader</span><span class="p">)</span><span class="w">
          </span><span class="n">prediction</span><span class="w">        </span><span class="p">(</span><span class="nf">model</span><span class="w"> </span><span class="n">features</span><span class="p">)</span><span class="w">
          </span><span class="n">errors</span><span class="w">            </span><span class="p">(</span><span class="nf">torch/sub</span><span class="w"> </span><span class="n">prediction</span><span class="w"> </span><span class="n">labels</span><span class="p">)]</span><span class="w">
      </span><span class="p">{</span><span class="no">:n</span><span class="w"> </span><span class="p">(</span><span class="nf">builtins/len</span><span class="w"> </span><span class="n">errors</span><span class="p">)</span><span class="w">
       </span><span class="no">:min</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/min</span><span class="w"> </span><span class="n">errors</span><span class="p">)</span><span class="w"> </span><span class="n">item</span><span class="p">)</span><span class="w">
       </span><span class="no">:mean</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/mean</span><span class="w"> </span><span class="n">errors</span><span class="p">)</span><span class="w"> </span><span class="n">item</span><span class="p">)</span><span class="w">
       </span><span class="no">:max</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/max</span><span class="w"> </span><span class="n">errors</span><span class="p">)</span><span class="w"> </span><span class="n">item</span><span class="p">)</span><span class="w">
       </span><span class="no">:std</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/std</span><span class="w"> </span><span class="n">errors</span><span class="p">)</span><span class="w"> </span><span class="n">item</span><span class="p">)})))</span><span class="w">
</span><span class="p">(</span><span class="nf">report-perf</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="n">test-data-loader</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:n 14,</span><span class="w">
</span><span class="c1">;  :min -3.883366346359253,</span><span class="w">
</span><span class="c1">;  :mean -0.9666337966918945,</span><span class="w">
</span><span class="c1">;  :max 0.1055092141032219,</span><span class="w">
</span><span class="c1">;  :std 1.0836751461029053}</span></code></pre></figure>

<p>Note that the output is not zero.
PyTorch performs random initialisation of the weights for us.
Breaking the symmetry like this is important, otherwise all activations and gradients will be the same and the model will not be able to learn.</p>

<h2 id="training">Training</h2>

<p>A training epoch performs the following step for each mini-batch in the training set:</p>
<ul>
  <li>reset the gradient of the optimizer</li>
  <li>perform model predictions for the input features</li>
  <li>compute the loss which compares the predictions with the labels</li>
  <li>perform backpropagation to get gradients for each model parameter</li>
  <li>perform a gradient descent step using the optimizer</li>
  <li>return the loss value</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">train-epoch</span><span class="w">
  </span><span class="p">[</span><span class="n">train-data-loader</span><span class="w"> </span><span class="n">criterion</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="n">optimizer</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="n">train</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[[</span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="p">]</span><span class="w"> </span><span class="n">train-data-loader</span><span class="p">]</span><span class="w">
       </span><span class="p">(</span><span class="nf">do</span><span class="w">
         </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">optimizer</span><span class="w"> </span><span class="n">zero_grad</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">prediction</span><span class="w"> </span><span class="p">(</span><span class="nf">model</span><span class="w"> </span><span class="n">features</span><span class="p">)</span><span class="w">
               </span><span class="n">loss</span><span class="w">       </span><span class="p">(</span><span class="nf">criterion</span><span class="w"> </span><span class="n">prediction</span><span class="w"> </span><span class="n">labels</span><span class="p">)]</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">loss</span><span class="w"> </span><span class="n">backward</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">optimizer</span><span class="w"> </span><span class="n">step</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">loss</span><span class="w"> </span><span class="n">item</span><span class="p">)))))</span></code></pre></figure>

<p>In order to check whether the model is overfitting or underfitting, we need to evaluate the model on the development set.
The following method computes the loss values for each mini-batch in the development set.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">dev-epoch</span><span class="w">
  </span><span class="p">[</span><span class="n">dev-data-loader</span><span class="w"> </span><span class="n">criterion</span><span class="w"> </span><span class="n">model</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="nb">eval</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
    </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[[</span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="p">]</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="p">]</span><span class="w">
         </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">prediction</span><span class="w"> </span><span class="p">(</span><span class="nf">model</span><span class="w"> </span><span class="n">features</span><span class="p">)</span><span class="w">
               </span><span class="n">loss</span><span class="w">       </span><span class="p">(</span><span class="nf">criterion</span><span class="w"> </span><span class="n">prediction</span><span class="w"> </span><span class="n">labels</span><span class="p">)]</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">loss</span><span class="w"> </span><span class="n">item</span><span class="p">)))))</span></code></pre></figure>

<p>We define a function to compute the average of a list of numbers.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">average</span><span class="w"> </span><span class="p">[</span><span class="n">numbers</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="n">numbers</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">numbers</span><span class="p">)))</span></code></pre></figure>

<p>Now we can implement a training run.
A training run basically consists of many training epochs.
Here we are using the stochastic gradient descent method (SGD).
Note that usually the Adam optimizer is used, because it is more efficient.
As a loss function we simply use the mean squared error (MSE).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">training-run</span><span class="w">
  </span><span class="p">[</span><span class="n">train-data-loader</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="w"> </span><span class="n">epochs</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="n">lr</span><span class="w"> </span><span class="n">dropout-rate</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">model</span><span class="w">     </span><span class="p">(</span><span class="nf">ParabolaNet</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="n">dropout-rate</span><span class="p">)</span><span class="w">
        </span><span class="n">optimizer</span><span class="w"> </span><span class="p">(</span><span class="nf">optim/SGD</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="s">"parameters"</span><span class="p">)</span><span class="w"> </span><span class="no">:lr</span><span class="w"> </span><span class="n">lr</span><span class="p">)</span><span class="w">
        </span><span class="n">criterion</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/MSELoss</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">loop</span><span class="w"> </span><span class="p">[</span><span class="n">epoch</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">train-losses</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="n">dev-losses</span><span class="w"> </span><span class="p">[]]</span><span class="w">
          </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">train-loss</span><span class="w"> </span><span class="p">(</span><span class="nf">average</span><span class="w"> </span><span class="p">(</span><span class="nf">train-epoch</span><span class="w"> </span><span class="n">train-data-loader</span><span class="w"> </span><span class="n">criterion</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="n">optimizer</span><span class="p">))</span><span class="w">
                </span><span class="n">dev-loss</span><span class="w">   </span><span class="p">(</span><span class="nf">average</span><span class="w"> </span><span class="p">(</span><span class="nf">dev-epoch</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="w"> </span><span class="n">criterion</span><span class="w"> </span><span class="n">model</span><span class="p">))]</span><span class="w">
            </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">&lt;</span><span class="w"> </span><span class="n">epoch</span><span class="w"> </span><span class="n">epochs</span><span class="p">)</span><span class="w">
              </span><span class="p">(</span><span class="nf">recur</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">epoch</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">train-losses</span><span class="w"> </span><span class="n">train-loss</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">dev-losses</span><span class="w"> </span><span class="n">dev-loss</span><span class="p">))</span><span class="w">
              </span><span class="p">{</span><span class="no">:model</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="no">:train-losses</span><span class="w"> </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">train-losses</span><span class="w"> </span><span class="n">train-loss</span><span class="p">)</span><span class="w"> </span><span class="no">:dev-losses</span><span class="w"> </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">dev-losses</span><span class="w"> </span><span class="n">dev-loss</span><span class="p">)})))))</span></code></pre></figure>

<p>Let’s train a model.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">learning-rate</span><span class="w"> </span><span class="mf">0.1</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">num-epochs</span><span class="w"> </span><span class="mi">20000</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">training-run</span><span class="w"> </span><span class="n">train-data-loader</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="w"> </span><span class="n">num-epochs</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="n">learning-rate</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span></code></pre></figure>

<h2 id="hyperparameter-tuning">Hyperparameter tuning</h2>

<p>Let’s plot our model.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-model</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="p">(</span><span class="no">:model</span><span class="w"> </span><span class="n">result</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/variance.png" alt="overfitted network" /></p>

<p>As one can see, the model is overfitting the training set.
I.e. the model fits the training data closely but does not generalize well.
This makes the model sensitive to noise.</p>

<p>Here one can observe overfitting directly.
In general however one uses the training and dev loss to detect if the model is overfitting.</p>

<p>First we define a smoothing function which smoothes a sequence of loss values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">smoothing</span><span class="w">
  </span><span class="p">[</span><span class="n">alpha</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">coll</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nf">reductions</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">prev-avg</span><span class="w"> </span><span class="n">current</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">alpha</span><span class="w"> </span><span class="n">prev-avg</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">alpha</span><span class="p">)</span><span class="w"> </span><span class="n">current</span><span class="p">)))</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="n">coll</span><span class="p">)))</span></code></pre></figure>

<p>Next we define a function to plot the training loss (learning curve) and validation loss over time.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">plot-losses</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">train-losses</span><span class="w"> </span><span class="n">dev-losses</span><span class="p">]}</span><span class="w"> </span><span class="n">smoothing-fn</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">train-losses</span><span class="p">))</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nf">smoothing-fn</span><span class="w"> </span><span class="n">train-losses</span><span class="p">)})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Losses"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-line</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="no">:=name</span><span class="w"> </span><span class="s">"training loss"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-line</span><span class="w"> </span><span class="p">{</span><span class="no">:=dataset</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">dev-losses</span><span class="p">))</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nf">smoothing-fn</span><span class="w"> </span><span class="n">dev-losses</span><span class="p">)})</span><span class="w">
                          </span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="no">:=name</span><span class="w"> </span><span class="s">"dev loss"</span><span class="p">})))</span></code></pre></figure>

<p>Let’s plot the losses without smoothing.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-losses</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">smoothing</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/loss-raw.png" alt="raw loss values" /></p>

<p>In practice one uses smoothing to make the trend of the curves more visible.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-losses</span><span class="w"> </span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">smoothing</span><span class="w"> </span><span class="mf">0.99</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/loss-smooth.png" alt="smooth loss values" /></p>

<p>Of particular interest are the loss values at the end of the training process.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="p">(</span><span class="no">:train-losses</span><span class="w"> </span><span class="n">result</span><span class="p">))</span><span class="w">
</span><span class="c1">; 0.034154752967879176</span><span class="w">
</span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="p">(</span><span class="no">:dev-losses</span><span class="w"> </span><span class="n">result</span><span class="p">))</span><span class="w">
</span><span class="c1">; 0.13410548369089761</span></code></pre></figure>

<p>As one can see, the final dev set loss is more than twice the amount of the training loss.
This is a sign that the model is overfitting (high variance).</p>

<p><strong>High variance</strong> can be resolved using the following techniques:</p>
<ul>
  <li>use more data</li>
  <li>regularization</li>
  <li>neural network architecture search</li>
</ul>

<p>We can report the performance using the test set.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">report-perf</span><span class="w"> </span><span class="p">(</span><span class="no">:model</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w"> </span><span class="n">test-data-loader</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:n 14,</span><span class="w">
</span><span class="c1">;  :min -0.30802178382873535,</span><span class="w">
</span><span class="c1">;  :mean 0.11105262488126755,</span><span class="w">
</span><span class="c1">;  :max 0.5724084377288818,</span><span class="w">
</span><span class="c1">;  :std 0.2797224223613739}</span></code></pre></figure>

<p>Let’s see what underfitting looks like.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">result2</span><span class="w"> </span><span class="p">(</span><span class="nf">training-run</span><span class="w"> </span><span class="n">train-data-loader</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="w"> </span><span class="n">num-epochs</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">learning-rate</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">plot-model</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="p">(</span><span class="no">:model</span><span class="w"> </span><span class="n">result2</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/bias.png" alt="underfitted network" /></p>

<p>Let’s look at the losses.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-losses</span><span class="w"> </span><span class="n">result2</span><span class="w"> </span><span class="p">(</span><span class="nf">smoothing</span><span class="w"> </span><span class="mf">0.99</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/loss-bias.png" alt="underfitted network" /></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="p">(</span><span class="no">:train-losses</span><span class="w"> </span><span class="n">result2</span><span class="p">))</span><span class="w">
</span><span class="c1">; 0.8137271299958229</span><span class="w">
</span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="p">(</span><span class="no">:dev-losses</span><span class="w"> </span><span class="n">result2</span><span class="p">))</span><span class="w">
</span><span class="c1">; 1.8136223355929058</span></code></pre></figure>

<p>As one can see, both the training and dev set loss are high.
This is a sign that the model is underfitting (high bias).</p>

<p><strong>High bias</strong> can be resolved using the following techniques:</p>
<ul>
  <li>bigger network</li>
  <li>train longer</li>
  <li>neural network architecture search</li>
</ul>

<p>We can report the performance using the test set.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">report-perf</span><span class="w"> </span><span class="p">(</span><span class="no">:model</span><span class="w"> </span><span class="n">result2</span><span class="p">)</span><span class="w"> </span><span class="n">test-data-loader</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:n 14,</span><span class="w">
</span><span class="c1">;  :min -2.526854991912842,</span><span class="w">
</span><span class="c1">;  :mean 0.6758203506469727,</span><span class="w">
</span><span class="c1">;  :max 1.5044240951538086,</span><span class="w">
</span><span class="c1">;  :std 1.0056211948394775}</span></code></pre></figure>

<h2 id="regularization">Regularization</h2>

<p>Instead of tuning the number of hidden units and layers (which can only be done in discrete steps), one can use regularization.</p>

<p>Here we are using dropout regularization which randomly sets some activations to zero during training.
After trying out a few values, I found 0.05 to be a good dropout rate for this dataset size and model.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">result3</span><span class="w"> </span><span class="p">(</span><span class="nf">training-run</span><span class="w"> </span><span class="n">train-data-loader</span><span class="w"> </span><span class="n">dev-data-loader</span><span class="w"> </span><span class="n">num-epochs</span><span class="w"> </span><span class="n">n-hidden</span><span class="w"> </span><span class="n">learning-rate</span><span class="w"> </span><span class="mf">0.05</span><span class="p">))</span></code></pre></figure>

<p>Here is the model output.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-model</span><span class="w"> </span><span class="n">features</span><span class="w"> </span><span class="n">labels</span><span class="w"> </span><span class="p">(</span><span class="no">:model</span><span class="w"> </span><span class="n">result3</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/regularized.png" alt="regularized network" /></p>

<p>And the losses are as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">plot-losses</span><span class="w"> </span><span class="n">result3</span><span class="w"> </span><span class="p">(</span><span class="nf">smoothing</span><span class="w"> </span><span class="mf">0.99</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/loss-regularized.png" alt="loss of regularized network" /></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="p">(</span><span class="no">:train-losses</span><span class="w"> </span><span class="n">result3</span><span class="p">))</span><span class="w">
</span><span class="c1">; 0.08883395940065383</span><span class="w">
</span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="p">(</span><span class="no">:dev-losses</span><span class="w"> </span><span class="n">result3</span><span class="p">))</span><span class="w">
</span><span class="c1">; 0.06939788286884625</span></code></pre></figure>

<p>Again we can report the performance using the test set.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">report-perf</span><span class="w"> </span><span class="p">(</span><span class="no">:model</span><span class="w"> </span><span class="n">result3</span><span class="p">)</span><span class="w"> </span><span class="n">test-data-loader</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:n 14,</span><span class="w">
</span><span class="c1">;  :min -0.376054048538208,</span><span class="w">
</span><span class="c1">;  :mean -0.01708267442882061,</span><span class="w">
</span><span class="c1">;  :max 0.34027233719825745,</span><span class="w">
</span><span class="c1">;  :std 0.20343200862407684}</span></code></pre></figure>

<h2 id="learning-rate">Learning Rate</h2>

<p>We did not explore different learning rates, but this hyperparameter is usually straightforward to tune:</p>

<ul>
  <li>The learning rate is too low if the model converges very slowly.</li>
  <li>The learning rate is too high if the loss becomes unstable and the parameters diverge.</li>
</ul>

<p>Enjoy! :)</p>]]></content><author><name>Jan Wedekind</name></author><category term="ai" /><summary type="html"><![CDATA[(Cross posting article published at Clojure Civitas)]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/parabola.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/parabola.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">X-Plane 12 Citation-X Checklist</title><link href="https://www.wedesoft.de/simulation/2026/05/10/x-plane-citation-x-checklist/" rel="alternate" type="text/html" title="X-Plane 12 Citation-X Checklist" /><published>2026-05-10T00:00:00+01:00</published><updated>2026-05-10T00:00:00+01:00</updated><id>https://www.wedesoft.de/simulation/2026/05/10/x-plane-citation-x-checklist</id><content type="html" xml:base="https://www.wedesoft.de/simulation/2026/05/10/x-plane-citation-x-checklist/"><![CDATA[<style>
.image-container {
  position: relative;
  width: 100%;
  display: inline-block;
}
#cockpit-image {
  display: block;
}
.image-container, #cockpit-image {
  padding: 0;
  margin: 0;
  border: none;
}
#bounding-box {
  position: absolute;
  border: 3px solid #ffeb3b;
  background-color: rgba(255, 235, 59, 0.2);
  display: none;
  box-sizing: border-box;
  transition: all 0.3s ease;
}
.trigger-link {
  color: #268bd2;
  text-decoration: none;
  user-select: none; 
  -webkit-user-select: none;
  -ms-user-select: none;
  cursor: pointer;
}
.trigger-link:hover,
.trigger-link:focus {
  text-decoration: underline;
}
.scroll-box {
  width: 100%;        /* Define the width of your box */
  height: 8lh;
  overflow-y: auto;    /* Adds a vertical scrollbar only when necessary */
  border: 1px solid #ccc;
  padding: 10px;
}
</style>

<p>If you are trying out the Citation-X with X-Plane 12, I can really recommend the <a href="https://forums.x-plane.org/files/file/20785-xchecklist-linwinmac/">XChecklist plugin</a> and the <a href="https://forums.x-plane.org/files/file/90301-xchecklist-for-laminar-citation-x/">checklist file for the Citation-X</a>.
To help you with finding all the different controls, I created this interactive page.</p>

<div class="image-container">
    <img id="cockpit-image" src="/pics/citation-x-1.jpg" alt="cockpit view" />
    <div id="bounding-box"></div>
</div>

<script>
  const img_element = document.getElementById('cockpit-image');
  const box_element = document.getElementById('bounding-box');

  function cockpit_show(url, orig_width, orig_height, x, y, w, h) {
    img_element.src = url;
    const left = (x / orig_width) * 100;
    const top = (y / orig_height) * 100;
    const width = (w / orig_width) * 100;
    const height = (h / orig_height) * 100;
    box_element.style.display = 'block';
    box_element.style.left = left + '%';
    box_element.style.top = top + '%';
    box_element.style.width = width + '%';
    box_element.style.height = height + '%';
  }
</script>

<div class="scroll-box">
<h4>PRELIMINARY</h4>
<ul>
<li>Aircraft Doors: OPEN</li>
<li>Parking Brake: SET</li>
<li>Landing Gear Handle: DOWN</li>
<li>Speedbrake Handle: CHECK ZERO</li>
<li>Throttles: CUT OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 926, 957, 88, 86)">Battery Switches 1 &amp; 2</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1166, 962, 37, 81)">Eicas Switch</span>: ON</li>
<li>Battery Volts (Elec Page): CHECK 24</li>
<li>Refuel: AS NEEDED</li>
<li>Fuel Balance: CHECK IMBALANCE &lt;200 lbs</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1166, 962, 37, 81)">Eicas Switch</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 926, 957, 88, 86)">Battery Switches 1 &amp; 2</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1112, 1134, 108, 76)">Standby Power</span>: TEST</li>
<li>Passengers &amp; Cargo: BOARDED &amp; CHECK</li>
</ul>

<h4>COCKPIT PREPARATION 1</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 926, 957, 88, 86)">Battery Switches 1 &amp; 2</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1061, 958, 43, 85)">External Power</span> if Available: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 880, 959, 45, 81)">Left</span> &amp; <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1016, 958, 43, 81)">Right Generator</span>: UP</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1166, 962, 37, 81)">Eicas Switch</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1112, 960, 46, 83)">Avionics Switch</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 850, 1057, 101, 75)">Fuel Boost</span>: BOTH NORM or ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 971, 1140, 53, 74)">FADEC Left Switch</span>: RESET &amp; NORMAL</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1038, 1140, 53, 74)">FADEC Right Switch</span>: RESET &amp; NORMAL</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1115, 1048, 111, 81)">Ignition Switches</span>: CHECK NORM</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1109, 1134, 112, 79)">Standby Power</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 569, 1054, 99, 75)">Center Wing Transfer</span>: BOTH NORM</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 361, 961, 78, 80)">Emergency Exit Lights</span>: ON (Red Light On)</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 361, 961, 78, 80)">Emergency Exit Lights</span>: ARM</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 352, 1051, 154, 165)">Instrument Lights</span> (FLOOD LTS, EL, LH, CTR): SET AS NEED</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 16, 1100, 117, 103)">Passenger Oxygen</span>: AUTO</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 728, 258, 89, 71)">Master Warning</span>: RESET</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 817, 258, 89, 71)">Master Caution</span>: RESET</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1144, 1041, 90, 84)">Windshield Heater LH &amp; RH</span>: ON</li>
</ul>

<h4>COCKPIT PREPARATION 2</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1464, 957, 47, 82)">Aux Pump A</span>: ON</li>
<li>Parking Brake: OFF</li>
<li>Parking Brake: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1464, 957, 47, 82)">Aux Pump A</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1593, 1052, 67, 78)">Seat Belt Lights</span>: UP</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1495, 1137, 51, 77)">Recognition Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1549, 1137, 51, 77)">Navigation Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1601, 1137, 59, 69)">Tail Flood Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2048, 1147, 269, 87)">Engine Bleed Air</span>: BOTH HP/LP</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2100, 1250, 80, 92)">Righthand Panel Lights</span>: SET</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1355, 855, 62, 99)">APU Master</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1295, 846, 44, 86)">Test Button</span>: CHECK</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1234, 822, 58, 98)">APU Start</span>: HOLD TILL START</li>
<li>APU N1: 100 RPM</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1354, 581, 74, 110)">APU Generator</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1173, 542, 77, 117)">APU Bleed Air</span>: MAX COOL</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2224, 1061, 94, 86)">Cabin</span> and <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2039, 1059, 95, 88)">Cockpit Packs</span>: ON</li>
</ul>

<h4>PREFLIGHT PROCEDURE</h4>
<ul>
<li>Aircraft Doors: CLOSED</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1061, 958, 43, 85)">External Power</span> if Available: DISCONNECTED</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1442, 523, 170, 178)">Rotary Test</span>: ROTATE TO ALL POSITIONS</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1464, 957, 47, 82)">Aux Pump A</span>: ON</li>
<li>Flaps &amp; Slats: RETRACT</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1464, 957, 47, 82)">Aux Pump A</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 661, 384, 116, 34)">PFD Source Selection</span>: SELECT FMS or NAV</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1578, 108, 79, 179)">Lateral Navigation Mode</span>: NAV</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1744, 112, 67, 44)">Vertical Navigation Mode</span>: FLC</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1827, 99, 67, 188)">Flight Level Change Target Airspeed</span>: 180 KIAS</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1911, 157, 70, 44)">Yaw Damper</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1903, 203, 68, 43)">Mach Trim</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 699, 254, 43, 29)">Transponder Frequency</span>: SET</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 696, 284, 43, 29)">Transponder Mode</span>: STANDBY</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 959, 1254, 126, 110)">Heading</span>: SET TO RUNWAY</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1345, 1250, 97, 105)">Altitude Select</span>: SET TO CLEARED ALT</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 930, 885, 49, 63)">Altimeter Baro</span>: SET LOCAL QNH</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 632, 885, 49, 63)">Minimums BARO PFD</span>: SET TO 1000FT AGL</li>
</ul>

<h4>ENGINE START</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1530, 1048, 64, 81)">GND REC/ANTI-COLL Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2224, 1061, 94, 86)">Cabin</span> and <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2039, 1059, 95, 88)">Cockpit Packs</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1052, 1067, 48, 51)">Engine Start RH Button</span>: PRESS</li>
<li>RH N2%: WAIT &gt;10</li>
<li>Right Power Lever: IDLE</li>
<li>RH N2%: WAIT &gt;60</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 969, 1067, 48, 51)">Engine Start LH Button</span>: PRESS</li>
<li>LH N2%: WAIT &gt;10</li>
<li>Left Power Lever: IDLE</li>
<li>Eicas Fuel Page: CHECK FLOW &gt;500 PPH</li>
<li>Eicas Hyd Page: CHECK PSI&gt;3000</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2048, 1147, 269, 87)">Engine Bleed Air</span>: BOTH LP</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2224, 1061, 94, 86)">Cabin</span> and <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2039, 1059, 95, 88)">Cockpit Packs</span>: ON</li>
</ul>

<h4>BEFORE TAXI</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1782, 1124, 159, 85)">Pressurization Switches</span>: ALL THREE NORM</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1839, 944, 180, 151)">Pressurization Altitude</span>: SET</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1145, 955, 90, 85)">Pitot Static Switches</span>: BOTH UP</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1475, 752, 61, 55)">Flaps 5 or 15</span>: SET</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1475, 671, 63, 78)">Pitch Trim</span>: SET GREEN</li>
<li>Rudder &amp; Aileron Trim: CHECK NEUTRAL</li>
<li>Yoke Roll Control: FREE MOVEMENT</li>
<li>Yoke Pitch Control: FREE MOVEMENT</li>
<li>Rudder Control: FREE MOVEMENT</li>
<li>Speedbrake Handle: CHECK MOVEMENT THEN ZERO</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1212, 1052, 68, 117)">Taxi Light</span>: ON</li>
</ul>

<h4>TAXI</h4>
<ul>
<li>Parking Brake: RELEASED</li>
<li>Parking Brake: SET</li>
</ul>

<h4>BEFORE TAKEOFF</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1530, 1048, 64, 81)">GND REC/ANTI-COLL Lights</span>: UP</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1281, 1052, 116, 121)">Landing Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1212, 1052, 68, 117)">Taxi Light</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 696, 284, 43, 29)">Transponder</span>: ALT</li>
<li>Parking Brake: RELEASED</li>
<li>Pedal Brakes: FULL PRESS</li>
</ul>

<h4>TAKEOFF</h4>
<ul>
<li>Throttle Lever: SET 40% N1</li>
<li>Both Engines Simultaneous: CHECK</li>
<li>Pedal Brakes: RELEASE</li>
<li>Throttle Lever: MAX POSITION T/O</li>
<li>At Vr Speed: ROTATE</li>
</ul>

<h4>AFTER TAKEOFF</h4>
<ul>
<li>==&gt;: Positive Rating Climb</li>
<li>Landing Gear: UP</li>
<li>==&gt;: Above 1000ft AGl</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1924, 113, 64, 45)">Autopilot</span>: ENGAGE</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1744, 112, 67, 44)">Vertical Navigation Mode</span>: FLC or VNAV</li>
<li>Flaps &amp; Slats: UP AS REQUIRED</li>
<li>Throttle Lever: CLB POSITION</li>
<li>Cabin Pressurization: CHECK POSITIVE FPM</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 930, 885, 49, 63)">Altimeter Baro</span>: STD AT TRANS ALT</li>
<li>==&gt;: Above 10000ft</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1281, 1052, 116, 121)">Landing Lights</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1593, 1052, 67, 78)">Seat Belt Lights</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1173, 542, 77, 117)">APU Bleed Air</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1354, 581, 74, 110)">APU Generator</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1234, 822, 58, 98)">APU Start</span>: HOLD DOWN</li>
<li>==&gt;: APU RPM ZERO</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1355, 855, 62, 99)">APU Master</span>: OFF</li>
<li>==&gt;: Above 18000ft</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1041, 1055, 182, 120)">ENG SYNC</span>: FAN</li>
</ul>

<h4>CRUISE</h4>
<ul>
<li>BANK: AS REQUIRED</li>
<li>Throttle Lever: CRU POSITION</li>
</ul>

<h4>DESCENT</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1345, 1250, 97, 105)">Altitude Knob</span>: SET TO CLEARED ALT</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1744, 112, 67, 44)">Vertical Navigation Mode</span>: SET VNAV, FLC or VS</li>
<li>==&gt;: Below 18000ft</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1041, 1055, 182, 120)">ENG SYNC</span>: OFF</li>
<li>BANK: AS REQUIRED</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1355, 855, 62, 99)">APU Master</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1234, 822, 58, 98)">APU Start</span>: HOLD TILL START</li>
<li>APU N1 100%: READY</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1354, 581, 74, 110)">APU Generator</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1173, 542, 77, 117)">APU Bleed Air</span>: MAX COOL</li>
<li>==&gt;: Below 10000ft</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1281, 1052, 116, 121)">Landing Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1593, 1052, 67, 78)">Seat Belt Lights</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 930, 885, 49, 63)">Altimeter Baro</span>: SET LOCAL QNH</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 632, 885, 49, 63)">RA/BARO Minimums</span>: SET</li>
</ul>

<h4>APPROACH &amp; LANDING</h4>
<ul>
<li>==&gt;: SPEED &lt;250 KIAS</li>
<li>Slats &amp; Flaps: FLAPS 5</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1584, 204, 66, 41)">Autopilot Approach Mode</span>: ARM</li>
<li>==&gt;: SPEED &lt;210 KIAS</li>
<li>Flaps: FLAPS 15</li>
<li>Landing Gear: DOWN 3 GREEN</li>
<li>==&gt;: SPEED 180 KIAS</li>
<li>Flaps: FLAPS 35</li>
<li>==&gt;: AGL &lt;500 ft</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1924, 113, 64, 45)">AP</span> &amp; <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1911, 157, 70, 44)">YD</span>: DISCONNECT</li>
<li>Throttle Lever: IDLE</li>
<li>Reverse Throttle: IF NECESSARY</li>
<li>==&gt;: GND SPEED &lt;50 KIAS</li>
<li>Reverse Throttle: OFF</li>
</ul>

<h4>AFTER LANDING</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1145, 955, 90, 85)">Pitot Static Heat</span>: BOTH OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1530, 1048, 64, 81)">GND REC/ANTI-COLL Lights</span>: GND REC</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 696, 284, 43, 29)">Transponder Mode</span>: STANDBY</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1212, 1052, 68, 117)">Taxi Light</span>: ON</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1281, 1052, 116, 121)">Landing Lights</span>: OFF</li>
<li>Speedbrakes: RETRACTED</li>
<li>Flaps: UP</li>
</ul>

<h4>SHUTDOWN</h4>
<ul>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-3.jpg', 2560, 1440, 1212, 1052, 68, 117)">Taxi Light</span>: OFF</li>
<li>Parking Brake: SET</li>
<li>Throttles: CUT OFF</li>
<li>Engines N2%: Below 10%</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1530, 1048, 64, 81)">GND REC/ANTI-COLL Lights</span>: OFF</li>
<li>Aircraft Doors: OPEN</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1593, 1052, 67, 78)">Seat Belts</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1173, 542, 77, 117)">APU Bleed Air</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1354, 581, 74, 110)">APU Generator</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1234, 822, 58, 98)">APU Start</span>: HOLD DOWN</li>
<li>==&gt;: APU RPM ZERO</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-4.jpg', 2560, 1440, 1355, 855, 62, 99)">APU Master</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2048, 1147, 269, 87)">Engine Bleed Air</span>: BOTH OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2224, 1061, 94, 86)">Cabin</span> and <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 2039, 1059, 95, 88)">Cockpit Packs</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1495, 1137, 51, 77)">Recognition Lights</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1549, 1137, 51, 77)">Navigation Lights</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1601, 1137, 59, 69)">Tail Flood Lights</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-2.jpg', 2560, 1440, 1144, 1041, 90, 84)">Windshield Heater LH &amp; RH</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 16, 1100, 117, 103)">Passenger Oxygen</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 361, 961, 78, 80)">Emergency Exit Lights</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 569, 1054, 99, 75)">Center Wing Transfer</span>: BOTH OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 850, 1057, 101, 75)">Fuel Boost</span>: BOTH OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1109, 1134, 112, 79)">Standby Power</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1112, 960, 46, 83)">Avionics Switch</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1166, 962, 37, 81)">Eicas Switch</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 880, 959, 45, 81)">Left</span> &amp; <span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 1016, 958, 43, 81)">Right Generator</span>: OFF</li>
<li><span class="trigger-link" onclick="cockpit_show('/pics/citation-x-1.jpg', 2560, 1440, 926, 957, 88, 86)">Battery Switches 1 &amp; 2</span>: BOTH OFF</li>
</ul>

</div>

<h4>Autopilot explanation</h4>
<p>Vertical flight path</p>
<ul>
  <li><strong>FLC</strong>: <em>Flight Level Change</em> climbs/descends to the target altitude while keeping the target speed.</li>
  <li><strong>VS</strong>: <em>Vertical Speed</em> climb/descends to the target altitude using a specified target rate.</li>
  <li><strong>VNAV</strong>: <em>Vertical Navigation</em> the flight management system (FMS) controls the altitude but never descends below the target altitude.</li>
  <li><strong>ALT</strong>: <em>Altitude Hold</em> Keep the autopilot at the current altitude. This mode is entered automatically by some of the other modes when the target altitude is reached.</li>
</ul>

<p>Lateral navigation</p>
<ul>
  <li><strong>HDG</strong>: <em>Heading</em> tells the autopilot to turn to the target heading.</li>
  <li><strong>NAV</strong>: <em>Lateral Navigation</em> uses the flight management system (FMS) or the VOR/ILS for controlling the heading.</li>
</ul>

<p>Both</p>
<ul>
  <li><strong>APP</strong>: <em>Approach</em> arms the ILS approach. The airplane should be below the ILS glide path so that the autopilot can start descending when encountering the glide path.</li>
</ul>

<p>Other</p>
<ul>
  <li><strong>AP</strong>: Engage/Disengage autopilot but keep displaying guidance information.</li>
  <li><strong>STBY</strong>: Disengage autopilot and clear guidance information.</li>
  <li><strong>BANK</strong>: Toggle reduced maximum bank angle used by autopilot.</li>
  <li><strong>C/O</strong>: <em>Change Over</em> switch between displaying airspeed in IAS (Indicated Airspeed in knots) and Mach number. Mach number display is used at high altitudes (e.g. when climbing above 25000 feet (flight level 250)).</li>
  <li><strong>BC</strong>: <em>Back Course</em> is a legacy mode allowing to use the back course of an old ILS.</li>
</ul>]]></content><author><name>Jan Wedekind</name></author><category term="simulation" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/cockpit.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/cockpit.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Proximal Policy Optimization with Clojure and PyTorch</title><link href="https://www.wedesoft.de/ai/2026/04/22/proximal-policy-optimization/" rel="alternate" type="text/html" title="Proximal Policy Optimization with Clojure and PyTorch" /><published>2026-04-22T00:00:00+01:00</published><updated>2026-04-22T00:00:00+01:00</updated><id>https://www.wedesoft.de/ai/2026/04/22/proximal-policy-optimization</id><content type="html" xml:base="https://www.wedesoft.de/ai/2026/04/22/proximal-policy-optimization/"><![CDATA[<p><em>(Cross posting article published at <a href="https://clojurecivitas.org/ppo/main.html">Clojure Civitas</a>)</em></p>

<h2 id="motivation">Motivation</h2>

<p>Recently I started to look into the problem of reentry trajectory planning in the context of developing the <a href="https://store.steampowered.com/app/3687560/sfsim/">sfsim</a> space flight simulator.
I had looked into reinforcement learning before and even tried out Q-learning using the <a href="https://gymnasium.farama.org/environments/box2d/lunar_lander/">lunar lander reference environment of OpenAI’s gym library</a> (now maintained by the Farama Foundation).
However it had stability issues.
The algorithm would converge on a strategy and then suddenly diverge again.</p>

<p>More recently (2017) the <a href="https://arxiv.org/abs/1707.06347">Proximal Policy Optimization (PPO) algorithm was published</a> and it has gained in popularity.
PPO is inspired by Trust Region Policy Optimization (TRPO) but is much easier to implement.
Also PPO handles continuous observation and action spaces which is important for control problems.
The <a href="https://github.com/DLR-RM/stable-baselines3">Stable Baselines3</a> Python library has a implementation of PPO, TRPO, and other reinforcement learning algorithms.
However I found <a href="https://github.com/XinJingHao/PPO-Continuous-Pytorch/">XinJingHao’s PPO implementation</a> which is easier to follow.</p>

<p>In order to use PPO with a simulation environment implemented in Clojure and also in order to get a better understanding of PPO, I dediced to do an implementation of PPO in Clojure.</p>

<h2 id="dependencies">Dependencies</h2>

<p>For this project we are using the following <code class="language-plaintext highlighter-rouge">deps.edn</code> file.
The Python setup is shown further down in this article.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w">
 </span><span class="p">{</span><span class="n">org.clojure/clojure</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.12.4"</span><span class="p">}</span><span class="w">
  </span><span class="n">clj-python/libpython-clj</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"2.026"</span><span class="p">}</span><span class="w">
  </span><span class="n">quil/quil</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"4.3.1563"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.clojure/core.async</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.9.865"</span><span class="p">}}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>The dependencies can be pulled in using the following statement.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.math</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">PI</span><span class="w"> </span><span class="n">cos</span><span class="w"> </span><span class="n">sin</span><span class="w"> </span><span class="n">exp</span><span class="w"> </span><span class="n">to-radians</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.core.async</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">async</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tablecloth.api</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">tc</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">scicloj.tableplot.v1.plotly</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">plotly</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">quil.core</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">q</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">quil.middleware</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">m</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">libpython-clj2.require</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">require-python</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">libpython-clj2.python</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="p">)</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">py</span><span class="p">])</span></code></pre></figure>

<h2 id="pendulum-environment">Pendulum Environment</h2>

<p><img src="/pics/pendulum.png" alt="screenshot of pendulum environment" /></p>

<p>To validate the implementation, we will implement the classical <a href="https://gymnasium.farama.org/environments/classic_control/pendulum/">pendulum</a> environment in Clojure.
In order to be able to switch environments, we define a protocol according to the environment abstract class used in OpenAI’s gym.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">defprotocol</span><span class="w"> </span><span class="n">Environment</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-update</span><span class="w"> </span><span class="p">[</span><span class="n">this</span><span class="w"> </span><span class="n">action</span><span class="p">])</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-observation</span><span class="w"> </span><span class="p">[</span><span class="n">this</span><span class="p">])</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-done?</span><span class="w"> </span><span class="p">[</span><span class="n">this</span><span class="p">])</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-truncate?</span><span class="w"> </span><span class="p">[</span><span class="n">this</span><span class="p">])</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-reward</span><span class="w"> </span><span class="p">[</span><span class="n">this</span><span class="w"> </span><span class="n">action</span><span class="p">]))</span></code></pre></figure>

<p>Here is a configuration for testing the pendulum.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">frame-rate</span><span class="w"> </span><span class="mi">20</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">config</span><span class="w">
  </span><span class="p">{</span><span class="no">:length</span><span class="w">  </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">)</span><span class="w">
   </span><span class="no">:max-speed</span><span class="w"> </span><span class="mf">8.0</span><span class="w">
   </span><span class="no">:motor</span><span class="w"> </span><span class="mf">6.0</span><span class="w">
   </span><span class="no">:gravitation</span><span class="w"> </span><span class="mf">10.0</span><span class="w">
   </span><span class="no">:dt</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="n">frame-rate</span><span class="p">)</span><span class="w">
   </span><span class="no">:save</span><span class="w"> </span><span class="n">false</span><span class="w">
   </span><span class="no">:timeout</span><span class="w"> </span><span class="mf">10.0</span><span class="w">
   </span><span class="no">:angle-weight</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
   </span><span class="no">:velocity-weight</span><span class="w"> </span><span class="mf">0.1</span><span class="w">
   </span><span class="no">:control-weight</span><span class="w"> </span><span class="mf">0.0001</span><span class="p">})</span></code></pre></figure>

<h3 id="setup">Setup</h3>

<p>A method to initialise the pendulum is defined.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup</span><span class="w">
  </span><span class="s">"Initialise pendulum"</span><span class="w">
  </span><span class="p">[</span><span class="n">angle</span><span class="w"> </span><span class="n">velocity</span><span class="p">]</span><span class="w">
  </span><span class="p">{</span><span class="no">:angle</span><span class="w">          </span><span class="n">angle</span><span class="w">
   </span><span class="no">:velocity</span><span class="w">       </span><span class="n">velocity</span><span class="w">
   </span><span class="no">:t</span><span class="w">              </span><span class="mf">0.0</span><span class="p">})</span></code></pre></figure>

<p>Same as in OpenAI’s gym the angle is zero when the pendulum is pointing up.
Here a pendulum is initialised to be pointing down and have an angular velocity of 0.5 radians per second.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">setup</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:angle 3.141592653589793, :velocity 0.5, :t 0.0}</span></code></pre></figure>

<h3 id="state-updates">State Updates</h3>

<p>The angular acceleration due to gravitation is implemented as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">pendulum-gravity</span><span class="w">
  </span><span class="s">"Determine angular acceleration due to gravity"</span><span class="w">
  </span><span class="p">[</span><span class="n">gravitation</span><span class="w"> </span><span class="n">length</span><span class="w"> </span><span class="n">angle</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">sin</span><span class="w"> </span><span class="n">angle</span><span class="p">)</span><span class="w"> </span><span class="n">gravitation</span><span class="p">)</span><span class="w"> </span><span class="n">length</span><span class="p">))</span></code></pre></figure>

<p>The angular acceleration depends on the gravitation, length of pendulum, and angle of pendulum.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">pendulum-gravity</span><span class="w"> </span><span class="mf">9.81</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; 0.0</span><span class="w">
</span><span class="p">(</span><span class="nf">pendulum-gravity</span><span class="w"> </span><span class="mf">9.81</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
</span><span class="c1">; 9.81</span><span class="w">
</span><span class="p">(</span><span class="nf">pendulum-gravity</span><span class="w"> </span><span class="mf">9.81</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
</span><span class="c1">; 4.905</span></code></pre></figure>

<p>The motor is controlled using an input value between -1 and 1.
This value is simply multiplied with the maximum angular acceleration provided by the motor.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">motor-acceleration</span><span class="w">
  </span><span class="s">"Angular acceleration from motor"</span><span class="w">
  </span><span class="p">[</span><span class="n">control</span><span class="w"> </span><span class="n">motor-acceleration</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">control</span><span class="w"> </span><span class="n">motor-acceleration</span><span class="p">))</span></code></pre></figure>

<p>A simulation step of the pendulum is implemented using Euler integration.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">update-state</span><span class="w">
  </span><span class="s">"Perform simulation step of pendulum"</span><span class="w">
  </span><span class="p">([{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">angle</span><span class="w"> </span><span class="n">velocity</span><span class="w"> </span><span class="n">t</span><span class="p">]}</span><span class="w">
    </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">control</span><span class="p">]}</span><span class="w">
    </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">dt</span><span class="w"> </span><span class="n">motor</span><span class="w"> </span><span class="n">gravitation</span><span class="w"> </span><span class="n">length</span><span class="w"> </span><span class="n">max-speed</span><span class="p">]}]</span><span class="w">
   </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gravity</span><span class="w">        </span><span class="p">(</span><span class="nf">pendulum-gravity</span><span class="w"> </span><span class="n">gravitation</span><span class="w"> </span><span class="n">length</span><span class="w"> </span><span class="n">angle</span><span class="p">)</span><span class="w">
         </span><span class="n">motor</span><span class="w">          </span><span class="p">(</span><span class="nf">motor-acceleration</span><span class="w"> </span><span class="n">control</span><span class="w"> </span><span class="n">motor</span><span class="p">)</span><span class="w">
         </span><span class="n">t</span><span class="w">              </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="n">dt</span><span class="p">)</span><span class="w">
         </span><span class="n">acceleration</span><span class="w">   </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">motor</span><span class="w"> </span><span class="n">gravity</span><span class="p">)</span><span class="w">
         </span><span class="n">velocity</span><span class="w">       </span><span class="p">(</span><span class="nb">max</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">max-speed</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nb">min</span><span class="w"> </span><span class="n">max-speed</span><span class="w">
                                  </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">velocity</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">acceleration</span><span class="w"> </span><span class="n">dt</span><span class="p">))))</span><span class="w">
         </span><span class="n">angle</span><span class="w">          </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">angle</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">velocity</span><span class="w"> </span><span class="n">dt</span><span class="p">))]</span><span class="w">
     </span><span class="p">{</span><span class="no">:angle</span><span class="w">          </span><span class="n">angle</span><span class="w">
      </span><span class="no">:velocity</span><span class="w">       </span><span class="n">velocity</span><span class="w">
      </span><span class="no">:t</span><span class="w">              </span><span class="n">t</span><span class="p">})))</span></code></pre></figure>

<p>Here are a few examples for advancing the state in different situations.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="no">:t</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:angle 3.141592653589793, :velocity 9.184850993605151E-17, :t 0.05}</span><span class="w">
</span><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.1</span><span class="w"> </span><span class="no">:t</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:angle 3.146592653589793, :velocity 0.1000000000000001, :t 0.05}</span><span class="w">
</span><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="no">:t</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:angle 1.6082963267948966, :velocity 0.75, :t 0.05}</span><span class="w">
</span><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="no">:t</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="mf">1.0</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:angle 0.015000000000000003, :velocity 0.30000000000000004, :t 0.05}</span></code></pre></figure>

<h3 id="observation">Observation</h3>

<p>The observation of the pendulum state uses cosinus and sinus of the angle to resolve the wrap around problem of angles.
The angular speed is normalized to be between -1 and 1 as well.
This so called <a href="https://en.wikipedia.org/wiki/Feature_scaling">feature scaling</a> is done in order to improve convergence.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">observation</span><span class="w">
  </span><span class="s">"Get observation from state"</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">angle</span><span class="w"> </span><span class="n">velocity</span><span class="p">]}</span><span class="w"> </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">max-speed</span><span class="p">]}]</span><span class="w">
  </span><span class="p">[(</span><span class="nf">cos</span><span class="w"> </span><span class="n">angle</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">sin</span><span class="w"> </span><span class="n">angle</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">velocity</span><span class="w"> </span><span class="n">max-speed</span><span class="p">)])</span></code></pre></figure>

<p>The observation of the pendulum is a vector with 3 elements.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">observation</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; [1.0 0.0 0.0]</span><span class="w">
</span><span class="p">(</span><span class="nf">observation</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.5</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; [1.0 0.0 0.0625]</span><span class="w">
</span><span class="p">(</span><span class="nf">observation</span><span class="w"> </span><span class="p">{</span><span class="no">:angle</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="no">:velocity</span><span class="w"> </span><span class="mf">0.0</span><span class="p">}</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
</span><span class="c1">; [6.123233995736766E-17 1.0 0.0]</span></code></pre></figure>

<p>Note that the observation needs to capture all information required for achieving the objective, because it is the only information available to the actor for deciding on the next action.</p>

<h3 id="action">Action</h3>

<p>The action of a pendulum is a vector with one element between 0 and 1.
The following method clips it and converts it to an action hashmap used by the pendulum environment.
Note that an action can consist of several values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">action</span><span class="w">
  </span><span class="s">"Convert array to action"</span><span class="w">
  </span><span class="p">[</span><span class="n">array</span><span class="p">]</span><span class="w">
  </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="p">(</span><span class="nb">max</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">min</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">array</span><span class="p">))</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)))})</span></code></pre></figure>

<p>The following examples show how the action vector is mapped to a control input between -1 and 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="p">])</span><span class="w">
</span><span class="c1">; {:control -1.0}</span><span class="w">
</span><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="p">[</span><span class="mf">0.5</span><span class="p">])</span><span class="w">
</span><span class="c1">; {:control 0.0}</span><span class="w">
</span><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">])</span><span class="w">
</span><span class="c1">; {:control 1.0}</span></code></pre></figure>

<h3 id="termination">Termination</h3>

<p>The truncate method is used to stop a pendulum run after a specific amount of time.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">truncate?</span><span class="w">
  </span><span class="s">"Decide whether a run should be aborted"</span><span class="w">
  </span><span class="p">([{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">t</span><span class="p">]}</span><span class="w"> </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">timeout</span><span class="p">]}]</span><span class="w">
   </span><span class="p">(</span><span class="nb">&gt;=</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="n">timeout</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">truncate?</span><span class="w"> </span><span class="p">{</span><span class="no">:t</span><span class="w"> </span><span class="mf">50.0</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="no">:timeout</span><span class="w"> </span><span class="mf">100.0</span><span class="p">})</span><span class="w">
</span><span class="c1">; false</span><span class="w">
</span><span class="p">(</span><span class="nf">truncate?</span><span class="w"> </span><span class="p">{</span><span class="no">:t</span><span class="w"> </span><span class="mf">100.0</span><span class="p">}</span><span class="w"> </span><span class="p">{</span><span class="no">:timeout</span><span class="w"> </span><span class="mf">100.0</span><span class="p">})</span><span class="w">
</span><span class="c1">; true</span></code></pre></figure>

<p>It is also possible to define a termination condition.
For the pendulum environment we specify that it never terminates.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">done?</span><span class="w">
  </span><span class="s">"Decide whether pendulum achieved target state"</span><span class="w">
  </span><span class="p">([</span><span class="n">_state</span><span class="w"> </span><span class="n">_config</span><span class="p">]</span><span class="w">
   </span><span class="n">false</span><span class="p">))</span></code></pre></figure>

<h3 id="reward">Reward</h3>

<p>The following method normalizes an angle to be between -PI and +PI.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">normalize-angle</span><span class="w">
  </span><span class="s">"Angular deviation from up angle"</span><span class="w">
  </span><span class="p">[</span><span class="n">angle</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">angle</span><span class="w"> </span><span class="n">PI</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span></code></pre></figure>

<p>We also need the square of a number.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sqr</span><span class="w">
  </span><span class="s">"Square of number"</span><span class="w">
  </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">x</span><span class="p">))</span></code></pre></figure>

<p>The reward function penalises deviation from the upright position, non-zero velocities, and non-zero control input.
Note that it is important that the reward function is continuous because machine learning uses gradient descent.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">reward</span><span class="w">
  </span><span class="s">"Reward function"</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">angle</span><span class="w"> </span><span class="n">velocity</span><span class="p">]}</span><span class="w">
   </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">angle-weight</span><span class="w"> </span><span class="n">velocity-weight</span><span class="w"> </span><span class="n">control-weight</span><span class="p">]}</span><span class="w">
   </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">control</span><span class="p">]}]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">angle-weight</span><span class="w"> </span><span class="p">(</span><span class="nf">sqr</span><span class="w"> </span><span class="p">(</span><span class="nf">normalize-angle</span><span class="w"> </span><span class="n">angle</span><span class="p">)))</span><span class="w">
        </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">velocity-weight</span><span class="w"> </span><span class="p">(</span><span class="nf">sqr</span><span class="w"> </span><span class="n">velocity</span><span class="p">))</span><span class="w">
        </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">control-weight</span><span class="w"> </span><span class="p">(</span><span class="nf">sqr</span><span class="w"> </span><span class="n">control</span><span class="p">)))))</span></code></pre></figure>

<h3 id="environment-protocol">Environment Protocol</h3>

<p>Finally we are able to implement the pendulum as a generic environment.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">defrecord</span><span class="w"> </span><span class="n">Pendulum</span><span class="w"> </span><span class="p">[</span><span class="n">config</span><span class="w"> </span><span class="n">state</span><span class="p">]</span><span class="w">
  </span><span class="n">Environment</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-update</span><span class="w"> </span><span class="p">[</span><span class="n">_this</span><span class="w"> </span><span class="n">input</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="nf">-&gt;Pendulum</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="n">input</span><span class="p">)</span><span class="w"> </span><span class="n">config</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-observation</span><span class="w"> </span><span class="p">[</span><span class="n">_this</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="nf">observation</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-done?</span><span class="w"> </span><span class="p">[</span><span class="n">_this</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="nf">done?</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-truncate?</span><span class="w"> </span><span class="p">[</span><span class="n">_this</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="nf">truncate?</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">environment-reward</span><span class="w"> </span><span class="p">[</span><span class="n">_this</span><span class="w"> </span><span class="n">input</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="nf">reward</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="n">input</span><span class="p">))))</span></code></pre></figure>

<p>The following factory method creates an environment with an initial random state covering all possible pendulum states.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">pendulum-factory</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">angle</span><span class="w">     </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w"> </span><span class="n">PI</span><span class="p">)</span><span class="w">
        </span><span class="n">max-speed</span><span class="w"> </span><span class="p">(</span><span class="no">:max-speed</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
        </span><span class="n">velocity</span><span class="w">  </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">max-speed</span><span class="p">))</span><span class="w"> </span><span class="n">max-speed</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">-&gt;Pendulum</span><span class="w"> </span><span class="n">config</span><span class="w"> </span><span class="p">(</span><span class="nf">setup</span><span class="w"> </span><span class="n">angle</span><span class="w"> </span><span class="n">velocity</span><span class="p">))))</span></code></pre></figure>

<h3 id="visualisation">Visualisation</h3>

<p>The following method is used to draw the pendulum and visualise the motor control input.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">draw-state</span><span class="w"> </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">angle</span><span class="p">]}</span><span class="w"> </span><span class="p">{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">control</span><span class="p">]}]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">origin-x</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nf">q/width</span><span class="p">)</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
        </span><span class="n">origin-y</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nf">q/height</span><span class="p">)</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
        </span><span class="n">length</span><span class="w">     </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">(</span><span class="nf">q/height</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:length</span><span class="w"> </span><span class="n">config</span><span class="p">))</span><span class="w">
        </span><span class="n">pendulum-x</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">origin-x</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">length</span><span class="w"> </span><span class="p">(</span><span class="nf">sin</span><span class="w"> </span><span class="n">angle</span><span class="p">)))</span><span class="w">
        </span><span class="n">pendulum-y</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">origin-y</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">length</span><span class="w"> </span><span class="p">(</span><span class="nf">cos</span><span class="w"> </span><span class="n">angle</span><span class="p">)))</span><span class="w">
        </span><span class="n">size</span><span class="w">       </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.05</span><span class="w"> </span><span class="p">(</span><span class="nf">q/height</span><span class="p">))</span><span class="w">
        </span><span class="n">arc-radius</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">abs</span><span class="w"> </span><span class="n">control</span><span class="p">)</span><span class="w"> </span><span class="mf">0.2</span><span class="w"> </span><span class="p">(</span><span class="nf">q/height</span><span class="p">))</span><span class="w">
        </span><span class="n">positive</span><span class="w">   </span><span class="p">(</span><span class="nb">pos?</span><span class="w"> </span><span class="n">control</span><span class="p">)</span><span class="w">
        </span><span class="n">tip-angle</span><span class="w">  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">positive</span><span class="w"> </span><span class="mi">225</span><span class="w"> </span><span class="mi">-45</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/frame-rate</span><span class="w"> </span><span class="n">frame-rate</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/background</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/stroke-weight</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/stroke</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/fill</span><span class="w"> </span><span class="mi">175</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/line</span><span class="w"> </span><span class="n">origin-x</span><span class="w"> </span><span class="n">origin-y</span><span class="w"> </span><span class="n">pendulum-x</span><span class="w"> </span><span class="n">pendulum-y</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/stroke-weight</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/ellipse</span><span class="w"> </span><span class="n">pendulum-x</span><span class="w"> </span><span class="n">pendulum-y</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/no-fill</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/arc</span><span class="w"> </span><span class="n">origin-x</span><span class="w"> </span><span class="n">origin-y</span><span class="w">
           </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">arc-radius</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">arc-radius</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mi">-45</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mi">225</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/with-translation</span><span class="w"> </span><span class="p">[(</span><span class="nb">+</span><span class="w"> </span><span class="n">origin-x</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">cos</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">tip-angle</span><span class="p">))</span><span class="w"> </span><span class="n">arc-radius</span><span class="p">))</span><span class="w">
                         </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">origin-y</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">sin</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">tip-angle</span><span class="p">))</span><span class="w"> </span><span class="n">arc-radius</span><span class="p">))]</span><span class="w">
      </span><span class="p">(</span><span class="nf">q/with-rotation</span><span class="w"> </span><span class="p">[(</span><span class="nf">to-radians</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">positive</span><span class="w"> </span><span class="mi">225</span><span class="w"> </span><span class="mi">-45</span><span class="p">))]</span><span class="w">
        </span><span class="p">(</span><span class="nf">q/triangle</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">positive</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mi">-10</span><span class="p">)</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="no">:save</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="nf">q/save-frame</span><span class="w"> </span><span class="s">"frame-####.png"</span><span class="p">))))</span></code></pre></figure>

<h3 id="animation">Animation</h3>

<p>With Quil we can create an animation of the pendulum and react to mouse input.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">-main</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">_args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">done-chan</span><span class="w">   </span><span class="p">(</span><span class="nf">async/chan</span><span class="p">)</span><span class="w">
        </span><span class="n">last-action</span><span class="w"> </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="mf">0.0</span><span class="p">})]</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/sketch</span><span class="w">
      </span><span class="no">:title</span><span class="w"> </span><span class="s">"Inverted Pendulum with Mouse Control"</span><span class="w">
      </span><span class="no">:size</span><span class="w"> </span><span class="p">[</span><span class="mi">854</span><span class="w"> </span><span class="mi">480</span><span class="p">]</span><span class="w">
      </span><span class="no">:setup</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">setup</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
      </span><span class="no">:update</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">state</span><span class="p">]</span><span class="w">
                  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">action</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="p">(</span><span class="nb">min</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
                                              </span><span class="p">(</span><span class="nb">max</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                                                   </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nf">q/mouse-x</span><span class="p">)</span><span class="w">
                                                             </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nf">q/width</span><span class="p">)</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)))))}</span><span class="w">
                        </span><span class="n">state</span><span class="w">  </span><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">action</span><span class="w"> </span><span class="n">config</span><span class="p">)]</span><span class="w">
                    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nf">done?</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">async/close!</span><span class="w"> </span><span class="n">done-chan</span><span class="p">))</span><span class="w">
                    </span><span class="p">(</span><span class="nf">reset!</span><span class="w"> </span><span class="n">last-action</span><span class="w"> </span><span class="n">action</span><span class="p">)</span><span class="w">
                    </span><span class="n">state</span><span class="p">))</span><span class="w">
      </span><span class="no">:draw</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">draw-state</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="o">@</span><span class="n">last-action</span><span class="p">)</span><span class="w">
      </span><span class="no">:middleware</span><span class="w"> </span><span class="p">[</span><span class="n">m/fun-mode</span><span class="p">]</span><span class="w">
      </span><span class="no">:on-close</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">_</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">async/close!</span><span class="w"> </span><span class="n">done-chan</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">async/&lt;!!</span><span class="w"> </span><span class="n">done-chan</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">System/exit</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/manual.gif" alt="manually controlled pendulum" /></p>

<h2 id="neural-networks">Neural Networks</h2>

<p>PPO is a machine learning technique using backpropagation to learn the parameters of two neural networks.</p>

<ul>
  <li>The <strong>actor</strong> network takes an observation as an input and outputs the parameters of a probability distribution for sampling the next action to take.</li>
  <li>The <strong>critic</strong> takes an observation as an input and outputs the expected cumulative reward for the current state.</li>
</ul>

<h3 id="import-pytorch">Import PyTorch</h3>

<p>For implementing the neural networks and backpropagation, we can use the Python-Clojure bridge <a href="https://github.com/clj-python/libpython-clj">libpython-clj2</a> and the <a href="https://pytorch.org/">PyTorch</a> machine learning library.
The PyTorch library is quite comprehensive, is free software, and you can find a lot of documentation on how to use it.
The default version of <a href="https://pypi.org/project/torch/">PyTorch on pypi.org</a> comes with CUDA (Nvidia) GPU support.
There are also <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/3rd-party/pytorch-install.html#use-a-wheels-package">PyTorch wheels provided by AMD</a> which come with <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/quick-start.html">ROCm</a> support.
Here we are going to use a CPU version of PyTorch which is a much smaller install.</p>

<p>You need to install <a href="https://www.python.org/">Python 3.10</a> or later.
For package management we are going to use the <a href="https://docs.astral.sh/uv/">uv</a> package manager.
The following <em>pyproject.toml</em> file is used to install PyTorch and NumPy.</p>

<figure class="highlight"><pre><code class="language-toml" data-lang="toml"><span class="nn">[project]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"ppo"</span>
<span class="py">version</span> <span class="p">=</span> <span class="s">"0.1.0"</span>
<span class="py">description</span> <span class="p">=</span> <span class="s">"Proximal Policy Optimization"</span>
<span class="py">authors</span> <span class="p">=</span> <span class="p">[</span><span class="err">{</span> <span class="py">name</span><span class="p">=</span><span class="s">"Jan Wedekind"</span><span class="p">,</span> <span class="py">email</span><span class="p">=</span><span class="s">"jan@wedesoft.de"</span> <span class="err">}</span><span class="p">]</span>
<span class="py">requires-python</span> <span class="p">=</span> <span class="py">"&gt;</span><span class="p">=</span><span class="mf">3.10</span><span class="err">.</span><span class="mi">0</span><span class="s">"</span><span class="err">
</span><span class="py">dependencies</span> <span class="p">=</span> <span class="p">[</span>
    <span class="s">"numpy"</span><span class="p">,</span>
    <span class="s">"torch"</span><span class="p">,</span>
<span class="p">]</span>

<span class="nn">[tool.uv]</span>
<span class="py">python-preference</span> <span class="p">=</span> <span class="s">"only-system"</span>

<span class="nn">[tool.uv.sources]</span>
<span class="nn">torch</span> <span class="o">=</span> <span class="p">{</span> <span class="py">index</span> <span class="p">=</span> <span class="s">"pytorch"</span> <span class="p">}</span>
<span class="nn">numpy</span> <span class="o">=</span> <span class="p">{</span> <span class="py">index</span> <span class="p">=</span> <span class="s">"pytorch"</span> <span class="p">}</span>

<span class="nn">[[tool.uv.index]]</span>
<span class="py">name</span> <span class="p">=</span> <span class="s">"pytorch"</span>
<span class="py">url</span> <span class="p">=</span> <span class="s">"https://download.pytorch.org/whl/cpu"</span>

<span class="nn">[build-system]</span>
<span class="py">requires</span> <span class="p">=</span> <span class="p">[</span><span class="s">"setuptools"</span><span class="p">,</span> <span class="s">"wheel"</span><span class="p">]</span>
<span class="py">build-backend</span> <span class="p">=</span> <span class="s">"setuptools.build_meta"</span></code></pre></figure>

<p>Note that we are specifying a custom repository index to get the CPU-only version of PyTorch.
Also we are using the system version of Python to prevent <em>uv</em> from trying to install its own version which lacks the <em>_cython</em> module.
To freeze the dependencies and create a <em>uv.lock</em> file, you need to run</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">uv lock</code></pre></figure>

<p>You can install the dependencies using</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">uv <span class="nb">sync</span></code></pre></figure>

<p>In order to access PyTorch from Clojure you need to run the <code class="language-plaintext highlighter-rouge">clj</code> command via <code class="language-plaintext highlighter-rouge">uv</code>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">uv run clj</code></pre></figure>

<p>Now you should be able to import the Python modules using <em>require-python</em>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require-python</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">builtins</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">python</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">torch</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.nn</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">nn</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.nn.functional</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">F</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.optim</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">optim</span><span class="p">]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.distributions</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">Beta</span><span class="p">)]</span><span class="w">
                </span><span class="o">'</span><span class="p">[</span><span class="n">torch.nn.utils</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">utils</span><span class="p">])</span><span class="w">
</span><span class="c1">; :ok</span></code></pre></figure>

<h3 id="tensor-conversion">Tensor Conversion</h3>

<p>First we implement a few methods for converting nested Clojure vectors to PyTorch tensors and back.</p>

<h4 id="clojure-to-pytorch">Clojure to PyTorch</h4>

<p>The method <code class="language-plaintext highlighter-rouge">tensor</code> is for converting a Clojure datatype to a PyTorch tensor.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tensor</span><span class="w">
  </span><span class="s">"Convert nested vector to tensor"</span><span class="w">
  </span><span class="p">([</span><span class="n">data</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="n">torch/float32</span><span class="p">))</span><span class="w">
  </span><span class="p">([</span><span class="n">data</span><span class="w"> </span><span class="n">dtype</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="nf">torch/tensor</span><span class="w"> </span><span class="n">data</span><span class="w"> </span><span class="no">:dtype</span><span class="w"> </span><span class="n">dtype</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">PI</span><span class="p">)</span><span class="w">
</span><span class="c1">; tensor(3.1416)</span><span class="w">
</span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mf">2.0</span><span class="w"> </span><span class="mf">3.0</span><span class="w"> </span><span class="mf">5.0</span><span class="p">])</span><span class="w">
</span><span class="c1">; tensor([2., 3., 5.])</span><span class="w">
</span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">2.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mf">3.0</span><span class="w"> </span><span class="mf">4.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mf">5.0</span><span class="w"> </span><span class="mf">6.0</span><span class="p">]])</span><span class="w">
</span><span class="c1">; tensor([[1., 2.],</span><span class="w">
</span><span class="c1">;         [3., 4.],</span><span class="w">
</span><span class="c1">;         [5., 6.]])</span><span class="w">
</span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="n">torch/long</span><span class="p">)</span><span class="w">
</span><span class="c1">; tensor([1, 2, 3])</span></code></pre></figure>

<h4 id="pytorch-to-clojure">PyTorch to Clojure</h4>

<p>The next method is for converting a PyTorch tensor back to a Clojure datatype.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tolist</span><span class="w">
  </span><span class="s">"Convert tensor to nested vector"</span><span class="w">
  </span><span class="p">[</span><span class="n">tensor</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">py/-&gt;jvm</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">tensor</span><span class="w"> </span><span class="n">tolist</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">tolist</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mf">2.0</span><span class="w"> </span><span class="mf">3.0</span><span class="w"> </span><span class="mf">5.0</span><span class="p">]))</span><span class="w">
</span><span class="c1">; [2.0 3.0 5.0]</span><span class="w">
</span><span class="p">(</span><span class="nf">tolist</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">2.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mf">3.0</span><span class="w"> </span><span class="mf">4.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mf">5.0</span><span class="w"> </span><span class="mf">6.0</span><span class="p">]]))</span><span class="w">
</span><span class="c1">; [[1.0 2.0] [3.0 4.0] [5.0 6.0]]</span></code></pre></figure>

<h4 id="pytorch-scalar-to-clojure">PyTorch scalar to Clojure</h4>

<p>A tensor with no dimensions can also be converted using <code class="language-plaintext highlighter-rouge">toitem</code></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">toitem</span><span class="w">
  </span><span class="s">"Convert torch scalar value to float"</span><span class="w">
  </span><span class="p">[</span><span class="n">tensor</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">tensor</span><span class="w"> </span><span class="n">item</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="nf">toitem</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
</span><span class="c1">; 3.1415927410125732</span></code></pre></figure>

<h3 id="critic-network">Critic Network</h3>

<p>The critic network is a neural network with an input layer of size <code class="language-plaintext highlighter-rouge">observation-size</code> and two fully connected hidden layers of size <code class="language-plaintext highlighter-rouge">hidden-units</code> with <code class="language-plaintext highlighter-rouge">tanh</code> activation functions.
The critic output is a single value (an estimate for the expected cumulative return achievable by the given observed state).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">Critic</span><span class="w">
  </span><span class="p">(</span><span class="nf">py/create-class</span><span class="w">
    </span><span class="s">"Critic"</span><span class="w"> </span><span class="p">[</span><span class="n">nn/Module</span><span class="p">]</span><span class="w">
    </span><span class="p">{</span><span class="s">"__init__"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">observation-size</span><span class="w"> </span><span class="n">hidden-units</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">nn/Module</span><span class="w"> </span><span class="n">__init__</span><span class="w"> </span><span class="n">self</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">py/set-attrs!</span><span class="w">
             </span><span class="n">self</span><span class="w">
             </span><span class="p">{</span><span class="s">"fc1"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">observation-size</span><span class="w"> </span><span class="n">hidden-units</span><span class="p">)</span><span class="w">
              </span><span class="s">"fc2"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">hidden-units</span><span class="w"> </span><span class="n">hidden-units</span><span class="p">)</span><span class="w">
              </span><span class="s">"fc3"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">hidden-units</span><span class="w"> </span><span class="mi">1</span><span class="p">)})</span><span class="w">
           </span><span class="n">nil</span><span class="p">))</span><span class="w">
     </span><span class="s">"forward"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc1</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tanh</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc2</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tanh</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc3</span><span class="w"> </span><span class="n">x</span><span class="p">)]</span><span class="w">
             </span><span class="p">(</span><span class="nf">torch/squeeze</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mi">-1</span><span class="p">))))}))</span></code></pre></figure>

<p>When running inference, you need to run the network with gradient accumulation disabled, otherwise gradients get accumulated and can leak into a subsequent training step.
In Python this looks like this.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">no_grad</span><span class="p">():</span>
    <span class="c1"># ...</span></code></pre></figure>

<p>Here we create a Clojure macro to do the same job.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">without-gradient</span><span class="w">
  </span><span class="s">"Execute body without gradient calculation"</span><span class="w">
  </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">body</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">no-grad</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/no_grad</span><span class="p">)]</span><span class="w">
     </span><span class="p">(</span><span class="nf">try</span><span class="w">
       </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">no-grad</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="ss">'__enter__</span><span class="p">)</span><span class="w">
       </span><span class="o">~@</span><span class="n">body</span><span class="w">
       </span><span class="p">(</span><span class="nf">finally</span><span class="w">
         </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">no-grad</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="ss">'__exit__</span><span class="w"> </span><span class="n">nil</span><span class="w"> </span><span class="n">nil</span><span class="w"> </span><span class="n">nil</span><span class="p">)))))</span></code></pre></figure>

<p>Now we can create a network and try it out.
We create a test multilayer perceptron with three inputs, two hidden layers of 8 units each, and one output.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="p">(</span><span class="nf">Critic</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">8</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/critic.svg" alt="example of critic multilayer perceptron" /></p>

<p>Note that the network creates non-zero outputs because PyTorch performs random initialisation of the weights for us.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
  </span><span class="p">(</span><span class="nf">toitem</span><span class="w"> </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">]))))</span><span class="w">
</span><span class="c1">; -0.38925105333328247</span></code></pre></figure>

<p>We can also create a wrapper for using the neural network with Clojure datatypes.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">critic-observation</span><span class="w">
  </span><span class="s">"Use critic with Clojure datatypes"</span><span class="w">
  </span><span class="p">[</span><span class="n">critic</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w"> </span><span class="p">(</span><span class="nf">toitem</span><span class="w"> </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">observation</span><span class="p">))))))</span></code></pre></figure>

<p>Here is the output of the network for the observation <code class="language-plaintext highlighter-rouge">[-1 0 0]</code>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">((</span><span class="nf">critic-observation</span><span class="w"> </span><span class="n">critic</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])</span><span class="w">
</span><span class="c1">; -0.38925105333328247</span></code></pre></figure>

<h3 id="training">Training</h3>

<p>Training a neural network is done by defining a loss function.
The loss of the network then is calculated for a mini-batch of training data.
One can then use PyTorch’s backpropagation to compute the gradient of the loss value with respect to every single parameter of the network.
The gradient then is used to perform a gradient descent step.
A popular gradient descent method is the <a href="https://en.wikipedia.org/wiki/Stochastic_gradient_descent#Adam">Adam optimizer</a>.</p>

<p>Here is a wrapper for the Adam optimizer.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">adam-optimizer</span><span class="w">
  </span><span class="s">"Adam optimizer"</span><span class="w">
  </span><span class="p">[</span><span class="n">model</span><span class="w"> </span><span class="n">learning-rate</span><span class="w"> </span><span class="n">weight-decay</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">optim/Adam</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">model</span><span class="w"> </span><span class="n">parameters</span><span class="p">)</span><span class="w"> </span><span class="no">:lr</span><span class="w"> </span><span class="n">learning-rate</span><span class="w"> </span><span class="no">:weight_decay</span><span class="w"> </span><span class="n">weight-decay</span><span class="p">))</span></code></pre></figure>

<p>PyTorch also provides the mean square error (MSE) loss function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">mse-loss</span><span class="w">
  </span><span class="s">"Mean square error cost function"</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="nf">nn/MSELoss</span><span class="p">))</span></code></pre></figure>

<p>A training step can be performed as follows.
Here we only use a single mini-batch with a single observation and an expected output of 1.0.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">optimizer</span><span class="w"> </span><span class="p">(</span><span class="nf">adam-optimizer</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="mf">0.01</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">criterion</span><span class="w"> </span><span class="p">(</span><span class="nf">mse-loss</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">mini-batch</span><span class="w"> </span><span class="p">[(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">]])</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">])])</span><span class="w">
</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">prediction</span><span class="w"> </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">mini-batch</span><span class="p">))</span><span class="w">
      </span><span class="n">expected</span><span class="w">   </span><span class="p">(</span><span class="nb">second</span><span class="w"> </span><span class="n">mini-batch</span><span class="p">)</span><span class="w">
      </span><span class="n">loss</span><span class="w">       </span><span class="p">(</span><span class="nf">criterion</span><span class="w"> </span><span class="n">prediction</span><span class="w"> </span><span class="n">expected</span><span class="p">)]</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">optimizer</span><span class="w"> </span><span class="n">zero_grad</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">loss</span><span class="w"> </span><span class="n">backward</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">optimizer</span><span class="w"> </span><span class="n">step</span><span class="p">))</span></code></pre></figure>

<p>As you can see, the output of the network for the observation <code class="language-plaintext highlighter-rouge">[-1 0 0]</code> is now closer to 1.0.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">((</span><span class="nf">critic-observation</span><span class="w"> </span><span class="n">critic</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])</span><span class="w">
</span><span class="c1">; -0.3086397051811218</span></code></pre></figure>

<h3 id="actor-network">Actor Network</h3>

<p>The actor network for PPO takes an observation as an input and it outputs the parameters of a probability distribution over actions.
In addition to the forward pass, the actor network has a method <code class="language-plaintext highlighter-rouge">deterministic_act</code> to choose the expectation value of the distribution as a deterministic action.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">Actor</span><span class="w">
  </span><span class="p">(</span><span class="nf">py/create-class</span><span class="w">
    </span><span class="s">"Actor"</span><span class="w"> </span><span class="p">[</span><span class="n">nn/Module</span><span class="p">]</span><span class="w">
    </span><span class="p">{</span><span class="s">"__init__"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">observation-size</span><span class="w"> </span><span class="n">hidden-units</span><span class="w"> </span><span class="n">action-size</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">nn/Module</span><span class="w"> </span><span class="n">__init__</span><span class="w"> </span><span class="n">self</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">py/set-attrs!</span><span class="w">
             </span><span class="n">self</span><span class="w">
             </span><span class="p">{</span><span class="s">"fc1"</span><span class="w">     </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">observation-size</span><span class="w"> </span><span class="n">hidden-units</span><span class="p">)</span><span class="w">
              </span><span class="s">"fc2"</span><span class="w">     </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">hidden-units</span><span class="w"> </span><span class="n">hidden-units</span><span class="p">)</span><span class="w">
              </span><span class="s">"fcalpha"</span><span class="w"> </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">hidden-units</span><span class="w"> </span><span class="n">action-size</span><span class="p">)</span><span class="w">
              </span><span class="s">"fcbeta"</span><span class="w">  </span><span class="p">(</span><span class="nf">nn/Linear</span><span class="w"> </span><span class="n">hidden-units</span><span class="w"> </span><span class="n">action-size</span><span class="p">)})</span><span class="w">
           </span><span class="n">nil</span><span class="p">))</span><span class="w">
     </span><span class="s">"forward"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc1</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tanh</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fc2</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tanh</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w">
                 </span><span class="n">alpha</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/add</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nf">F/softplus</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fcalpha</span><span class="w"> </span><span class="n">x</span><span class="p">)))</span><span class="w">
                 </span><span class="n">beta</span><span class="w">  </span><span class="p">(</span><span class="nf">torch/add</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nf">F/softplus</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">fcbeta</span><span class="w"> </span><span class="n">x</span><span class="p">)))]</span><span class="w">
             </span><span class="p">[</span><span class="n">alpha</span><span class="w"> </span><span class="n">beta</span><span class="p">])))</span><span class="w">
     </span><span class="s">"deterministic_act"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
            </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[[</span><span class="n">alpha</span><span class="w"> </span><span class="n">beta</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">forward</span><span class="w"> </span><span class="n">x</span><span class="p">)]</span><span class="w">
              </span><span class="p">(</span><span class="nf">torch/div</span><span class="w"> </span><span class="n">alpha</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/add</span><span class="w"> </span><span class="n">alpha</span><span class="w"> </span><span class="n">beta</span><span class="p">)))))</span><span class="w">
     </span><span class="s">"get_dist"</span><span class="w">
     </span><span class="p">(</span><span class="nf">py/make-instance-fn</span><span class="w">
       </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">self</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[[</span><span class="n">alpha</span><span class="w"> </span><span class="n">beta</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">self</span><span class="w"> </span><span class="n">forward</span><span class="w"> </span><span class="n">x</span><span class="p">)]</span><span class="w">
             </span><span class="p">(</span><span class="nf">Beta</span><span class="w"> </span><span class="n">alpha</span><span class="w"> </span><span class="n">beta</span><span class="p">))))}))</span></code></pre></figure>

<p>Furthermore the actor network has a method <code class="language-plaintext highlighter-rouge">get_dist</code> to return a <a href="https://docs.pytorch.org/docs/stable/distributions.html">Torch distribution</a> object which can be used to sample a random action or query the current log-probability of an action.
Here (as the default in <a href="https://github.com/XinJingHao/PPO-Continuous-Pytorch/">XinJingHao’s PPO implementation</a>) we use the <a href="https://en.wikipedia.org/wiki/Beta_distribution">Beta distribution</a> with parameters <code class="language-plaintext highlighter-rouge">alpha</code> and <code class="language-plaintext highlighter-rouge">beta</code> both greater than 1.0.
See <a href="https://mathlets.org/mathlets/beta-distribution/">here</a> for an interactive visualization of the Beta distribution.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">indeterministic-act</span><span class="w">
  </span><span class="s">"Sample action using actor network returning random action and log-probability"</span><span class="w">
  </span><span class="p">[</span><span class="n">actor</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">indeterministic-act-with-actor</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
        </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">dist</span><span class="w">    </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">get_dist</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">observation</span><span class="p">))</span><span class="w">
              </span><span class="n">sample</span><span class="w">  </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="n">sample</span><span class="p">)</span><span class="w">
              </span><span class="n">action</span><span class="w">  </span><span class="p">(</span><span class="nf">torch/clamp</span><span class="w"> </span><span class="n">sample</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
              </span><span class="n">logprob</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="n">log_prob</span><span class="w"> </span><span class="n">action</span><span class="p">)]</span><span class="w">
          </span><span class="p">{</span><span class="no">:action</span><span class="w"> </span><span class="p">(</span><span class="nf">tolist</span><span class="w"> </span><span class="n">action</span><span class="p">)</span><span class="w"> </span><span class="no">:logprob</span><span class="w"> </span><span class="p">(</span><span class="nf">tolist</span><span class="w"> </span><span class="n">logprob</span><span class="p">)}))))</span></code></pre></figure>

<p>We create a test multilayer perceptron with three inputs, two hidden layers of 8 units each, and two outputs which serve as parameters for the Beta distribution.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="p">(</span><span class="nf">Actor</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/actor.svg" alt="example of actor multilayer perceptron" /></p>

<p>One can then use the network to:</p>

<p>a. get the parameters of the distribution for a given observation.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">without-gradient</span><span class="w"> </span><span class="p">(</span><span class="nf">actor</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])))</span><span class="w">
</span><span class="c1">; (tensor([1.7002]), tensor([1.7489]))</span></code></pre></figure>

<p>b. choose the expectation value of the distribution as an action.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">without-gradient</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">deterministic_act</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])))</span><span class="w">
</span><span class="c1">; tensor([0.4929])</span></code></pre></figure>

<p>c. sample a random action from the distribution and get the associated log-probability.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">((</span><span class="nf">indeterministic-act</span><span class="w"> </span><span class="n">actor</span><span class="p">)</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])</span><span class="w">
</span><span class="p">{</span><span class="no">:action</span><span class="w"> </span><span class="p">[</span><span class="mf">0.6526480913162231</span><span class="p">]</span><span class="n">,</span><span class="w"> </span><span class="no">:logprob</span><span class="w"> </span><span class="p">[</span><span class="mf">0.2350209504365921</span><span class="p">]}</span></code></pre></figure>

<p>We can also query the current log-probability of a previously sampled action.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">logprob-of-action</span><span class="w">
  </span><span class="s">"Get log probability of action"</span><span class="w">
  </span><span class="p">[</span><span class="n">actor</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="w"> </span><span class="n">action</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">dist</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">get_dist</span><span class="w"> </span><span class="n">observation</span><span class="p">)]</span><span class="w">
        </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="n">log_prob</span><span class="w"> </span><span class="n">action</span><span class="p">))))</span></code></pre></figure>

<p>Here is a plot of the probability density function (PDF) actor output for a single observation.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">actions</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.01</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
        </span><span class="n">logprob</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">action</span><span class="p">]</span><span class="w">
                    </span><span class="p">(</span><span class="nf">tolist</span><span class="w">
                      </span><span class="p">((</span><span class="nf">logprob-of-action</span><span class="w"> </span><span class="n">actor</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">action</span><span class="p">))))</span><span class="w">
        </span><span class="n">scatter</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w">
                  </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="n">actions</span><span class="w">
                   </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">action</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">exp</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">logprob</span><span class="w"> </span><span class="p">[</span><span class="n">action</span><span class="p">]))))</span><span class="w"> </span><span class="n">actions</span><span class="p">)})]</span><span class="w">
    </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
        </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Actor output for a single observation"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="no">:lines</span><span class="p">})</span><span class="w">
        </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">}))))</span></code></pre></figure>

<p><img src="/pics/actorpdf.png" alt="probability density function output of actor for a single observation" /></p>

<p>Finally we can also query the entropy of the distribution.
By incorporating the entropy into the loss function later on, we can encourage exploration and prevent the probability density function from collapsing.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">entropy-of-distribution</span><span class="w">
  </span><span class="s">"Get entropy of distribution"</span><span class="w">
  </span><span class="p">[</span><span class="n">actor</span><span class="w"> </span><span class="n">observation</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">dist</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">get_dist</span><span class="w"> </span><span class="n">observation</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">dist</span><span class="w"> </span><span class="n">entropy</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">without-gradient</span><span class="w"> </span><span class="p">(</span><span class="nf">entropy-of-distribution</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">])))</span><span class="w">
</span><span class="c1">; tensor([-0.0825])</span></code></pre></figure>

<h2 id="proximal-policy-optimization">Proximal Policy Optimization</h2>

<h3 id="sampling-data">Sampling data</h3>

<p>In order to perform optimization, we sample the environment using the current policy (indeterministic action using actor).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sample-environment</span><span class="w">
  </span><span class="s">"Collect trajectory data from environment"</span><span class="w">
  </span><span class="p">[</span><span class="n">environment-factory</span><span class="w"> </span><span class="n">policy</span><span class="w"> </span><span class="n">size</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">loop</span><span class="w"> </span><span class="p">[</span><span class="n">state</span><span class="w">             </span><span class="p">(</span><span class="nf">environment-factory</span><span class="p">)</span><span class="w">
         </span><span class="n">observations</span><span class="w">      </span><span class="p">[]</span><span class="w">
         </span><span class="n">actions</span><span class="w">           </span><span class="p">[]</span><span class="w">
         </span><span class="n">logprobs</span><span class="w">          </span><span class="p">[]</span><span class="w">
         </span><span class="n">next-observations</span><span class="w"> </span><span class="p">[]</span><span class="w">
         </span><span class="n">rewards</span><span class="w">           </span><span class="p">[]</span><span class="w">
         </span><span class="n">dones</span><span class="w">             </span><span class="p">[]</span><span class="w">
         </span><span class="n">truncates</span><span class="w">         </span><span class="p">[]</span><span class="w">
         </span><span class="n">i</span><span class="w">                 </span><span class="n">size</span><span class="p">]</span><span class="w">
    </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">pos?</span><span class="w"> </span><span class="n">i</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="w">      </span><span class="p">(</span><span class="nf">environment-observation</span><span class="w"> </span><span class="n">state</span><span class="p">)</span><span class="w">
            </span><span class="n">sample</span><span class="w">           </span><span class="p">(</span><span class="nf">policy</span><span class="w"> </span><span class="n">observation</span><span class="p">)</span><span class="w">
            </span><span class="n">action</span><span class="w">           </span><span class="p">(</span><span class="no">:action</span><span class="w"> </span><span class="n">sample</span><span class="p">)</span><span class="w">
            </span><span class="n">logprob</span><span class="w">          </span><span class="p">(</span><span class="no">:logprob</span><span class="w"> </span><span class="n">sample</span><span class="p">)</span><span class="w">
            </span><span class="n">reward</span><span class="w">           </span><span class="p">(</span><span class="nf">environment-reward</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">action</span><span class="p">)</span><span class="w">
            </span><span class="n">done</span><span class="w">             </span><span class="p">(</span><span class="nf">environment-done?</span><span class="w"> </span><span class="n">state</span><span class="p">)</span><span class="w">
            </span><span class="n">truncate</span><span class="w">         </span><span class="p">(</span><span class="nf">environment-truncate?</span><span class="w"> </span><span class="n">state</span><span class="p">)</span><span class="w">
            </span><span class="n">next-state</span><span class="w">       </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">or</span><span class="w"> </span><span class="n">done</span><span class="w"> </span><span class="n">truncate</span><span class="p">)</span><span class="w">
                               </span><span class="p">(</span><span class="nf">environment-factory</span><span class="p">)</span><span class="w">
                               </span><span class="p">(</span><span class="nf">environment-update</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">action</span><span class="p">))</span><span class="w">
            </span><span class="n">next-observation</span><span class="w"> </span><span class="p">(</span><span class="nf">environment-observation</span><span class="w"> </span><span class="n">next-state</span><span class="p">)]</span><span class="w">
        </span><span class="p">(</span><span class="nf">recur</span><span class="w"> </span><span class="n">next-state</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">observations</span><span class="w"> </span><span class="n">observation</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">actions</span><span class="w"> </span><span class="n">action</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">logprobs</span><span class="w"> </span><span class="n">logprob</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">next-observations</span><span class="w"> </span><span class="n">next-observation</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">rewards</span><span class="w"> </span><span class="n">reward</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">dones</span><span class="w"> </span><span class="n">done</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">conj</span><span class="w"> </span><span class="n">truncates</span><span class="w"> </span><span class="n">truncate</span><span class="p">)</span><span class="w">
               </span><span class="p">(</span><span class="nb">dec</span><span class="w"> </span><span class="n">i</span><span class="p">)))</span><span class="w">
      </span><span class="p">{</span><span class="no">:observations</span><span class="w">      </span><span class="n">observations</span><span class="w">
       </span><span class="no">:actions</span><span class="w">           </span><span class="n">actions</span><span class="w">
       </span><span class="no">:logprobs</span><span class="w">          </span><span class="n">logprobs</span><span class="w">
       </span><span class="no">:next-observations</span><span class="w"> </span><span class="n">next-observations</span><span class="w">
       </span><span class="no">:rewards</span><span class="w">           </span><span class="n">rewards</span><span class="w">
       </span><span class="no">:dones</span><span class="w">             </span><span class="n">dones</span><span class="w">
       </span><span class="no">:truncates</span><span class="w">         </span><span class="n">truncates</span><span class="p">})))</span></code></pre></figure>

<p>Here for example we are sampling 3 consecutives states of the pendulum.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">sample-environment</span><span class="w"> </span><span class="n">pendulum-factory</span><span class="w"> </span><span class="p">(</span><span class="nf">indeterministic-act</span><span class="w"> </span><span class="n">actor</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
</span><span class="c1">; {:observations</span><span class="w">
</span><span class="c1">;  [[-0.7596729533565417 0.6503053159390207 0.5479034035454418]</span><span class="w">
</span><span class="c1">;   [-0.8900589293843874 0.4558454806435161 0.5866609335014912]</span><span class="w">
</span><span class="c1">;   [-0.9762048336009674 0.21685046196424718 0.6368372482766531]],</span><span class="w">
</span><span class="c1">;  :actions</span><span class="w">
</span><span class="c1">;  [[0.20388542115688324] [0.5992106795310974] [0.1662445366382599]],</span><span class="w">
</span><span class="c1">;  :logprobs</span><span class="w">
</span><span class="c1">;  [[0.08455279469490051] [0.26384592056274414] [-0.028919726610183716]],</span><span class="w">
</span><span class="c1">;  :next-observations</span><span class="w">
</span><span class="c1">;  [[-0.8900589293843874 0.4558454806435161 0.5866609335014912]</span><span class="w">
</span><span class="c1">;   [-0.9762048336009674 0.21685046196424718 0.6368372482766531]</span><span class="w">
</span><span class="c1">;   [-0.99941293940555 -0.034260422483655656 0.6321353193336707]],</span><span class="w">
</span><span class="c1">;  :rewards [-7.8437431872499745 -9.322367484397839 -11.139601368813137],</span><span class="w">
</span><span class="c1">;  :dones [false false false],</span><span class="w">
</span><span class="c1">;  :truncates [false false false]}</span></code></pre></figure>

<h3 id="advantages">Advantages</h3>

<h4 id="theory">Theory</h4>

<p>If we are in state \(s_t\) and take an action \(a_t\) at timestep \(t\), we receive reward \(r_t\) and end up in state \(s_{t+1}\).
The cumulative reward for state \(s_t\) is a finite or infinite sequence using a discount factor \(\gamma&lt;1\):</p>

<p>\[
r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \gamma^3 r_{t+3} + \ldots
\]</p>

<p>The critic \(V\) estimates the expected cumulative reward for starting from the specified state.</p>

<p>\[
V(s_t) = \mathop{\hat{\mathbb{E}}} [ r_t + \gamma r_{t+1} + \gamma^2 r_{t+2} + \gamma^3 r_{t+3} + \ldots ]
\]</p>

<p>In particular, the difference between discounted rewards can be used to get an estimate for the individual reward:</p>

<p>\[
V(s_t) = \mathop{\hat{\mathbb{E}}} [ r_t ] + \gamma V(s_{t+1})\Leftrightarrow\mathop{\hat{\mathbb{E}}} [ r_t ] = V(s_t) - \gamma V(s_{t+1})
\]</p>

<p>The deviation of the individual reward received in state \(s_t\) from the expected reward is:</p>

<p>\[
\delta_t = r_t + \gamma V(s_{t+1}) - V(s_t)\mathrm{\ if\ not\ }\operatorname{done}_t
\]</p>

<p>The special case where a time series is “done” (and the next one is started) uses 0 as the remaining expected cumulative reward.</p>

<p>\[
\delta_t = r_t - V(s_t)\mathrm{\ if\ }\operatorname{done}_{t}
\]</p>

<p>If we have a sample set with a sequence of \(T\) states (\(t=0,1,\ldots,T-1\)), one can compute the cumulative advantage for each time step going backwards:</p>

<p>\[

\begin{aligned}
\hat{A} _ {T-1} & = -V(s_{T-1}) + r_{T-1} + \gamma V(s_T) = \delta_{T-1} \\
\hat{A} _ {T-2} & = -V(s_{T-2}) + r_{T-2} + \gamma r_{T-1} + \gamma^2 V(s_T) = \delta_{T-2} + \gamma \delta_{T-1} \\
& \vdots \\
\hat{A} _ 0 & = -V(s_0) + r_0 + \gamma r_1 + \gamma^2 r_2 + \ldots + + \gamma^{T-1} r_{T-1} + \gamma^{T} V(s_{T}) \\
            & = \delta_0 + \gamma \delta_1 + \gamma^2 \delta_2 + \ldots + \gamma^{T-1} \delta_{T-1}
\end{aligned}

\]</p>

<p>I.e. we can compute the cumulative advantages as follows:</p>

<ul>
  <li>Start with \(\hat{A} _ {T-1} = \delta_{T-1}\)</li>
  <li>Continue with \(\hat{A} _ t = \delta_t + \gamma \hat{A} _ {t+1}\) for \(t=T-2,T-3,\ldots,0\)</li>
</ul>

<p>PPO uses an additional factor \(\lambda\le 1\) called Generalized Advantage Estimation (GAE) which can be used to steer the training towards more immediate rewards if there are stability issues.
See <a href="https://arxiv.org/abs/1707.06347">Schulman et al.</a> for more details.</p>

<h4 id="implementation-of-deltas">Implementation of Deltas</h4>

<p>The code for computing the \(\delta\) values follows here:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">deltas</span><span class="w">
  </span><span class="s">"Compute difference between actual reward plus discounted estimate of next state and estimated value of current state"</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">observations</span><span class="w"> </span><span class="n">next-observations</span><span class="w"> </span><span class="n">rewards</span><span class="w"> </span><span class="n">dones</span><span class="p">]}</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="n">gamma</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="w"> </span><span class="n">next-observation</span><span class="w"> </span><span class="n">reward</span><span class="w"> </span><span class="n">done</span><span class="p">]</span><span class="w">
            </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">reward</span><span class="w">
                  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="n">done</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="n">next-observation</span><span class="p">))))</span><span class="w">
               </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="n">observation</span><span class="p">)))</span><span class="w">
        </span><span class="n">observations</span><span class="w"> </span><span class="n">next-observations</span><span class="w"> </span><span class="n">rewards</span><span class="w"> </span><span class="n">dones</span><span class="p">))</span></code></pre></figure>

<p>If the reward is zero and the critic outputs constant zero, there is no difference between the expected and received reward.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">deltas</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">4</span><span class="p">]]</span><span class="w"> </span><span class="no">:next-observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">3</span><span class="p">]]</span><span class="w"> </span><span class="no">:rewards</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="p">]}</span><span class="w">
        </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
        </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; [0.0]</span></code></pre></figure>

<p>If the reward is 1.0 and the critic outputs zero for both observations, the difference is 1.0.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">deltas</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">4</span><span class="p">]]</span><span class="w"> </span><span class="no">:next-observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">3</span><span class="p">]]</span><span class="w"> </span><span class="no">:rewards</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="p">]}</span><span class="w">
        </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
        </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; [1.0]</span></code></pre></figure>

<p>If the reward is 1.0 and the difference of critic outputs is also 1.0 then there is no difference between the expected and received reward (when \(\gamma=1\)).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">linear-critic</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">observation</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">deltas</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">4</span><span class="p">]]</span><span class="w"> </span><span class="no">:next-observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">3</span><span class="p">]]</span><span class="w"> </span><span class="no">:rewards</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="p">]}</span><span class="w">
        </span><span class="n">linear-critic</span><span class="w">
        </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; [0.0]</span></code></pre></figure>

<p>If the next critic value is 1.0 and discounted with 0.5 and the current critic value is 2.0, we expect a reward of 1.5.
If we only get a reward of 1.0, the difference is -0.5.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">deltas</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">2</span><span class="p">]]</span><span class="w"> </span><span class="no">:next-observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">1</span><span class="p">]]</span><span class="w"> </span><span class="no">:rewards</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="p">]}</span><span class="w">
        </span><span class="n">linear-critic</span><span class="w">
        </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
</span><span class="c1">; [-0.5]</span></code></pre></figure>

<p>If the run is terminated, the current critic value is compared with the reward which in this case is the last reward received in this run.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">deltas</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">4</span><span class="p">]]</span><span class="w"> </span><span class="no">:next-observations</span><span class="w"> </span><span class="p">[[</span><span class="mi">3</span><span class="p">]]</span><span class="w"> </span><span class="no">:rewards</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">true</span><span class="p">]}</span><span class="w">
        </span><span class="n">linear-critic</span><span class="w">
        </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; [0.0]</span></code></pre></figure>

<h4 id="implementation-of-advantages">Implementation of Advantages</h4>

<p>The advantages can be computed in an elegant way using <code class="language-plaintext highlighter-rouge">reductions</code> and the previously computed <code class="language-plaintext highlighter-rouge">deltas</code>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">advantages</span><span class="w">
  </span><span class="s">"Compute advantages attributed to each action"</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">dones</span><span class="w"> </span><span class="n">truncates</span><span class="p">]}</span><span class="w"> </span><span class="n">deltas</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">vec</span><span class="w">
    </span><span class="p">(</span><span class="nb">reverse</span><span class="w">
    </span><span class="p">(</span><span class="nb">rest</span><span class="w">
      </span><span class="p">(</span><span class="nf">reductions</span><span class="w">
        </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">advantage</span><span class="w"> </span><span class="p">[</span><span class="n">delta</span><span class="w"> </span><span class="n">done</span><span class="w"> </span><span class="n">truncate</span><span class="p">]]</span><span class="w">
            </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">delta</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">or</span><span class="w"> </span><span class="n">done</span><span class="w"> </span><span class="n">truncate</span><span class="p">)</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="w"> </span><span class="n">advantage</span><span class="p">))))</span><span class="w">
        </span><span class="mf">0.0</span><span class="w">
        </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">vector</span><span class="w"> </span><span class="n">deltas</span><span class="w"> </span><span class="n">dones</span><span class="w"> </span><span class="n">truncates</span><span class="p">)))))))</span></code></pre></figure>

<p>For example when all deltas are 1.0 and if using an discount factor of 0.5, the advantages approach 2.0 assymptotically when going backwards in time.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">advantages</span><span class="w"> </span><span class="p">{</span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="p">]</span><span class="w"> </span><span class="no">:truncates</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="p">]}</span><span class="w">
            </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w">
            </span><span class="mf">0.5</span><span class="w">
            </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; [1.75 1.5 1.0]</span></code></pre></figure>

<p>When an episode is terminated (or truncated), the accumulation of advantages starts again when going backwards in time.
I.e. the computation of advantages does not distinguish between terminated and truncated episodes (unlike the deltas).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">advantages</span><span class="w"> </span><span class="p">{</span><span class="no">:dones</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">true</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">true</span><span class="p">]</span><span class="w">
             </span><span class="no">:truncates</span><span class="w"> </span><span class="p">[</span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="n">false</span><span class="p">]}</span><span class="w">
            </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w">
            </span><span class="mf">0.5</span><span class="w">
            </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
</span><span class="c1">; [1.75 1.5 1.0 1.75 1.5 1.0]</span></code></pre></figure>

<p>We add the advantages to the batch of samples with the following function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">assoc-advantages</span><span class="w">
  </span><span class="s">"Associate advantages with batch of samples"</span><span class="w">
  </span><span class="p">[</span><span class="n">critic</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="w"> </span><span class="n">batch</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">deltas</span><span class="w">     </span><span class="p">(</span><span class="nf">deltas</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="n">gamma</span><span class="p">)</span><span class="w">
        </span><span class="n">advantages</span><span class="w"> </span><span class="p">(</span><span class="nf">advantages</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="n">deltas</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">assoc</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="no">:advantages</span><span class="w"> </span><span class="n">advantages</span><span class="p">)))</span></code></pre></figure>

<h3 id="critic-loss-function">Critic Loss Function</h3>

<p>The target values for the critic are simply the current values plus the new advantages.
The target values can be computed using PyTorch’s <code class="language-plaintext highlighter-rouge">add</code> function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">critic-target</span><span class="w">
  </span><span class="s">"Determine target values for critic"</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">observations</span><span class="w"> </span><span class="n">advantages</span><span class="p">]}</span><span class="w"> </span><span class="n">critic</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/add</span><span class="w"> </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="n">observations</span><span class="p">)</span><span class="w"> </span><span class="n">advantages</span><span class="p">)))</span></code></pre></figure>

<p>We add the critic targets to the batch of samples with the following function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">assoc-critic-target</span><span class="w">
  </span><span class="s">"Associate critic target values with batch of samples"</span><span class="w">
  </span><span class="p">[</span><span class="n">critic</span><span class="w"> </span><span class="n">batch</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">target</span><span class="w"> </span><span class="p">(</span><span class="nf">critic-target</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="n">critic</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">assoc</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="no">:critic-target</span><span class="w"> </span><span class="n">target</span><span class="p">)))</span></code></pre></figure>

<p>If we add the target values to the samples, we can compute the critic loss for a batch of samples as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">critic-loss</span><span class="w">
  </span><span class="s">"Compute loss value for batch of samples and critic"</span><span class="w">
  </span><span class="p">[</span><span class="n">samples</span><span class="w"> </span><span class="n">critic</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">criterion</span><span class="w"> </span><span class="p">(</span><span class="nf">mse-loss</span><span class="p">)</span><span class="w">
        </span><span class="n">loss</span><span class="w">      </span><span class="p">(</span><span class="nf">criterion</span><span class="w"> </span><span class="p">(</span><span class="nf">critic</span><span class="w"> </span><span class="p">(</span><span class="no">:observations</span><span class="w"> </span><span class="n">samples</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="no">:critic-target</span><span class="w"> </span><span class="n">samples</span><span class="p">))]</span><span class="w">
    </span><span class="n">loss</span><span class="p">))</span></code></pre></figure>

<h3 id="actor-loss-function">Actor Loss Function</h3>

<p>The core of the actor loss function relies on the action probability ratio of using the updated and the old policy (actor network output).
The ratio is defined as
\[
r_t(\theta)=\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\operatorname{old}}}(a_t|s_t)}
\].</p>

<p>Note that \(r_t(\theta)\) here refers to the probability ratio as opposed to the reward of the previous section.</p>

<p>The sampled observations, log probabilities, and actions are combined with the actor’s parameter-dependent log probabilities.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">probability-ratios</span><span class="w">
  </span><span class="s">"Probability ratios for a actions using updated policy and old policy"</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">observations</span><span class="w"> </span><span class="n">logprobs</span><span class="w"> </span><span class="n">actions</span><span class="p">]}</span><span class="w"> </span><span class="n">logprob-of-action</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">updated-logprobs</span><span class="w"> </span><span class="p">(</span><span class="nf">logprob-of-action</span><span class="w"> </span><span class="n">observations</span><span class="w"> </span><span class="n">actions</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">torch/exp</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/sub</span><span class="w"> </span><span class="n">updated-logprobs</span><span class="w"> </span><span class="n">logprobs</span><span class="p">)</span><span class="w"> </span><span class="n">sum</span><span class="w"> </span><span class="mi">1</span><span class="p">))))</span></code></pre></figure>

<p>The objective is to increase the probability of actions which lead to a positive advantage and reduce the probability of actions which lead to a negative advantage.
I.e. maximising the following objective function.</p>

<p>\[

L^{CPI}(\theta) = \mathop{\hat{\mathbb{E}}}_t [\frac{\pi_\theta(a_t|s_t)}{\pi_{\theta_{\operatorname{old}}} (a_t|s_t)} \hat{A}_t] = \mathop{\hat{\mathbb{E}}}_t [r_t (\theta) \hat{A}_t]

\]</p>

<p>The core idea of PPO is to use clipped probability ratios for the loss function in order to increase stability, .
The probability ratio is clipped to stay below \(1+\epsilon\) for positive advantages and to stay above \(1-\epsilon\) for negative advantages.</p>

<p>\[

L^{CLIP}(\theta) = \mathop{\hat{\mathbb{E}}}_t [\min(r_t (\theta) \hat{A}_t, \mathop{\operatorname{clip}}(r_t (\theta), 1-\epsilon, 1+\epsilon) \hat{A}_t)]

\]</p>

<p>See <a href="https://arxiv.org/abs/1707.06347">Schulman et al.</a> for more details.</p>

<p>Because PyTorch minimizes a loss, we need to negate above objective function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">clipped-surrogate-loss</span><span class="w">
  </span><span class="s">"Clipped surrogate loss (negative objective)"</span><span class="w">
  </span><span class="p">[</span><span class="n">probability-ratios</span><span class="w"> </span><span class="n">advantages</span><span class="w"> </span><span class="n">epsilon</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">torch/mean</span><span class="w">
    </span><span class="p">(</span><span class="nf">torch/neg</span><span class="w">
      </span><span class="p">(</span><span class="nf">torch/min</span><span class="w">
        </span><span class="p">(</span><span class="nf">torch/mul</span><span class="w"> </span><span class="n">probability-ratios</span><span class="w"> </span><span class="n">advantages</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">torch/mul</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/clamp</span><span class="w"> </span><span class="n">probability-ratios</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="n">epsilon</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="n">epsilon</span><span class="p">))</span><span class="w">
                   </span><span class="n">advantages</span><span class="p">)))))</span></code></pre></figure>

<p>We can plot the objective function for a single action and a positive advantage.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">ratios</span><span class="w">  </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">2.01</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
        </span><span class="n">loss</span><span class="w">    </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">ratio</span><span class="w"> </span><span class="n">advantage</span><span class="w"> </span><span class="n">epsilon</span><span class="p">]</span><span class="w">
                    </span><span class="p">(</span><span class="nf">toitem</span><span class="w">
                      </span><span class="p">(</span><span class="nf">torch/neg</span><span class="w">
                        </span><span class="p">(</span><span class="nf">clipped-surrogate-loss</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">ratio</span><span class="p">)</span><span class="w">
                                                </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">advantage</span><span class="p">)</span><span class="w">
                                                </span><span class="n">epsilon</span><span class="p">))))</span><span class="w">
        </span><span class="n">scatter</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w">
                  </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="n">ratios</span><span class="w">
                   </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">ratio</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">loss</span><span class="w"> </span><span class="n">ratio</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.2</span><span class="p">))</span><span class="w"> </span><span class="n">ratios</span><span class="p">)})]</span><span class="w">
    </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
        </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Objective Function for Positive Advantage"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="no">:lines</span><span class="p">})</span><span class="w">
        </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">}))))</span></code></pre></figure>

<p><img src="/pics/actorloss1.png" alt="actor loss over ratio for positive advantage" /></p>

<p>And for a negative advantage.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">ratios</span><span class="w">  </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">2.01</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
        </span><span class="n">loss</span><span class="w">    </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">ratio</span><span class="w"> </span><span class="n">advantage</span><span class="w"> </span><span class="n">epsilon</span><span class="p">]</span><span class="w">
                    </span><span class="p">(</span><span class="nf">toitem</span><span class="w">
                      </span><span class="p">(</span><span class="nf">torch/neg</span><span class="w">
                        </span><span class="p">(</span><span class="nf">clipped-surrogate-loss</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">ratio</span><span class="p">)</span><span class="w">
                                                </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">advantage</span><span class="p">)</span><span class="w">
                                                </span><span class="n">epsilon</span><span class="p">))))</span><span class="w">
        </span><span class="n">scatter</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w">
                  </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="n">ratios</span><span class="w">
                   </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">ratio</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">loss</span><span class="w"> </span><span class="n">ratio</span><span class="w"> </span><span class="mf">-0.5</span><span class="w"> </span><span class="mf">0.2</span><span class="p">))</span><span class="w"> </span><span class="n">ratios</span><span class="p">)})]</span><span class="w">
    </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
        </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Objective Function for Negative Advantage"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="no">:lines</span><span class="p">})</span><span class="w">
        </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">}))))</span></code></pre></figure>

<p><img src="/pics/actorloss2.png" alt="actor loss over ratio for positive advantage" /></p>

<p>We can now implement the actor loss function which we want to minimize.
The loss function uses the clipped surrogate loss function as defined above.
The loss function also penalises low entropy values of the distributions output by the actor in order to encourage exploration.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">actor-loss</span><span class="w">
  </span><span class="s">"Compute loss value for batch of samples and actor"</span><span class="w">
  </span><span class="p">[</span><span class="n">samples</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">epsilon</span><span class="w"> </span><span class="n">entropy-factor</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">ratios</span><span class="w">         </span><span class="p">(</span><span class="nf">probability-ratios</span><span class="w"> </span><span class="n">samples</span><span class="w"> </span><span class="p">(</span><span class="nf">logprob-of-action</span><span class="w"> </span><span class="n">actor</span><span class="p">))</span><span class="w">
        </span><span class="n">entropy</span><span class="w">        </span><span class="p">(</span><span class="nf">torch/mul</span><span class="w">
                         </span><span class="n">entropy-factor</span><span class="w">
                         </span><span class="p">(</span><span class="nf">torch/neg</span><span class="w">
                           </span><span class="p">(</span><span class="nf">torch/mean</span><span class="w">
                             </span><span class="p">(</span><span class="nf">entropy-of-distribution</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="p">(</span><span class="no">:observations</span><span class="w"> </span><span class="n">samples</span><span class="p">)))))</span><span class="w">
        </span><span class="n">surrogate-loss</span><span class="w"> </span><span class="p">(</span><span class="nf">clipped-surrogate-loss</span><span class="w"> </span><span class="n">ratios</span><span class="w"> </span><span class="p">(</span><span class="no">:advantages</span><span class="w"> </span><span class="n">samples</span><span class="p">)</span><span class="w"> </span><span class="n">epsilon</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">torch/add</span><span class="w"> </span><span class="n">surrogate-loss</span><span class="w"> </span><span class="n">entropy</span><span class="p">)))</span></code></pre></figure>

<p>A notable detail in <a href="https://github.com/XinJingHao/PPO-Continuous-Pytorch/">XinJingHao’s PPO implementation</a> is that the advantage values used in the actor loss (not in the critic loss!) are normalized.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">normalize-advantages</span><span class="w">
  </span><span class="s">"Normalize advantages"</span><span class="w">
  </span><span class="p">[</span><span class="n">batch</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">advantages</span><span class="w"> </span><span class="p">(</span><span class="no">:advantages</span><span class="w"> </span><span class="n">batch</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">assoc</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="no">:advantages</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/div</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/sub</span><span class="w"> </span><span class="n">advantages</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/mean</span><span class="w"> </span><span class="n">advantages</span><span class="p">))</span><span class="w">
                                        </span><span class="p">(</span><span class="nf">torch/std</span><span class="w"> </span><span class="n">advantages</span><span class="p">)))))</span></code></pre></figure>

<h3 id="preparing-samples">Preparing Samples</h3>

<h4 id="shuffling">Shuffling</h4>

<p>The data required for training needs to be converted to PyTorch tensors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tensor-batch</span><span class="w">
  </span><span class="s">"Convert batch to Torch tensors"</span><span class="w">
  </span><span class="p">[</span><span class="n">batch</span><span class="p">]</span><span class="w">
  </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">(</span><span class="no">:observations</span><span class="w"> </span><span class="n">batch</span><span class="p">))</span><span class="w">
   </span><span class="no">:logprobs</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">(</span><span class="no">:logprobs</span><span class="w"> </span><span class="n">batch</span><span class="p">))</span><span class="w">
   </span><span class="no">:actions</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">(</span><span class="no">:actions</span><span class="w"> </span><span class="n">batch</span><span class="p">))</span><span class="w">
   </span><span class="no">:advantages</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">(</span><span class="no">:advantages</span><span class="w"> </span><span class="n">batch</span><span class="p">))})</span></code></pre></figure>

<p>Furthermore it is good practice to shuffle the samples.
This ensures that samples early and late in the sequence are not threated differently.
Note that you need to shuffle <em>after</em> computing the advantages, because the computation of the advantages relies on the order of the samples.</p>

<p>We separate the generation of random indices to facilitate unit testing of the shuffling function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-order</span><span class="w">
  </span><span class="s">"Create a list of randomly ordered indices"</span><span class="w">
  </span><span class="p">[</span><span class="n">n</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">shuffle</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">shuffle-samples</span><span class="w">
  </span><span class="s">"Random shuffle of samples"</span><span class="w">
  </span><span class="p">([</span><span class="n">samples</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="nf">shuffle-samples</span><span class="w"> </span><span class="n">samples</span><span class="w"> </span><span class="p">(</span><span class="nf">random-order</span><span class="w"> </span><span class="p">(</span><span class="nf">python/len</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nb">vals</span><span class="w"> </span><span class="n">samples</span><span class="p">))))))</span><span class="w">
  </span><span class="p">([</span><span class="n">samples</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="nb">zipmap</span><span class="w"> </span><span class="p">(</span><span class="nb">keys</span><span class="w"> </span><span class="n">samples</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">torch/index_select</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/tensor</span><span class="w"> </span><span class="n">indices</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">vals</span><span class="w"> </span><span class="n">samples</span><span class="p">)))))</span></code></pre></figure>

<p>Here is an example of shuffling observations:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">shuffle-samples</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">7</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">10</span><span class="p">]])})</span><span class="w">
</span><span class="c1">; {:observations tensor([[ 1.],</span><span class="w">
</span><span class="c1">;         [ 4.],</span><span class="w">
</span><span class="c1">;         [ 6.],</span><span class="w">
</span><span class="c1">;         [ 5.],</span><span class="w">
</span><span class="c1">;         [10.],</span><span class="w">
</span><span class="c1">;         [ 8.],</span><span class="w">
</span><span class="c1">;         [ 7.],</span><span class="w">
</span><span class="c1">;         [ 2.],</span><span class="w">
</span><span class="c1">;         [ 9.],</span><span class="w">
</span><span class="c1">;         [ 3.]])}</span></code></pre></figure>

<h4 id="creating-batches">Creating Batches</h4>

<p>Furthermore we split up the samples into smaller batches to improve training speed.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">create-batches</span><span class="w">
  </span><span class="s">"Create mini batches from environment samples"</span><span class="w">
  </span><span class="p">[</span><span class="n">batch-size</span><span class="w"> </span><span class="n">samples</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">mapv</span><span class="w">
         </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">zipmap</span><span class="w"> </span><span class="p">(</span><span class="nb">keys</span><span class="w"> </span><span class="n">samples</span><span class="p">)</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w">
         </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">split</span><span class="w"> </span><span class="n">batch-size</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">vals</span><span class="w"> </span><span class="n">samples</span><span class="p">))))</span><span class="w">

</span><span class="p">(</span><span class="nf">create-batches</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="p">{</span><span class="no">:observations</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="p">[[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">5</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">7</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">10</span><span class="p">]])})</span><span class="w">
</span><span class="c1">; [{:observations tensor([[1.],</span><span class="w">
</span><span class="c1">;         [2.],</span><span class="w">
</span><span class="c1">;         [3.],</span><span class="w">
</span><span class="c1">;         [4.],</span><span class="w">
</span><span class="c1">;         [5.]])} {:observations tensor([[ 6.],</span><span class="w">
</span><span class="c1">;         [ 7.],</span><span class="w">
</span><span class="c1">;         [ 8.],</span><span class="w">
</span><span class="c1">;         [ 9.],</span><span class="w">
</span><span class="c1">;         [10.]])}]</span></code></pre></figure>

<h4 id="putting-it-all-together">Putting it All Together</h4>

<p>Finally we can implement a method which</p>
<ul>
  <li>samples data</li>
  <li>adds advantages</li>
  <li>converts to PyTorch tensors</li>
  <li>adds critic targets</li>
  <li>normalizes the advantages</li>
  <li>shuffles the samples</li>
  <li>creates batches</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sample-with-advantage-and-critic-target</span><span class="w">
  </span><span class="s">"Create batches of samples and add add advantages and critic target values"</span><span class="w">
  </span><span class="p">[</span><span class="n">environment-factory</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">batch-size</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">-&gt;&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">sample-environment</span><span class="w"> </span><span class="n">environment-factory</span><span class="w"> </span><span class="p">(</span><span class="nf">indeterministic-act</span><span class="w"> </span><span class="n">actor</span><span class="p">)</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">assoc-advantages</span><span class="w"> </span><span class="p">(</span><span class="nf">critic-observation</span><span class="w"> </span><span class="n">critic</span><span class="p">)</span><span class="w"> </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="p">)</span><span class="w">
       </span><span class="n">tensor-batch</span><span class="w">
       </span><span class="p">(</span><span class="nf">assoc-critic-target</span><span class="w"> </span><span class="n">critic</span><span class="p">)</span><span class="w">
       </span><span class="n">normalize-advantages</span><span class="w">
       </span><span class="n">shuffle-samples</span><span class="w">
       </span><span class="p">(</span><span class="nf">create-batches</span><span class="w"> </span><span class="n">batch-size</span><span class="p">)))</span></code></pre></figure>

<h3 id="ppo-main-loop">PPO Main Loop</h3>

<p>Now we can implement the PPO main loop.</p>

<p>The outer loop samples the environment using the current actor (i.e. policy) and computes the data required for training.</p>

<p>The inner loop performs a small number of updates using the samples from the outer loop.</p>

<p>Each update step performs a gradient descent update for the actor and a gradient descent update for the critic.
Another detail from <a href="https://github.com/XinJingHao/PPO-Continuous-Pytorch/">XinJingHao’s PPO implementation</a> is that the gradient norm for the actor update is clipped.</p>

<p>At the end of the loop, the smoothed loss values are shown and the deterministic actions and entropies for a few observations are shown which helps with parameter tuning.
Furthermore the entropy factor is slowly lowered so that the policy reduces exploration over time.</p>

<p>The actor and critic model are saved to disk after each checkpoint.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">-main</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">_args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">factory</span><span class="w">          </span><span class="n">pendulum-factory</span><span class="w">
        </span><span class="n">actor</span><span class="w">            </span><span class="p">(</span><span class="nf">Actor</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
        </span><span class="n">critic</span><span class="w">           </span><span class="p">(</span><span class="nf">Critic</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">64</span><span class="p">)</span><span class="w">
        </span><span class="n">n-epochs</span><span class="w">         </span><span class="mi">100000</span><span class="w">
        </span><span class="n">n-updates</span><span class="w">        </span><span class="mi">10</span><span class="w">
        </span><span class="n">gamma</span><span class="w">            </span><span class="mf">0.99</span><span class="w">
        </span><span class="n">lambda</span><span class="w">           </span><span class="mf">1.0</span><span class="w">
        </span><span class="n">epsilon</span><span class="w">          </span><span class="mf">0.2</span><span class="w">
        </span><span class="n">n-batches</span><span class="w">        </span><span class="mi">8</span><span class="w">
        </span><span class="n">batch-size</span><span class="w">       </span><span class="mi">50</span><span class="w">
        </span><span class="n">checkpoint</span><span class="w">       </span><span class="mi">100</span><span class="w">
        </span><span class="n">entropy-factor</span><span class="w">   </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="mf">0.1</span><span class="p">)</span><span class="w">
        </span><span class="n">entropy-decay</span><span class="w">    </span><span class="mf">0.999</span><span class="w">
        </span><span class="n">lr</span><span class="w">               </span><span class="mi">5</span><span class="n">e-5</span><span class="w">
        </span><span class="n">weight-decay</span><span class="w">     </span><span class="mi">1</span><span class="n">e-4</span><span class="w">
        </span><span class="n">smooth-actor-loss</span><span class="w">  </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
        </span><span class="n">smooth-critic-loss</span><span class="w"> </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
        </span><span class="n">actor-optimizer</span><span class="w">  </span><span class="p">(</span><span class="nf">adam-optimizer</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">lr</span><span class="w"> </span><span class="n">weight-decay</span><span class="p">)</span><span class="w">
        </span><span class="n">critic-optimizer</span><span class="w"> </span><span class="p">(</span><span class="nf">adam-optimizer</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="n">lr</span><span class="w"> </span><span class="n">weight-decay</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">epoch</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n-epochs</span><span class="p">)]</span><span class="w">
           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">samples</span><span class="w"> </span><span class="p">(</span><span class="nf">sample-with-advantage-and-critic-target</span><span class="w"> </span><span class="n">factory</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">critic</span><span class="w">
                                                                  </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">batch-size</span><span class="w"> </span><span class="n">n-batches</span><span class="p">)</span><span class="w">
                                                                  </span><span class="n">batch-size</span><span class="w">
                                                                  </span><span class="n">gamma</span><span class="w"> </span><span class="n">lambda</span><span class="p">)]</span><span class="w">
             </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">k</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n-updates</span><span class="p">)]</span><span class="w">
                    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">batch</span><span class="w"> </span><span class="n">samples</span><span class="p">]</span><span class="w">
                           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">loss</span><span class="w"> </span><span class="p">(</span><span class="nf">actor-loss</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">epsilon</span><span class="w"> </span><span class="o">@</span><span class="n">entropy-factor</span><span class="p">)]</span><span class="w">
                             </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor-optimizer</span><span class="w"> </span><span class="n">zero_grad</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">loss</span><span class="w"> </span><span class="n">backward</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">utils/clip_grad_norm_</span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">parameters</span><span class="p">)</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor-optimizer</span><span class="w"> </span><span class="n">step</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">swap!</span><span class="w"> </span><span class="n">smooth-actor-loss</span><span class="w">
                                    </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.999</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.001</span><span class="w"> </span><span class="p">(</span><span class="nf">toitem</span><span class="w"> </span><span class="n">loss</span><span class="p">)))))</span><span class="w"> </span><span class="p">))</span><span class="w">
                    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">batch</span><span class="w"> </span><span class="n">samples</span><span class="p">]</span><span class="w">
                           </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">loss</span><span class="w"> </span><span class="p">(</span><span class="nf">critic-loss</span><span class="w"> </span><span class="n">batch</span><span class="w"> </span><span class="n">critic</span><span class="p">)]</span><span class="w">
                             </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">critic-optimizer</span><span class="w"> </span><span class="n">zero_grad</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">loss</span><span class="w"> </span><span class="n">backward</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">critic-optimizer</span><span class="w"> </span><span class="n">step</span><span class="p">)</span><span class="w">
                             </span><span class="p">(</span><span class="nf">swap!</span><span class="w"> </span><span class="n">smooth-critic-loss</span><span class="w">
                                    </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.999</span><span class="w"> </span><span class="n">x</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.001</span><span class="w"> </span><span class="p">(</span><span class="nf">toitem</span><span class="w"> </span><span class="n">loss</span><span class="p">))))))))</span><span class="w">
             </span><span class="p">(</span><span class="nb">println</span><span class="w"> </span><span class="s">"Epoch:"</span><span class="w"> </span><span class="n">epoch</span><span class="w">
                      </span><span class="s">"Actor Loss:"</span><span class="w"> </span><span class="o">@</span><span class="n">smooth-actor-loss</span><span class="w">
                      </span><span class="s">"Critic Loss:"</span><span class="w"> </span><span class="o">@</span><span class="n">smooth-critic-loss</span><span class="w">
                      </span><span class="s">"Entropy Factor:"</span><span class="w"> </span><span class="o">@</span><span class="n">entropy-factor</span><span class="p">))</span><span class="w">
           </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
             </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">input</span><span class="w"> </span><span class="p">[[</span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">-1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="mf">-1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mf">-1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">-1.0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]]]</span><span class="w">
                    </span><span class="p">(</span><span class="nb">println</span><span class="w">
                      </span><span class="n">input</span><span class="w">
                      </span><span class="s">"-&gt;"</span><span class="w"> </span><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="p">(</span><span class="nf">tolist</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">deterministic_act</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">input</span><span class="p">))))</span><span class="w">
                      </span><span class="s">"entropy"</span><span class="w"> </span><span class="p">(</span><span class="nf">toitem</span><span class="w"> </span><span class="p">(</span><span class="nf">entropy-of-distribution</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">input</span><span class="p">))))))</span><span class="w">
           </span><span class="p">(</span><span class="nf">swap!</span><span class="w"> </span><span class="n">entropy-factor</span><span class="w"> </span><span class="nb">*</span><span class="w"> </span><span class="n">entropy-decay</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">=</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">epoch</span><span class="w"> </span><span class="n">checkpoint</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">dec</span><span class="w"> </span><span class="n">checkpoint</span><span class="p">))</span><span class="w">
             </span><span class="p">(</span><span class="nb">println</span><span class="w"> </span><span class="s">"Saving models"</span><span class="p">)</span><span class="w">
             </span><span class="p">(</span><span class="nf">torch/save</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">state_dict</span><span class="p">)</span><span class="w"> </span><span class="s">"actor.pt"</span><span class="p">)</span><span class="w">
             </span><span class="p">(</span><span class="nf">torch/save</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="n">state_dict</span><span class="p">)</span><span class="w"> </span><span class="s">"critic.pt"</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">torch/save</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">state_dict</span><span class="p">)</span><span class="w"> </span><span class="s">"actor.pt"</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">torch/save</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">critic</span><span class="w"> </span><span class="n">state_dict</span><span class="p">)</span><span class="w"> </span><span class="s">"critic.pt"</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">System/exit</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span></code></pre></figure>

<h2 id="visualisation-of-actor-output">Visualisation of Actor Output</h2>

<p>We can use <a href="https://cnuernber.github.io/dtype-next/">dtype-next</a> to visualise the output of the actor.
First we need to load additional modules.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.datatype</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dtype</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.tensor</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dtt</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.libs.buffered-image</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">bufimg</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.datatype.functional</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dfn</span><span class="p">])</span></code></pre></figure>

<p>Here we load a pre-trained model and visualise the output of the actor.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="p">(</span><span class="nf">Actor</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w">
</span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">load_state_dict</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/load</span><span class="w"> </span><span class="s">"src/ppo/actor.pt"</span><span class="p">))</span><span class="w">
</span><span class="c1">; &lt;All keys matched successfully&gt;</span><span class="w">

</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">angle-values</span><span class="w">   </span><span class="p">(</span><span class="nf">torch/linspace</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">PI</span><span class="p">)</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mi">854</span><span class="p">)</span><span class="w">
      </span><span class="n">speed-values</span><span class="w">   </span><span class="p">(</span><span class="nf">torch/linspace</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span><span class="w">
      </span><span class="n">grid</span><span class="w">           </span><span class="p">(</span><span class="nf">torch/meshgrid</span><span class="w"> </span><span class="n">speed-values</span><span class="w"> </span><span class="n">angle-values</span><span class="w"> </span><span class="no">:indexing</span><span class="w"> </span><span class="s">"ij"</span><span class="p">)</span><span class="w">
      </span><span class="n">cos-angle</span><span class="w">      </span><span class="p">(</span><span class="nf">torch/cos</span><span class="w"> </span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="n">grid</span><span class="p">))</span><span class="w">
      </span><span class="n">sin-angle</span><span class="w">      </span><span class="p">(</span><span class="nf">torch/sin</span><span class="w"> </span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="n">grid</span><span class="p">))</span><span class="w">
      </span><span class="n">observations</span><span class="w">   </span><span class="p">(</span><span class="nf">torch/stack</span><span class="w"> </span><span class="p">[(</span><span class="nf">py.</span><span class="w"> </span><span class="n">cos-angle</span><span class="w"> </span><span class="n">ravel</span><span class="p">)</span><span class="w">
                                   </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">sin-angle</span><span class="w"> </span><span class="n">ravel</span><span class="p">)</span><span class="w">
                                   </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">grid</span><span class="p">)</span><span class="w"> </span><span class="n">ravel</span><span class="p">)]</span><span class="w">
                                  </span><span class="no">:axis</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
      </span><span class="n">actions</span><span class="w">        </span><span class="p">(</span><span class="nf">without-gradient</span><span class="w">
                       </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">deterministic_act</span><span class="w"> </span><span class="n">observations</span><span class="p">)</span><span class="w">
                                 </span><span class="n">reshape</span><span class="w"> </span><span class="mi">480</span><span class="w"> </span><span class="mi">854</span><span class="p">)</span><span class="w"> </span><span class="n">numpy</span><span class="p">))</span><span class="w">
      </span><span class="n">actions-tensor</span><span class="w"> </span><span class="p">(</span><span class="nf">dtt/clone</span><span class="w">
                       </span><span class="p">(</span><span class="nf">dtype/elemwise-cast</span><span class="w"> </span><span class="p">(</span><span class="nf">dtt/ensure-tensor</span><span class="w"> </span><span class="p">(</span><span class="nf">py/-&gt;jvm</span><span class="w"> </span><span class="n">actions</span><span class="p">))</span><span class="w">
                                            </span><span class="no">:float32</span><span class="p">))</span><span class="w">
      </span><span class="n">actions-trsps</span><span class="w">  </span><span class="p">(</span><span class="nf">dtt/transpose</span><span class="w"> </span><span class="n">actions-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">])]</span><span class="w">
  </span><span class="p">(</span><span class="nf">dtt/mset!</span><span class="w"> </span><span class="n">actions-tensor</span><span class="w"> </span><span class="mi">240</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nf">actions-tensor</span><span class="w"> </span><span class="mi">240</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">dtt/mset!</span><span class="w"> </span><span class="n">actions-trsps</span><span class="w"> </span><span class="mi">427</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nf">actions-trsps</span><span class="w"> </span><span class="mi">427</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="n">actions-tensor</span><span class="w"> </span><span class="mi">255</span><span class="p">)))</span></code></pre></figure>

<p><img src="/pics/actorfunction.png" alt="Actor function output over state space" />
This image shows the motor control input as a function of pendulum angle and angular velocity.
As one can see, the pendulum is decelerated when the speed is high (dark values at the top of the image).
Near the centre of the image (speed zero and angle zero) one can see how the pendulum is accelerated when the angle is negative and the speed small and decelerated when the angle is positive and the speed is small.
Also the image is not symmetrical because otherwise the pendulum would not start swinging up when pointing downwards (left and right boundary of the image).</p>

<h2 id="automated-pendulum">Automated Pendulum</h2>

<p>The pendulum implementation can now be updated to use the actor instead of the mouse position as motor input when the mouse button is pressed.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">-main</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">_args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">actor</span><span class="w">       </span><span class="p">(</span><span class="nf">Actor</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
        </span><span class="n">done-chan</span><span class="w">   </span><span class="p">(</span><span class="nf">async/chan</span><span class="p">)</span><span class="w">
        </span><span class="n">last-action</span><span class="w"> </span><span class="p">(</span><span class="nf">atom</span><span class="w"> </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="mf">0.0</span><span class="p">})]</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nf">.exists</span><span class="w"> </span><span class="p">(</span><span class="nf">java.io.File.</span><span class="w"> </span><span class="s">"actor.pt"</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w"> </span><span class="n">load_state_dict</span><span class="w"> </span><span class="p">(</span><span class="nf">torch/load</span><span class="w"> </span><span class="s">"actor.pt"</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">q/sketch</span><span class="w">
      </span><span class="no">:title</span><span class="w"> </span><span class="s">"Inverted Pendulum with Mouse Control"</span><span class="w">
      </span><span class="no">:size</span><span class="w"> </span><span class="p">[</span><span class="mi">854</span><span class="w"> </span><span class="mi">480</span><span class="p">]</span><span class="w">
      </span><span class="no">:setup</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">setup</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
      </span><span class="no">:update</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">state</span><span class="p">]</span><span class="w">
                  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">observation</span><span class="w"> </span><span class="p">(</span><span class="nf">observation</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w">
                        </span><span class="n">action</span><span class="w">      </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nf">q/mouse-pressed?</span><span class="p">)</span><span class="w">
                                      </span><span class="p">(</span><span class="nf">action</span><span class="w"> </span><span class="p">(</span><span class="nf">tolist</span><span class="w"> </span><span class="p">(</span><span class="nf">py.</span><span class="w"> </span><span class="n">actor</span><span class="w">
                                                           </span><span class="n">deterministic_act</span><span class="w">
                                                           </span><span class="p">(</span><span class="nf">tensor</span><span class="w"> </span><span class="n">observation</span><span class="p">))))</span><span class="w">
                                      </span><span class="p">{</span><span class="no">:control</span><span class="w"> </span><span class="p">(</span><span class="nb">min</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
                                                     </span><span class="p">(</span><span class="nb">max</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                                                          </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nf">q/mouse-x</span><span class="p">)</span><span class="w">
                                                                    </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nf">q/width</span><span class="p">)</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)))))})</span><span class="w">
                        </span><span class="n">state</span><span class="w">       </span><span class="p">(</span><span class="nf">update-state</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">action</span><span class="w"> </span><span class="n">config</span><span class="p">)]</span><span class="w">
                    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nf">done?</span><span class="w"> </span><span class="n">state</span><span class="w"> </span><span class="n">config</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">async/close!</span><span class="w"> </span><span class="n">done-chan</span><span class="p">))</span><span class="w">
                    </span><span class="p">(</span><span class="nf">reset!</span><span class="w"> </span><span class="n">last-action</span><span class="w"> </span><span class="n">action</span><span class="p">)</span><span class="w">
                    </span><span class="n">state</span><span class="p">))</span><span class="w">
      </span><span class="no">:draw</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">draw-state</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="o">@</span><span class="n">last-action</span><span class="p">)</span><span class="w">
      </span><span class="no">:middleware</span><span class="w"> </span><span class="p">[</span><span class="n">m/fun-mode</span><span class="p">]</span><span class="w">
      </span><span class="no">:on-close</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">_</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">async/close!</span><span class="w"> </span><span class="n">done-chan</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">async/&lt;!!</span><span class="w"> </span><span class="n">done-chan</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">System/exit</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>Here is a small demo video of the pendulum being controlled using the actor network.
You can find a repository with the code of this article as well as unit tests at <a href="https://github.com/wedesoft/ppo">github.com/wedesoft/ppo</a>.</p>

<p><img src="/pics/automatic.gif" alt="automatically controlled pendulum" /></p>

<p>Enjoy!</p>]]></content><author><name>Jan Wedekind</name></author><category term="ai" /><summary type="html"><![CDATA[(Cross posting article published at Clojure Civitas)]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/pendulum.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/pendulum.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Installing Pytorch with AMD ROCm on GNU/Linux</title><link href="https://www.wedesoft.de/graphics/2026/03/24/rocm-torch-install/" rel="alternate" type="text/html" title="Installing Pytorch with AMD ROCm on GNU/Linux" /><published>2026-03-24T00:00:00+00:00</published><updated>2026-03-24T00:00:00+00:00</updated><id>https://www.wedesoft.de/graphics/2026/03/24/rocm-torch-install</id><content type="html" xml:base="https://www.wedesoft.de/graphics/2026/03/24/rocm-torch-install/"><![CDATA[<p>Quickly sharing my notes on how to install drivers for ROCm and Pytorch for machine learning on AMD GPUs:</p>

<p>First <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/quick-start.html">install ROCm and the AMD GPU driver</a>:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell"><span class="c"># Install ROCm</span>
wget https://repo.radeon.com/amdgpu-install/7.2.1/ubuntu/noble/amdgpu-install_7.2.1.70201-1_all.deb
<span class="nb">sudo </span>apt <span class="nb">install</span> ./amdgpu-install_7.2.1.70201-1_all.deb
<span class="c"># Install AMD driver</span>
wget https://repo.radeon.com/amdgpu-install/7.2.1/ubuntu/noble/amdgpu-install_7.2.1.70201-1_all.deb
<span class="nb">sudo </span>apt <span class="nb">install</span> ./amdgpu-install_7.2.1.70201-1_all.deb
<span class="nb">sudo </span>apt update
<span class="nb">sudo </span>apt <span class="nb">install</span> <span class="s2">"linux-headers-</span><span class="si">$(</span><span class="nb">uname</span> <span class="nt">-r</span><span class="si">)</span><span class="s2">"</span>
<span class="nb">sudo </span>apt <span class="nb">install </span>amdgpu-dkms</code></pre></figure>

<p>Then as shown in this <a href="https://www.reddit.com/r/comfyui/comments/1njjp79/complete_rocm_70_pytorch_280_installation_guide/">Reddit post</a>, I installed install triton, torch, torchvision, and torchaudio from <a href="https://repo.radeon.com/rocm/manylinux/">https://repo.radeon.com/rocm/manylinux/</a>.</p>

<p>Then I tried the following program to train a neural network to imitate an XOR gate.</p>

<figure class="highlight"><pre><code class="language-python" data-lang="python"><span class="kn">import</span> <span class="n">torch</span>
<span class="kn">import</span> <span class="n">torch.nn</span> <span class="k">as</span> <span class="n">nn</span>
<span class="kn">import</span> <span class="n">torch.optim</span> <span class="k">as</span> <span class="n">optim</span>

<span class="c1"># Check if GPU is available
</span><span class="n">device</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">device</span><span class="p">(</span><span class="sh">"</span><span class="s">cuda</span><span class="sh">"</span> <span class="k">if</span> <span class="n">torch</span><span class="p">.</span><span class="n">cuda</span><span class="p">.</span><span class="nf">is_available</span><span class="p">()</span> <span class="k">else</span> <span class="sh">"</span><span class="s">cpu</span><span class="sh">"</span><span class="p">)</span>

<span class="c1"># XOR data
</span><span class="n">X</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">tensor</span><span class="p">([[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">]],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
<span class="n">Y</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">tensor</span><span class="p">([[</span><span class="mi">0</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">1</span><span class="p">],</span> <span class="p">[</span><span class="mi">0</span><span class="p">]],</span> <span class="n">dtype</span><span class="o">=</span><span class="n">torch</span><span class="p">.</span><span class="n">float32</span><span class="p">).</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>

<span class="c1"># Define the neural network
</span><span class="k">class</span> <span class="nc">XORNet</span><span class="p">(</span><span class="n">nn</span><span class="p">.</span><span class="n">Module</span><span class="p">):</span>
    <span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="n">self</span><span class="p">):</span>
        <span class="nf">super</span><span class="p">(</span><span class="n">XORNet</span><span class="p">,</span> <span class="n">self</span><span class="p">).</span><span class="nf">__init__</span><span class="p">()</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc1</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">5</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">fc2</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Linear</span><span class="p">(</span><span class="mi">5</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span>
        <span class="n">self</span><span class="p">.</span><span class="n">sigmoid</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">Sigmoid</span><span class="p">()</span>

    <span class="k">def</span> <span class="nf">forward</span><span class="p">(</span><span class="n">self</span><span class="p">,</span> <span class="n">x</span><span class="p">):</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">torch</span><span class="p">.</span><span class="nf">relu</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">fc1</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
        <span class="n">x</span> <span class="o">=</span> <span class="n">self</span><span class="p">.</span><span class="nf">sigmoid</span><span class="p">(</span><span class="n">self</span><span class="p">.</span><span class="nf">fc2</span><span class="p">(</span><span class="n">x</span><span class="p">))</span>
        <span class="k">return</span> <span class="n">x</span>

<span class="c1"># Initialize the network, loss function and optimizer
</span><span class="n">model</span> <span class="o">=</span> <span class="nc">XORNet</span><span class="p">().</span><span class="nf">to</span><span class="p">(</span><span class="n">device</span><span class="p">)</span>
<span class="n">criterion</span> <span class="o">=</span> <span class="n">nn</span><span class="p">.</span><span class="nc">BCELoss</span><span class="p">()</span>
<span class="n">optimizer</span> <span class="o">=</span> <span class="n">optim</span><span class="p">.</span><span class="nc">SGD</span><span class="p">(</span><span class="n">model</span><span class="p">.</span><span class="nf">parameters</span><span class="p">(),</span> <span class="n">lr</span><span class="o">=</span><span class="mf">0.1</span><span class="p">)</span>

<span class="c1"># Training loop
</span><span class="k">for</span> <span class="n">epoch</span> <span class="ow">in</span> <span class="nf">range</span><span class="p">(</span><span class="mi">10000</span><span class="p">):</span>
    <span class="n">model</span><span class="p">.</span><span class="nf">train</span><span class="p">()</span>
    <span class="n">optimizer</span><span class="p">.</span><span class="nf">zero_grad</span><span class="p">()</span>
    <span class="n">outputs</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span><span class="n">X</span><span class="p">)</span>
    <span class="n">loss</span> <span class="o">=</span> <span class="nf">criterion</span><span class="p">(</span><span class="n">outputs</span><span class="p">,</span> <span class="n">Y</span><span class="p">)</span>
    <span class="n">loss</span><span class="p">.</span><span class="nf">backward</span><span class="p">()</span>
    <span class="n">optimizer</span><span class="p">.</span><span class="nf">step</span><span class="p">()</span>

    <span class="nf">if </span><span class="p">(</span><span class="n">epoch</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span> <span class="o">%</span> <span class="mi">1000</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
        <span class="nf">print</span><span class="p">(</span><span class="sa">f</span><span class="sh">'</span><span class="s">Epoch [</span><span class="si">{</span><span class="n">epoch</span><span class="o">+</span><span class="mi">1</span><span class="si">}</span><span class="s">/10000], Loss: </span><span class="si">{</span><span class="n">loss</span><span class="p">.</span><span class="nf">item</span><span class="p">()</span><span class="si">:</span><span class="p">.</span><span class="mi">4</span><span class="n">f</span><span class="si">}</span><span class="sh">'</span><span class="p">)</span>

<span class="c1"># Test the model
</span><span class="n">model</span><span class="p">.</span><span class="nf">eval</span><span class="p">()</span>
<span class="k">with</span> <span class="n">torch</span><span class="p">.</span><span class="nf">no_grad</span><span class="p">():</span>
    <span class="n">predictions</span> <span class="o">=</span> <span class="nf">model</span><span class="p">(</span><span class="n">X</span><span class="p">)</span>
    <span class="nf">print</span><span class="p">(</span><span class="sh">"</span><span class="s">Predictions:</span><span class="sh">"</span><span class="p">,</span> <span class="n">predictions</span><span class="p">.</span><span class="nf">round</span><span class="p">())</span></code></pre></figure>

<p>However I got the following error (using Torch 2.9.1 and ROCm 7.2.0).</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">RuntimeError: CUDA error: HIPBLAS_STATUS_INVALID_VALUE when calling <span class="sb">`</span>hipblasLtMatmulAlgoGetHeuristic<span class="o">(</span> ltHandle, computeDesc.descriptor<span class="o">()</span>, Adesc.descriptor<span class="o">()</span>, Bdesc.descriptor<span class="o">()</span>, Cdesc.descriptor<span class="o">()</span>, Cdesc.descriptor<span class="o">()</span>, preference.descriptor<span class="o">()</span>, 1, &amp;heuristicResult, &amp;returnedResult<span class="o">)</span><span class="sb">`</span></code></pre></figure>

<p>Then I found AMD’s information on <a href="https://rocm.docs.amd.com/projects/install-on-linux/en/latest/install/3rd-party/pytorch-install.html#use-a-wheels-package">how to install Pytorch with ROCm support</a>.
Basically you need to install the nightly build:</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">pip3 <span class="nb">install</span> <span class="nt">--pre</span> torch torchvision torchaudio <span class="nt">--index-url</span> https://download.pytorch.org/whl/nightly/rocm7.2</code></pre></figure>

<p>Now the XOR test works!</p>

<figure class="highlight"><pre><code class="language-shell" data-lang="shell">python3 xor.py
<span class="c"># Epoch [1000/10000], Loss: 0.0342</span>
<span class="c"># Epoch [2000/10000], Loss: 0.0114</span>
<span class="c"># Epoch [3000/10000], Loss: 0.0066</span>
<span class="c"># Epoch [4000/10000], Loss: 0.0046</span>
<span class="c"># Epoch [5000/10000], Loss: 0.0035</span>
<span class="c"># Epoch [6000/10000], Loss: 0.0028</span>
<span class="c"># Epoch [7000/10000], Loss: 0.0024</span>
<span class="c"># Epoch [8000/10000], Loss: 0.0020</span>
<span class="c"># Epoch [9000/10000], Loss: 0.0018</span>
<span class="c"># Epoch [10000/10000], Loss: 0.0016</span>
<span class="c"># Predictions: tensor([[0.],</span>
<span class="c">#         [1.],</span>
<span class="c">#         [1.],</span>
<span class="c">#         [0.]], device='cuda:0')</span></code></pre></figure>

<p>Enjoy!</p>]]></content><author><name>Jan Wedekind</name></author><category term="graphics" /><summary type="html"><![CDATA[Quickly sharing my notes on how to install drivers for ROCm and Pytorch for machine learning on AMD GPUs:]]></summary></entry><entry><title type="html">Volumetric Clouds with Clojure and LWJGL</title><link href="https://www.wedesoft.de/graphics/2026/01/27/volumetric-clouds/" rel="alternate" type="text/html" title="Volumetric Clouds with Clojure and LWJGL" /><published>2026-01-27T00:00:00+00:00</published><updated>2026-01-27T00:00:00+00:00</updated><id>https://www.wedesoft.de/graphics/2026/01/27/volumetric-clouds</id><content type="html" xml:base="https://www.wedesoft.de/graphics/2026/01/27/volumetric-clouds/"><![CDATA[<p>Procedural generation of volumetric clouds using different types of noise</p>

<p><em>(Cross posting article published at <a href="https://clojurecivitas.org/volumetric_clouds/main.html">Clojure Civitas</a>)</em></p>

<h2 id="dependencies">Dependencies</h2>

<p>To download the required libraries, we use a <code class="language-plaintext highlighter-rouge">deps.edn</code> file with the following content:
Replace the <em>natives-linux</em> classifier with <em>natives-macos</em> or <em>natives-windows</em> as required.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w">
 </span><span class="p">{</span><span class="w">
  </span><span class="n">org.clojure/clojure</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.12.3"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.scicloj/noj</span><span class="w">                      </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"2-beta18"</span><span class="p">}</span><span class="w">
  </span><span class="n">midje/midje</span><span class="w">                          </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.10.10"</span><span class="p">}</span><span class="w">
  </span><span class="n">generateme/fastmath</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.0.0-alpha4"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl</span><span class="w">                      </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl$natives-linux</span><span class="w">        </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-opengl</span><span class="w">               </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-opengl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-glfw</span><span class="w">                 </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">org.lwjgl/lwjgl-glfw$natives-linux</span><span class="w">   </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.4.0"</span><span class="p">}</span><span class="w">
  </span><span class="n">comb/comb</span><span class="w">                            </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"1.0.0"</span><span class="p">}</span><span class="w">
  </span><span class="p">}</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>We are going to import the following methods and namespaces:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.math</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">PI</span><span class="w"> </span><span class="n">sqrt</span><span class="w"> </span><span class="n">cos</span><span class="w"> </span><span class="n">sin</span><span class="w"> </span><span class="n">tan</span><span class="w"> </span><span class="n">to-radians</span><span class="w"> </span><span class="n">pow</span><span class="w"> </span><span class="n">floor</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">midje.sweet</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="n">facts</span><span class="w"> </span><span class="n">tabular</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">roughly</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.vector</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">vec3</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">mult</span><span class="w"> </span><span class="n">sub</span><span class="w"> </span><span class="n">div</span><span class="w"> </span><span class="n">mag</span><span class="w"> </span><span class="n">dot</span><span class="w"> </span><span class="n">normalize</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.matrix</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">mat-&gt;float-array</span><span class="w"> </span><span class="n">mulm</span><span class="w">
                                   </span><span class="n">rotation-matrix-3d-x</span><span class="w"> </span><span class="n">rotation-matrix-3d-y</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.datatype</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dtype</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.tensor</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">tensor</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.datatype.functional</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">dfn</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tablecloth.api</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">tc</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">scicloj.tableplot.v1.plotly</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">plotly</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">tech.v3.libs.buffered-image</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">bufimg</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">comb.template</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">template</span><span class="p">])</span><span class="w">
</span><span class="p">(</span><span class="nb">import</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.opengl</span><span class="w"> </span><span class="n">GL11</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl</span><span class="w"> </span><span class="n">BufferUtils</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.glfw</span><span class="w"> </span><span class="n">GLFW</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.opengl</span><span class="w"> </span><span class="n">GL</span><span class="w"> </span><span class="n">GL11</span><span class="w"> </span><span class="n">GL12</span><span class="w"> </span><span class="n">GL13</span><span class="w"> </span><span class="n">GL15</span><span class="w"> </span><span class="n">GL20</span><span class="w"> </span><span class="n">GL30</span><span class="w"> </span><span class="n">GL32</span><span class="w"> </span><span class="n">GL42</span><span class="p">])</span></code></pre></figure>

<h2 id="worley-noise">Worley noise</h2>

<p><a href="https://en.wikipedia.org/wiki/Worley_noise">Worley noise</a> is a type of structured noise which is defined for each pixel using the distance to the nearest seed point.</p>

<h3 id="noise-parameters">Noise parameters</h3>

<p>First we define a function to create parameters of the noise.</p>

<ul>
  <li><strong>size</strong> is the size of each dimension of the noise array</li>
  <li><strong>divisions</strong> is the number of subdividing cells in each dimension</li>
  <li><strong>dimensions</strong> is the number of dimensions</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-noise-params</span><span class="w">
  </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">divisions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w">
  </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="no">:divisions</span><span class="w"> </span><span class="n">divisions</span><span class="w"> </span><span class="no">:cellsize</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">divisions</span><span class="p">)</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">})</span></code></pre></figure>

<p>Here is a corresponding <a href="https://github.com/marick/Midje">Midje</a> test.
Note that ideally you practise <a href="https://martinfowler.com/bliki/TestDrivenDevelopment.html">Test Driven Development (TDD)</a>, i.e. you start with writing one failing test.
Because this is a Clojure notebook, the unit tests are displayed after the implementation.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="s">"Noise parameter initialisation"</span><span class="w">
      </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">})</span></code></pre></figure>

<h3 id="2d-and-3d-vectors">2D and 3D vectors</h3>

<p>Next we need a function which allows us to create 2D or 3D vectors depending on the number of input parameters.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">vec-n</span><span class="w">
  </span><span class="p">([</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">))</span><span class="w">
  </span><span class="p">([</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Generic vector function for creating 2D and 3D vectors"</span><span class="w">
       </span><span class="p">(</span><span class="nf">vec-n</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">vec-n</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span></code></pre></figure>

<h3 id="random-points">Random points</h3>

<p>The following method generates a random point in a cell specified by the cell indices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-point-in-cell</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">cellsize</span><span class="p">]}</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">random-seq</span><span class="w"> </span><span class="p">(</span><span class="nf">repeatedly</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">rand</span><span class="w"> </span><span class="n">cellsize</span><span class="p">))</span><span class="w">
        </span><span class="n">dimensions</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">indices</span><span class="p">))</span><span class="w"> </span><span class="n">cellsize</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">random-seq</span><span class="p">)))))</span></code></pre></figure>

<p>We test the method by replacing the random function with a deterministic function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Place random point in a cell"</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">s</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">1</span><span class="p">}</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">7.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-point-in-cell</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">11.0</span><span class="w"> </span><span class="mf">7.0</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)))</span></code></pre></figure>

<p>We can now use the <code class="language-plaintext highlighter-rouge">random-point</code> method to generate a grid of random points.
The grid is represented using a tensor from the <a href="https://cnuernber.github.io/dtype-next/">dtype-next</a> library.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-points</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">divisions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}]</span><span class="w">
  </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">divisions</span><span class="p">)</span><span class="w">
                           </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">random-point-in-cell</span><span class="w"> </span><span class="n">params</span><span class="p">))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Greate grid of random points"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">params-2d</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
             </span><span class="n">params-3d</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">3</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">s</span><span class="p">))]</span><span class="w">
           </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">14.0</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-2d</span><span class="p">)</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="mf">10.0</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-3d</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
           </span><span class="p">((</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params-3d</span><span class="p">)</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">22.0</span><span class="w"> </span><span class="mf">14.0</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))))</span></code></pre></figure>

<p>Here is a scatter plot showing one random point placed in each cell.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">points</span><span class="w">  </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="p">[(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">)])</span><span class="w">
      </span><span class="n">scatter</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="n">points</span><span class="p">)</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">second</span><span class="w"> </span><span class="n">points</span><span class="p">)})]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Random points"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">})))</span></code></pre></figure>

<p><img src="/pics/randompoints.png" alt="random points" /></p>

<h3 id="modular-distance">Modular distance</h3>

<p>In order to get a periodic noise array, we need to component-wise wrap around distance vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">mod-vec</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="p">]}</span><span class="w"> </span><span class="n">v</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">size2</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
        </span><span class="n">wrap</span><span class="w">  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">size2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">size2</span><span class="p">)))]</span><span class="w">
    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">wrap</span><span class="w"> </span><span class="n">v</span><span class="p">))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Wrap around components of vector to be within -size/2..size/2"</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">-3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">-3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">-3</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">-5</span><span class="w"> </span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">-5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span></code></pre></figure>

<p>Using the <code class="language-plaintext highlighter-rouge">mod-dist</code> function we can calculate the distance between two points in the periodic noise array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">mod-dist</span><span class="w">
  </span><span class="p">[</span><span class="n">params</span><span class="w"> </span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="p">(</span><span class="nf">mod-vec</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="n">a</span><span class="p">))))</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">tabular</code> macro implemented by Midje is useful for running parametrized tests.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Wrapped distance of two points"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nf">mod-dist</span><span class="w"> </span><span class="p">{</span><span class="no">:size</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">?ax</span><span class="w"> </span><span class="n">?ay</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">?bx</span><span class="w"> </span><span class="n">?by</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?ax</span><span class="w"> </span><span class="n">?ay</span><span class="w"> </span><span class="n">?bx</span><span class="w"> </span><span class="n">?by</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">5</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">3.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">5</span><span class="w">   </span><span class="mf">3.0</span><span class="w">
         </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">5</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">3.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">2.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">5</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mf">3.0</span><span class="p">)</span></code></pre></figure>

<h3 id="modular-lookup">Modular lookup</h3>

<p>We also need to lookup elements with wrap around.
We recursively use <code class="language-plaintext highlighter-rouge">tensor/select</code> and then finally the tensor as a function to lookup along each axis.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">wrap-get</span><span class="w">
  </span><span class="p">[</span><span class="n">t</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">t</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">tensor/select</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">mod</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">t</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">mod</span><span class="w"> </span><span class="n">args</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">t</span><span class="p">)))))</span></code></pre></figure>

<p>A tensor with index vectors is used to test the lookup.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Wrapped lookup of tensor values"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="n">vec2</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">7</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="mi">5</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="p">)))</span></code></pre></figure>

<p>The following function converts a noise coordinate to the index of a cell in the random point array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">division-index</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">cellsize</span><span class="p">]}</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">int</span><span class="w"> </span><span class="p">(</span><span class="nf">floor</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">cellsize</span><span class="p">))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Convert coordinate to division index"</span><span class="w">
       </span><span class="p">(</span><span class="nf">division-index</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w">  </span><span class="n">=&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w">
       </span><span class="p">(</span><span class="nf">division-index</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="mf">7.5</span><span class="p">)</span><span class="w">  </span><span class="n">=&gt;</span><span class="w"> </span><span class="mi">1</span><span class="w">
       </span><span class="p">(</span><span class="nf">division-index</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="mf">-0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mi">-1</span><span class="p">)</span></code></pre></figure>

<h3 id="getting-indices-of-neighbours">Getting indices of Neighbours</h3>

<p>The following function determines the neighbouring indices of a cell recursing over each dimension.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">neighbours</span><span class="w">
  </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">mapcat</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">v</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">delta</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">into</span><span class="w"> </span><span class="p">[(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="n">delta</span><span class="p">)]</span><span class="w"> </span><span class="n">v</span><span class="p">))</span><span class="w"> </span><span class="p">[</span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">]))</span><span class="w">
            </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">neighbours</span><span class="w"> </span><span class="p">(</span><span class="nb">rest</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w"> </span><span class="p">)</span><span class="w">
    </span><span class="p">[[]]))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Get neighbouring indices"</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[]]</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[</span><span class="mi">-1</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="p">]]</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="w"> </span><span class="mi">3</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[</span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">3</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="p">]]</span><span class="w">
       </span><span class="p">(</span><span class="nf">neighbours</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[[</span><span class="mi">0</span><span class="w"> </span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">9</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">10</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">11</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">1</span><span class="w"> </span><span class="mi">11</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">11</span><span class="p">]])</span></code></pre></figure>

<h3 id="sampling-worley-noise">Sampling Worley noise</h3>

<p>Using above functions one can now implement Worley noise.
For each pixel the distance to the closest seed point is calculated.
This is achieved by determining the distance to each random point in all neighbouring cells and then taking the minimum.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">worley-noise</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">random-points</span><span class="w"> </span><span class="p">(</span><span class="nf">random-points</span><span class="w"> </span><span class="n">params</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
        </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">coords</span><span class="p">]</span><span class="w">
            </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">center</span><span class="w">   </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">coords</span><span class="p">)</span><span class="w">
                  </span><span class="n">division</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">division-index</span><span class="w"> </span><span class="n">params</span><span class="p">)</span><span class="w"> </span><span class="n">center</span><span class="p">)]</span><span class="w">
              </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">min</span><span class="w">
                     </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">neighbour</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">neighbours</span><span class="w"> </span><span class="n">division</span><span class="p">)]</span><span class="w">
                          </span><span class="p">(</span><span class="nf">mod-dist</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">center</span><span class="p">))</span><span class="w">
                                    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">wrap-get</span><span class="w"> </span><span class="n">random-points</span><span class="w"> </span><span class="n">neighbour</span><span class="p">))))))</span><span class="w">
        </span><span class="no">:double</span><span class="p">))))</span></code></pre></figure>

<p>Here a 256 × 256 Worley noise tensor is created.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">worley</span><span class="w"> </span><span class="p">(</span><span class="nf">worley-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span></code></pre></figure>

<p>The values are inverted and normalised to be between 0 and 255.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">worley-norm</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">255</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">worley</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">worley</span><span class="p">)))</span><span class="w">
         </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">worley</span><span class="p">)</span><span class="w"> </span><span class="n">worley</span><span class="p">)))</span></code></pre></figure>

<p>Finally one can display the noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="n">worley-norm</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/worley256.png" alt="Worley noise" /></p>

<h2 id="perlin-noise">Perlin noise</h2>

<p><a href="https://adrianb.io/2014/08/09/perlinnoise.html">Perlin noise</a> is generated by choosing a random gradient vector at each cell corner.
The noise tensor’s intermediate values are interpolated with a continuous function, utilizing the gradient at the corner points.</p>

<h3 id="random-gradients">Random gradients</h3>

<p>The 2D or 3D gradients are generated by creating a vector where each component is set to a random number between -1 and 1.
Random vectors are generated until the vector length is greater 0 and lower or equal to 1.
The vector then is normalized and returned.
Random vectors outside the unit circle or sphere are discarded in order to achieve a uniform distribution on the surface of the unit circle or sphere.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-gradient</span><span class="w">
  </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">loop</span><span class="w"> </span><span class="p">[</span><span class="n">args</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
        </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">random-vector</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nb">rand</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w">
              </span><span class="n">vector-length</span><span class="w"> </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="n">random-vector</span><span class="p">)]</span><span class="w">
          </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">and</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;</span><span class="w"> </span><span class="n">vector-length</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">&lt;=</span><span class="w"> </span><span class="n">vector-length</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span><span class="w">
            </span><span class="p">(</span><span class="nf">div</span><span class="w"> </span><span class="n">random-vector</span><span class="w"> </span><span class="n">vector-length</span><span class="p">)</span><span class="w">
            </span><span class="p">(</span><span class="nf">recur</span><span class="w"> </span><span class="n">args</span><span class="p">)))))</span></code></pre></figure>

<p>The function below serves as a Midje checker for a vector with an approximate expected value.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">roughly-vec</span><span class="w">
  </span><span class="p">[</span><span class="n">expected</span><span class="w"> </span><span class="n">error</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">actual</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nb">&lt;=</span><span class="w"> </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="n">actual</span><span class="w"> </span><span class="n">expected</span><span class="p">))</span><span class="w"> </span><span class="n">error</span><span class="p">)))</span></code></pre></figure>

<p>In the following tests, the random function is again replaced with a deterministic function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Create unit vector with random direction"</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-gradient</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vec</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)))</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">))</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mf">1.5</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">random-gradient</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vec</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)))</span></code></pre></figure>

<p>The random gradient function is then used to generate a field of random gradients.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">random-gradients</span><span class="w">
 </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">divisions</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]}]</span><span class="w">
 </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">divisions</span><span class="p">)</span><span class="w"> </span><span class="n">random-gradient</span><span class="p">)))</span></code></pre></figure>

<p>The function is verified to correctly generate 2D and 3D random gradient fields.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Random gradients"</span><span class="w">
       </span><span class="p">(</span><span class="nf">with-redefs</span><span class="w"> </span><span class="p">[</span><span class="nb">rand</span><span class="w"> </span><span class="p">(</span><span class="nb">constantly</span><span class="w"> </span><span class="mf">1.5</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}))</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
         </span><span class="p">((</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">})</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vec</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">}))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w">
         </span><span class="p">((</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:divisions</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">})</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="p">(</span><span class="nf">sqrt</span><span class="w"> </span><span class="mi">3</span><span class="p">)))))</span></code></pre></figure>

<p>The gradient field can be plotted with Plotly as a scatter plot of disconnected lines.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
                                </span><span class="p">[(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">)])</span><span class="w">
      </span><span class="n">points</span><span class="w">    </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
                                </span><span class="p">[(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">8</span><span class="p">)])</span><span class="w">
      </span><span class="n">scatter</span><span class="w">   </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">mapcat</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">point</span><span class="w"> </span><span class="n">gradient</span><span class="p">]</span><span class="w">
                                            </span><span class="p">[(</span><span class="nf">point</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
                                             </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nf">point</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">(</span><span class="nf">gradient</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span><span class="w">
                                             </span><span class="n">nil</span><span class="p">])</span><span class="w">
                                        </span><span class="n">points</span><span class="w"> </span><span class="n">gradients</span><span class="p">)</span><span class="w">
                             </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">mapcat</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">point</span><span class="w"> </span><span class="n">gradient</span><span class="p">]</span><span class="w">
                                            </span><span class="p">[(</span><span class="nf">point</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
                                             </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nf">point</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="p">(</span><span class="nf">gradient</span><span class="w"> </span><span class="mi">1</span><span class="p">)))</span><span class="w">
                                             </span><span class="n">nil</span><span class="p">])</span><span class="w">
                                        </span><span class="n">points</span><span class="w"> </span><span class="n">gradients</span><span class="p">)})]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Random gradients"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="s">"lines"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">})))</span></code></pre></figure>

<p><img src="/pics/randomgradients.png" alt="Random gradients" /></p>

<h3 id="corner-vectors">Corner vectors</h3>

<p>The next step is to determine the vectors to the corners of the cell for a given point.
First we define a function to determine the fractional part of a number.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">frac</span><span class="w">
  </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/floor</span><span class="w"> </span><span class="n">x</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Fractional part of floating point number"</span><span class="w">
       </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.25</span><span class="w">
       </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="mf">1.75</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.75</span><span class="w">
       </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="mf">-0.25</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span></code></pre></figure>

<p>This function can be used to determine the relative position of a point in a cell.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">cell-pos</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">cellsize</span><span class="p">]}</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">frac</span><span class="w"> </span><span class="p">(</span><span class="nf">div</span><span class="w"> </span><span class="n">point</span><span class="w"> </span><span class="n">cellsize</span><span class="p">))))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Relative position of point in a cell"</span><span class="w">
       </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">5</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.25</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span></code></pre></figure>

<p>A 2 × 2 tensor of corner vectors can be computed by subtracting the corner coordinates from the point coordinates.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">corner-vectors</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">cell-pos</span><span class="w"> </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
      </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="n">cell-pos</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">args</span><span class="p">)))))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Compute relative vectors from cell corners to point in cell"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">corners2</span><span class="w"> </span><span class="p">(</span><span class="nf">corner-vectors</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w">
             </span><span class="n">corners3</span><span class="w"> </span><span class="p">(</span><span class="nf">corner-vectors</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">5</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">-0.25</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">-0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mf">-0.25</span><span class="w"> </span><span class="mf">-0.5</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">corners3</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mf">0.75</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)))</span></code></pre></figure>

<h3 id="extract-gradients-of-cell-corners">Extract gradients of cell corners</h3>

<p>The function below retrieves the gradient values at a cell’s corners, utilizing <code class="language-plaintext highlighter-rouge">wrap-get</code> for modular access.
The result is a 2 × 2 tensor of gradient vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">corner-gradients</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">division</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">division-index</span><span class="w"> </span><span class="n">params</span><span class="p">)</span><span class="w"> </span><span class="n">point</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
      </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">coords</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">wrap-get</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">division</span><span class="p">)</span><span class="w"> </span><span class="n">coords</span><span class="p">))))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Get 2x2 tensor of gradients from a larger tensor using wrap around"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
             </span><span class="n">gradients3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">z</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">)))</span><span class="w"> </span><span class="p">]</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">2</span><span class="p">}</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">23</span><span class="w"> </span><span class="mi">15</span><span class="p">))</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="p">((</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="no">:dimensions</span><span class="w"> </span><span class="mi">3</span><span class="p">}</span><span class="w"> </span><span class="n">gradients3</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">9</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">3</span><span class="p">))</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span></code></pre></figure>

<h3 id="influence-values">Influence values</h3>

<p>The influence value is the function value of the function with the selected random gradient at a corner.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">influence-values</span><span class="w">
  </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="n">vectors</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
    </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">gradients</span><span class="p">))</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">dot</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vectors</span><span class="w"> </span><span class="n">args</span><span class="p">)))</span><span class="w">
    </span><span class="no">:double</span><span class="p">))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Compute influence values from corner vectors and gradients"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mi">10</span><span class="p">)))</span><span class="w">
             </span><span class="n">vectors2</span><span class="w">   </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
             </span><span class="n">influence2</span><span class="w"> </span><span class="p">(</span><span class="nf">influence-values</span><span class="w"> </span><span class="n">gradients2</span><span class="w"> </span><span class="n">vectors2</span><span class="p">)</span><span class="w">
             </span><span class="n">gradients3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">z</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">)))</span><span class="w">
             </span><span class="n">vectors3</span><span class="w">   </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">2</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_z</span><span class="w"> </span><span class="n">_y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="w"> </span><span class="mi">100</span><span class="p">)))</span><span class="w">
             </span><span class="n">influence3</span><span class="w"> </span><span class="p">(</span><span class="nf">influence-values</span><span class="w"> </span><span class="n">gradients3</span><span class="w"> </span><span class="n">vectors3</span><span class="p">)]</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">10.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">11.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">influence3</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">111.0</span><span class="p">))</span></code></pre></figure>

<h3 id="interpolating-the-influence-values">Interpolating the influence values</h3>

<p>For interpolation the following “ease curve” is used.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">ease-curve</span><span class="w">
  </span><span class="p">[</span><span class="n">t</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">6.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">15.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">t</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="mf">10.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="n">t</span><span class="w"> </span><span class="n">t</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Monotonously increasing function with zero derivative at zero and one"</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.25</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.103516</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.5</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.896484</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span></code></pre></figure>

<p>The ease curve monotonously increases in the interval from zero to one.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:t</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.025</span><span class="w"> </span><span class="mf">0.025</span><span class="p">)</span><span class="w">
                 </span><span class="no">:ease</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">ease-curve</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.025</span><span class="w"> </span><span class="mf">0.025</span><span class="p">))})</span><span class="w">
    </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Ease Curve"</span><span class="p">})</span><span class="w">
    </span><span class="p">(</span><span class="nf">plotly/layer-line</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:t</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:ease</span><span class="p">}))</span></code></pre></figure>

<p><img src="/pics/easecurve.png" alt="Ease curve" /></p>

<p>The interpolation weights are recursively calculated from the ease curve and the coordinate distances of the point to upper and lower cell boundary.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">interpolation-weights</span><span class="w">
  </span><span class="p">([</span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">(</span><span class="nf">cell-pos</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)))</span><span class="w">
  </span><span class="p">([</span><span class="n">pos</span><span class="p">]</span><span class="w">
   </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">pos</span><span class="p">)</span><span class="w">
     </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">w1</span><span class="w">   </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="n">pos</span><span class="p">))</span><span class="w">
           </span><span class="n">w2</span><span class="w">   </span><span class="p">(</span><span class="nb">last</span><span class="w"> </span><span class="n">pos</span><span class="p">)</span><span class="w">
           </span><span class="n">elem</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">(</span><span class="nb">butlast</span><span class="w"> </span><span class="n">pos</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">tensor/-&gt;tensor</span><span class="w"> </span><span class="p">[(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="n">w1</span><span class="p">)</span><span class="w"> </span><span class="n">elem</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nf">ease-curve</span><span class="w"> </span><span class="n">w2</span><span class="p">)</span><span class="w"> </span><span class="n">elem</span><span class="p">)]))</span><span class="w">
     </span><span class="mf">1.0</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Interpolation weights"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">weights2</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec2</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">7</span><span class="p">))</span><span class="w">
             </span><span class="n">weights3</span><span class="w"> </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="p">{</span><span class="no">:cellsize</span><span class="w"> </span><span class="mi">8</span><span class="p">}</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">3</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.014391</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.001662</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.882094</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.101854</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">weights3</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="mf">0.010430</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">)))</span></code></pre></figure>

<h3 id="sampling-perlin-noise">Sampling Perlin noise</h3>

<p>A Perlin noise sample is computed by</p>
<ul>
  <li>Getting the random gradients for the cell corners.</li>
  <li>Getting the corner vectors for the cell corners.</li>
  <li>Computing the influence values which have the desired gradients.</li>
  <li>Determining the interpolation weights.</li>
  <li>Computing the weighted sum of the influence values.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">perlin-sample</span><span class="w">
  </span><span class="p">[</span><span class="n">params</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">point</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">corner-gradients</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">point</span><span class="p">)</span><span class="w">
        </span><span class="n">vectors</span><span class="w">   </span><span class="p">(</span><span class="nf">corner-vectors</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)</span><span class="w">
        </span><span class="n">influence</span><span class="w"> </span><span class="p">(</span><span class="nf">influence-values</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">vectors</span><span class="p">)</span><span class="w">
        </span><span class="n">weights</span><span class="w">   </span><span class="p">(</span><span class="nf">interpolation-weights</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">point</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">dfn/reduce-+</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="n">weights</span><span class="w"> </span><span class="n">influence</span><span class="p">))))</span></code></pre></figure>

<p>Now one can sample the Perlin noise by performing above computation for the center of each pixel.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">perlin-noise</span><span class="w">
  </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">size</span><span class="w"> </span><span class="n">dimensions</span><span class="p">]</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">params</span><span class="p">}]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">gradients</span><span class="w"> </span><span class="p">(</span><span class="nf">random-gradients</span><span class="w"> </span><span class="n">params</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w">
        </span><span class="p">(</span><span class="nb">repeat</span><span class="w"> </span><span class="n">dimensions</span><span class="w"> </span><span class="n">size</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
            </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">center</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec-n</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">reverse</span><span class="w"> </span><span class="n">args</span><span class="p">)))]</span><span class="w">
              </span><span class="p">(</span><span class="nf">perlin-sample</span><span class="w"> </span><span class="n">params</span><span class="w"> </span><span class="n">gradients</span><span class="w"> </span><span class="n">center</span><span class="p">)))</span><span class="w">
        </span><span class="no">:double</span><span class="p">))))</span></code></pre></figure>

<p>Here a 256 × 256 Perlin noise tensor is created.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">perlin</span><span class="w"> </span><span class="p">(</span><span class="nf">perlin-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">256</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="mi">2</span><span class="p">)))</span></code></pre></figure>

<p>The values are normalised to be between 0 and 255.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">perlin-norm</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">255</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">perlin</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">perlin</span><span class="p">)))</span><span class="w">
         </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="n">perlin</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">perlin</span><span class="p">))))</span></code></pre></figure>

<p>Finally one can display the noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="n">perlin-norm</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/perlin256.png" alt="Perlin noise" /></p>

<h2 id="mixing-noise-values">Mixing noise values</h2>

<h3 id="combination-of-worley-and-perlin-noise">Combination of Worley and Perlin noise</h3>

<p>You can blend Worley and Perlin noise by performing a linear combination of both.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">perlin-worley-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/+</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.3</span><span class="w"> </span><span class="n">perlin-norm</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.7</span><span class="w"> </span><span class="n">worley-norm</span><span class="p">)))</span></code></pre></figure>

<p>Here for example is the average of Perlin and Worley noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/+</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">perlin-norm</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">worley-norm</span><span class="p">)))</span></code></pre></figure>

<p><img src="/pics/perlinworley256.png" alt="Worley and Perlin noise" /></p>

<h3 id="interpolation">Interpolation</h3>

<p>One can linearly interpolate tensor values by recursing over the dimensions as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">interpolate</span><span class="w">
  </span><span class="p">[</span><span class="n">tensor</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w">  </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="n">args</span><span class="p">)</span><span class="w">
          </span><span class="n">xc</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
          </span><span class="n">xf</span><span class="w"> </span><span class="p">(</span><span class="nf">frac</span><span class="w"> </span><span class="n">xc</span><span class="p">)</span><span class="w">
          </span><span class="n">x0</span><span class="w"> </span><span class="p">(</span><span class="nb">int</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/floor</span><span class="w"> </span><span class="n">xc</span><span class="p">))]</span><span class="w">
      </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="n">xf</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">tensor</span><span class="w">      </span><span class="n">x0</span><span class="w"> </span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">rest</span><span class="w"> </span><span class="n">args</span><span class="p">)))</span><span class="w">
         </span><span class="p">(</span><span class="nb">*</span><span class="w">        </span><span class="n">xf</span><span class="w">  </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="p">(</span><span class="nf">wrap-get</span><span class="w"> </span><span class="n">tensor</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">x0</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">rest</span><span class="w"> </span><span class="n">args</span><span class="p">)))))</span><span class="w">
    </span><span class="n">tensor</span><span class="p">))</span></code></pre></figure>

<p>Here x-, y-,  and z-ramps are used to test that interpolation works.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Interpolate values of tensor"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">x2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="n">x</span><span class="p">))</span><span class="w">
             </span><span class="n">y2</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="n">y</span><span class="p">))</span><span class="w">
             </span><span class="n">x3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_z</span><span class="w"> </span><span class="n">_y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="n">x</span><span class="p">))</span><span class="w">
             </span><span class="n">y3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_z</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="n">y</span><span class="p">))</span><span class="w">
             </span><span class="n">z3</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">[</span><span class="mi">4</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">8</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">z</span><span class="w"> </span><span class="n">_y</span><span class="w"> </span><span class="n">_x</span><span class="p">]</span><span class="w"> </span><span class="n">z</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">3.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y2</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">4.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">3.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y2</span><span class="w"> </span><span class="mf">3.0</span><span class="w"> </span><span class="mf">3.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x2</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y2</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">x3</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="w"> </span><span class="mf">5.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">5.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">y3</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="w"> </span><span class="mf">3.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">3.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">interpolate</span><span class="w"> </span><span class="n">z3</span><span class="w"> </span><span class="mf">2.5</span><span class="w"> </span><span class="mf">3.5</span><span class="w"> </span><span class="mf">5.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">2.0</span><span class="p">))</span></code></pre></figure>

<h3 id="octaves-of-noise">Octaves of noise</h3>

<p>Fractal Brownian Motion is implemented by computing a weighted sum of the same base noise function using different frequencies.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">fractal-brownian-motion</span><span class="w">
  </span><span class="p">[</span><span class="n">base</span><span class="w"> </span><span class="n">octaves</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">scales</span><span class="w"> </span><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">octaves</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">iterate</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
            </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">amplitude</span><span class="w"> </span><span class="n">scale</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">amplitude</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">base</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="n">args</span><span class="p">))))</span><span class="w">
                 </span><span class="n">octaves</span><span class="w"> </span><span class="n">scales</span><span class="p">))))</span></code></pre></figure>

<p>Here the Fractal Brownian Motion is tested using an alternating 1D function and later a 2D checkboard function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Fractal Brownian motion"</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">base1</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">&gt;=</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
             </span><span class="n">base2</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="nb">=</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/round</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="mf">2.0</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">Math/round</span><span class="w"> </span><span class="p">(</span><span class="nf">mod</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)))</span><span class="w">
                               </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))]</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.5</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.5</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base2</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
         </span><span class="p">(</span><span class="nf">fractal-brownian-motion</span><span class="w"> </span><span class="n">base1</span><span class="w"> </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span></code></pre></figure>

<h3 id="remapping-and-clamping">Remapping and clamping</h3>

<p>The remap function is used to map a range of values of an input tensor to a different range.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">remap</span><span class="w">
  </span><span class="p">[</span><span class="n">value</span><span class="w"> </span><span class="n">low1</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">low2</span><span class="w"> </span><span class="n">high2</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/+</span><span class="w"> </span><span class="n">low2</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">low1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">high2</span><span class="w"> </span><span class="n">low2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">low1</span><span class="p">)))))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Remap values of tensor"</span><span class="w">
       </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">((</span><span class="nf">remap</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/-&gt;tensor</span><span class="w"> </span><span class="p">[</span><span class="n">?value</span><span class="p">])</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
             </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?expected</span><span class="p">)</span><span class="w">
       </span><span class="n">?value</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="w"> </span><span class="n">?expected</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">1</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">3</span><span class="w">
       </span><span class="mi">2</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">1</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">4</span><span class="w">      </span><span class="mi">2</span><span class="p">)</span></code></pre></figure>

<p>The clamp function is used to element-wise clamp values to a range.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">clamp</span><span class="w">
  </span><span class="p">[</span><span class="n">value</span><span class="w"> </span><span class="n">low</span><span class="w"> </span><span class="n">high</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">dfn/max</span><span class="w"> </span><span class="n">low</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/min</span><span class="w"> </span><span class="n">value</span><span class="w"> </span><span class="n">high</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Clamp values of tensor"</span><span class="w">
       </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">((</span><span class="nf">clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">tensor/-&gt;tensor</span><span class="w"> </span><span class="p">[</span><span class="n">?value</span><span class="p">])</span><span class="w"> </span><span class="n">?low</span><span class="w"> </span><span class="n">?high</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?expected</span><span class="p">)</span><span class="w">
       </span><span class="n">?value</span><span class="w"> </span><span class="n">?low</span><span class="w"> </span><span class="n">?high</span><span class="w"> </span><span class="n">?expected</span><span class="w">
       </span><span class="mi">2</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">3</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">
       </span><span class="mi">4</span><span class="w">      </span><span class="mi">2</span><span class="w">    </span><span class="mi">3</span><span class="w">      </span><span class="mi">3</span><span class="p">)</span></code></pre></figure>

<h3 id="generating-octaves-of-noise">Generating octaves of noise</h3>

<p>The octaves function is used to create a series of decreasing weights and normalize them so that they add up to 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">octaves</span><span class="w">
  </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="n">decay</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">series</span><span class="w"> </span><span class="p">(</span><span class="nb">take</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="p">(</span><span class="nb">iterate</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">decay</span><span class="p">)</span><span class="w"> </span><span class="mf">1.0</span><span class="p">))</span><span class="w">
        </span><span class="n">sum</span><span class="w">    </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="n">series</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">sum</span><span class="p">)</span><span class="w"> </span><span class="n">series</span><span class="p">)))</span></code></pre></figure>

<p>Here is an example of noise weights decreasing by 50% at each octave.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w">
</span><span class="c1">; [0.5333333333333333</span><span class="w">
</span><span class="c1">;  0.26666666666666666</span><span class="w">
</span><span class="c1">;  0.13333333333333333</span><span class="w">
</span><span class="c1">;  0.06666666666666667]</span></code></pre></figure>

<p>Now a noise array can be generated using octaves of noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">noise-octaves</span><span class="w">
  </span><span class="p">[</span><span class="n">tensor</span><span class="w"> </span><span class="n">octaves</span><span class="w"> </span><span class="n">low</span><span class="w"> </span><span class="n">high</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">tensor/clone</span><span class="w">
    </span><span class="p">(</span><span class="nf">clamp</span><span class="w">
      </span><span class="p">(</span><span class="nf">remap</span><span class="w">
        </span><span class="p">(</span><span class="nf">tensor/compute-tensor</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/shape</span><span class="w"> </span><span class="n">tensor</span><span class="p">)</span><span class="w">
                               </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]</span><span class="w">
                                   </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">fractal-brownian-motion</span><span class="w">
                                     </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="n">tensor</span><span class="p">)</span><span class="w">
                                     </span><span class="n">octaves</span><span class="w">
                                     </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="mf">0.5</span><span class="p">)</span><span class="w"> </span><span class="n">args</span><span class="p">)))</span><span class="w">
                               </span><span class="no">:double</span><span class="p">)</span><span class="w">
        </span><span class="n">low</span><span class="w"> </span><span class="n">high</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
      </span><span class="mi">0</span><span class="w"> </span><span class="mi">255</span><span class="p">)))</span></code></pre></figure>

<h3 id="2d-examples">2D examples</h3>

<p>Here is an example of 4 octaves of Worley noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">worley-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.6</span><span class="p">)</span><span class="w"> </span><span class="mi">120</span><span class="w"> </span><span class="mi">230</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/worleyoctaves.png" alt="Octaves of Worley noise" /></p>

<p>Here is an example of 4 octaves of Perlin noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">perlin-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.6</span><span class="p">)</span><span class="w"> </span><span class="mi">120</span><span class="w"> </span><span class="mi">230</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/perlinoctaves.png" alt="Octaves of Perlin noise" /></p>

<p>Here is an example of 4 octaves of mixed Perlin and Worley noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">bufimg/tensor-&gt;image</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">perlin-worley-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.6</span><span class="p">)</span><span class="w"> </span><span class="mi">120</span><span class="w"> </span><span class="mi">230</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/perlinworleyoctaves.png" alt="Octaves of mixed Perlin and Worley noise" /></p>

<h2 id="opengl-rendering">OpenGL rendering</h2>

<h3 id="opengl-initialization">OpenGL initialization</h3>

<p>In order to render the clouds we create a window and an OpenGL context.
Note that we need to create an invisible window to get an OpenGL context, even though we are not going to draw to the window</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwInit</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="mi">640</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwDefaultWindowHints</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">GLFW/glfwWindowHint</span><span class="w"> </span><span class="n">GLFW/GLFW_VISIBLE</span><span class="w"> </span><span class="n">GLFW/GLFW_FALSE</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window</span><span class="w"> </span><span class="p">(</span><span class="nf">GLFW/glfwCreateWindow</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="s">"Invisible Window"</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwMakeContextCurrent</span><span class="w"> </span><span class="n">window</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">GL/createCapabilities</span><span class="p">)</span></code></pre></figure>

<h3 id="compiling-and-linking-shader-programs">Compiling and linking shader programs</h3>

<p>The following method is used to compile a shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-shader</span><span class="w"> </span><span class="p">[</span><span class="n">source</span><span class="w"> </span><span class="n">shader-type</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateShader</span><span class="w"> </span><span class="n">shader-type</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glShaderSource</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">source</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glCompileShader</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderi</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">GL20/GL_COMPILE_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderInfoLog</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">shader</span><span class="p">))</span></code></pre></figure>

<p>The different shaders are then linked to become a program using the following method.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-program</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateProgram</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glAttachShader</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glDeleteShader</span><span class="w"> </span><span class="n">shader</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glLinkProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgrami</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">GL20/GL_LINK_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgramInfoLog</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">program</span><span class="p">))</span></code></pre></figure>

<p>This method is used to perform both compilation and linking of vertex shaders and fragment shaders.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-program-with-shaders</span><span class="w">
  </span><span class="p">[</span><span class="n">vertex-sources</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-shaders</span><span class="w">   </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">GL20/GL_VERTEX_SHADER</span><span class="p">)</span><span class="w"> </span><span class="n">vertex-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">fragment-shaders</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">GL20/GL_FRAGMENT_SHADER</span><span class="p">)</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">program</span><span class="w">          </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">make-program</span><span class="w"> </span><span class="p">(</span><span class="nb">concat</span><span class="w"> </span><span class="n">vertex-shaders</span><span class="w"> </span><span class="n">fragment-shaders</span><span class="p">))]</span><span class="w">
    </span><span class="n">program</span><span class="p">))</span></code></pre></figure>

<p>In order to pass data to LWJGL methods, we need to be able to convert arrays to Java buffer objects.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">def-make-buffer</span><span class="w"> </span><span class="p">[</span><span class="n">method</span><span class="w"> </span><span class="n">create-buffer</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="o">~</span><span class="n">method</span><span class="w"> </span><span class="p">[</span><span class="n">data</span><span class="o">#</span><span class="p">]</span><span class="w">
     </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="o">~</span><span class="n">create-buffer</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">.put</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">.flip</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="n">buffer</span><span class="o">#</span><span class="p">)))</span></code></pre></figure>

<h3 id="setup-of-vertex-data">Setup of vertex data</h3>

<p>Above macro is used to define methods for creating float, int, and byte buffer objects.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-float-buffer</span><span class="w"> </span><span class="n">BufferUtils/createFloatBuffer</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-int-buffer</span><span class="w"> </span><span class="n">BufferUtils/createIntBuffer</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-byte-buffer</span><span class="w"> </span><span class="n">BufferUtils/createByteBuffer</span><span class="p">)</span></code></pre></figure>

<p>We implement a method to create a vertex array object (VAO) with a vertex buffer object (VBO) and an index buffer object (IBO).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-vao</span><span class="w"> </span><span class="p">[</span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="p">(</span><span class="nf">GL30/glGenVertexArrays</span><span class="p">)</span><span class="w">
        </span><span class="n">vbo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)</span><span class="w">
        </span><span class="n">ibo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="n">vertices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-int-buffer</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="no">:vao</span><span class="w"> </span><span class="n">vao</span><span class="w"> </span><span class="no">:vbo</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="no">:ibo</span><span class="w"> </span><span class="n">ibo</span><span class="p">}))</span></code></pre></figure>

<p>We also define the corresponding destructor for the vertex data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">teardown-vao</span><span class="w"> </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="n">ibo</span><span class="p">]}]</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vao</span><span class="p">))</span></code></pre></figure>

<h3 id="offscreen-rendering-to-a-texture">Offscreen rendering to a texture</h3>

<p>The following method is used to create an empty 2D RGBA floating point texture</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-texture-2d</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">texture</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL42/glTexStorage2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="n">GL30/GL_RGBA32F</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
    </span><span class="n">texture</span><span class="p">))</span></code></pre></figure>

<p>We define a method to convert a Java buffer object to a floating point array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">float-buffer-&gt;array</span><span class="w">
  </span><span class="s">"Convert float buffer to float array"</span><span class="w">
  </span><span class="p">[</span><span class="n">buffer</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nf">.limit</span><span class="w"> </span><span class="n">buffer</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">.get</span><span class="w"> </span><span class="n">buffer</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">.flip</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="n">result</span><span class="p">))</span></code></pre></figure>

<p>The following method copies texture data into a Java buffer and then converts it to a floating point array.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">read-texture-2d</span><span class="w">
  </span><span class="p">[</span><span class="n">texture</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">BufferUtils/createFloatBuffer</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="mi">4</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glGetTexImage</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL12/GL_RGBA</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">float-buffer-&gt;array</span><span class="w"> </span><span class="n">buffer</span><span class="p">)))</span></code></pre></figure>

<p>This method sets up rendering using a specified texture as a framebuffer and then executes the body.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">framebuffer-render</span><span class="w">
  </span><span class="p">[</span><span class="n">texture</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">body</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">fbo</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="nf">GL30/glGenFramebuffers</span><span class="p">)]</span><span class="w">
     </span><span class="p">(</span><span class="nf">try</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL30/glBindFramebuffer</span><span class="w"> </span><span class="n">GL30/GL_FRAMEBUFFER</span><span class="w"> </span><span class="n">fbo</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="o">~</span><span class="n">texture</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL32/glFramebufferTexture</span><span class="w"> </span><span class="n">GL30/GL_FRAMEBUFFER</span><span class="w"> </span><span class="n">GL30/GL_COLOR_ATTACHMENT0</span><span class="w">
                                  </span><span class="o">~</span><span class="n">texture</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL20/glDrawBuffers</span><span class="w"> </span><span class="p">(</span><span class="nf">make-int-buffer</span><span class="w">
                             </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="n">GL30/GL_COLOR_ATTACHMENT0</span><span class="p">])))</span><span class="w">
       </span><span class="p">(</span><span class="nf">GL11/glViewport</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="p">)</span><span class="w">
       </span><span class="o">~@</span><span class="n">body</span><span class="w">
       </span><span class="p">(</span><span class="nf">finally</span><span class="w">
         </span><span class="p">(</span><span class="nf">GL30/glBindFramebuffer</span><span class="w"> </span><span class="n">GL30/GL_FRAMEBUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
         </span><span class="p">(</span><span class="nf">GL30/glDeleteFramebuffers</span><span class="w"> </span><span class="n">fbo</span><span class="o">#</span><span class="p">)))))</span></code></pre></figure>

<p>We also create a method to set up the layout of the vertex buffer.
Our vertex data is only going to contain 3D coordinates of points.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-point-attribute</span><span class="w">
  </span><span class="p">[</span><span class="n">program</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">point-attribute</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"point"</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="n">point-attribute</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w">
                                </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="n">point-attribute</span><span class="p">)))</span></code></pre></figure>

<p>We are going to use a simple background quad to perform volumetric rendering.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-quad-vao</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vertices</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="n">,</span><span class="w">
                               </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="n">,</span><span class="w">
                                </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="n">,</span><span class="w">
                               </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">])</span><span class="w">
        </span><span class="n">indices</span><span class="w">  </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="p">])]</span><span class="w">
    </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">)))</span></code></pre></figure>

<p>We now have all definitions ready to implement rendering of an image.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">render-array</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">body</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">texture</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="nf">make-texture-2d</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="p">)]</span><span class="w">
     </span><span class="p">(</span><span class="nf">try</span><span class="w">
       </span><span class="p">(</span><span class="nf">framebuffer-render</span><span class="w"> </span><span class="n">texture</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="w"> </span><span class="o">~@</span><span class="n">body</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">read-texture-2d</span><span class="w"> </span><span class="n">texture</span><span class="o">#</span><span class="w"> </span><span class="o">~</span><span class="n">width</span><span class="w"> </span><span class="o">~</span><span class="n">height</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">finally</span><span class="w">
         </span><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">texture</span><span class="o">#</span><span class="p">)))))</span></code></pre></figure>

<p>The following method creates a program and the quad VAO and sets up the memory layout.
The program and VAO are then used to render a single pixel.
Using this method we can write unit tests for OpenGL shaders!</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">render-pixel</span><span class="w">
  </span><span class="p">[</span><span class="n">vertex-sources</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program-with-shaders</span><span class="w"> </span><span class="n">vertex-sources</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">vao</span><span class="w">     </span><span class="p">(</span><span class="nf">setup-quad-vao</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">setup-point-attribute</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">try</span><span class="w">
      </span><span class="p">(</span><span class="nf">render-array</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">1</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">finally</span><span class="w">
        </span><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)))))</span></code></pre></figure>

<p>We are going to use a simple vertex shader to simply pass through the points from the vertex buffer without any transformations.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-passthrough</span><span class="w">
</span><span class="s">"#version 130
in vec3 point;
void main()
{
  gl_Position = vec4(point, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>The following fragment shader is used to test rendering white pixels.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-test</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
void main()
{
  fragColor = vec4(1, 1, 1, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We can now render a single white RGBA pixel using the graphics card.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="n">fragment-test</span><span class="p">])</span><span class="w">
</span><span class="c1">; [1.0, 1.0, 1.0, 1.0]</span></code></pre></figure>

<h2 id="volumetric-clouds">Volumetric Clouds</h2>

<h3 id="mocks-and-probing-shaders">Mocks and probing shaders</h3>

<p>The following fragment shader creates a 3D checkboard pattern serving as a mock function below.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-mock</span><span class="w">
</span><span class="s">"#version 130
float noise(vec3 idx)
{
  ivec3 v = ivec3(floor(idx.x), floor(idx.y), floor(idx.z)) % 2;
  return ((v.x == 1) == (v.y == 1)) == (v.z == 1) ? 1.0 : 0.0;
}"</span><span class="p">)</span></code></pre></figure>

<p>We can test this mock function using the following probing shader.
Note that we are using the <code class="language-plaintext highlighter-rouge">template</code> macro of the <code class="language-plaintext highlighter-rouge">comb</code> Clojure library to generate the probing shader code from a template.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float noise(vec3 idx);
void main()
{
  fragColor = vec4(noise(vec3(&lt;%= x %&gt;, &lt;%= y %&gt;, &lt;%= z %&gt;)));
}"</span><span class="p">))</span></code></pre></figure>

<p>Here multiple tests are run to test that the mock implements a checkboard pattern correctly.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test noise mock"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                  </span><span class="p">[</span><span class="n">noise-mock</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-probe</span><span class="w"> </span><span class="n">?x</span><span class="w"> </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="p">)])</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?x</span><span class="w"> </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">1.0</span><span class="p">)</span></code></pre></figure>

<h3 id="octaves-of-noise-1">Octaves of noise</h3>

<p>We now implement a shader for 3D Fractal Brownian motion.
Note that we can use the template macro to generate code for an arbitrary number of octaves.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-octaves</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">octaves</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float noise(vec3 idx);
float octaves(vec3 idx)
{
  float result = 0.0;
&lt;% (doseq [multiplier octaves] %&gt;
  result += &lt;%= multiplier %&gt; * noise(idx);
  idx *= 2.0;
&lt;%= ) %&gt;
  return result;
}"</span><span class="p">))</span></code></pre></figure>

<p>Again we use a probing shader to test the shader function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">octaves-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="n">z</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float octaves(vec3 idx);
void main()
{
  fragColor = vec4(octaves(vec3(&lt;%= x %&gt;, &lt;%= y %&gt;, &lt;%= z %&gt;)));
}"</span><span class="p">))</span></code></pre></figure>

<p>A few unit tests with one or two octaves are sufficient to drive development of the shader function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test octaves of noise"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                    </span><span class="p">[</span><span class="n">noise-mock</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="n">?octaves</span><span class="p">)</span><span class="w">
                                     </span><span class="p">(</span><span class="nf">octaves-probe</span><span class="w"> </span><span class="n">?x</span><span class="w"> </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?x</span><span class="w">  </span><span class="n">?y</span><span class="w"> </span><span class="n">?z</span><span class="w"> </span><span class="n">?octaves</span><span class="w">  </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w">     </span><span class="mf">0.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="p">]</span><span class="w">     </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="p">]</span><span class="w">     </span><span class="mf">0.5</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w"> </span><span class="mf">1.0</span><span class="w">
         </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span></code></pre></figure>

<h3 id="shader-for-intersecting-a-ray-with-a-box">Shader for intersecting a ray with a box</h3>

<p>The following shader implements intersection of a ray with an axis-aligned box.
The shader function returns the distance of the near and far intersection with the box.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ray-box</span><span class="w">
</span><span class="s">"#version 130
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction)
{
  vec3 inv_dir = 1.0 / direction;
  vec3 smin = (box_min - origin) * inv_dir;
  vec3 smax = (box_max - origin) * inv_dir;
  vec3 s1 = min(smin, smax);
  vec3 s2 = max(smin, smax);
  float s_near = max(max(s1.x, s1.y), s1.z);
  float s_far = min(min(s2.x, s2.y), s2.z);
  if (isinf(s_near) || isinf(s_far))
    return vec2(0.0, 0.0);
  else
    return vec2(max(s_near, 0.0), max(0.0, s_far));
}"</span><span class="p">)</span></code></pre></figure>

<p>The probing shader returns the near and far distance in the red and green channel of the fragment color.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ray-box-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">ox</span><span class="w"> </span><span class="n">oy</span><span class="w"> </span><span class="n">oz</span><span class="w"> </span><span class="n">dx</span><span class="w"> </span><span class="n">dy</span><span class="w"> </span><span class="n">dz</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
void main()
{
  vec3 box_min = vec3(-1, -1, -1);
  vec3 box_max = vec3(1, 1, 1);
  vec3 origin = vec3(&lt;%= ox %&gt;, &lt;%= oy %&gt;, &lt;%= oz %&gt;);
  vec3 direction = vec3(&lt;%= dx %&gt;, &lt;%= dy %&gt;, &lt;%= dz %&gt;);
  fragColor = vec4(ray_box(box_min, box_max, origin, direction), 0, 0);
}"</span><span class="p">))</span></code></pre></figure>

<p>The <code class="language-plaintext highlighter-rouge">ray-box</code> shader is tested with different ray origins and directions.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test intersection of ray with box"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">((</span><span class="nf">juxt</span><span class="w"> </span><span class="nb">first</span><span class="w"> </span><span class="nb">second</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                              </span><span class="p">[</span><span class="n">ray-box</span><span class="w"> </span><span class="p">(</span><span class="nf">ray-box-probe</span><span class="w"> </span><span class="n">?ox</span><span class="w"> </span><span class="n">?oy</span><span class="w"> </span><span class="n">?oz</span><span class="w"> </span><span class="n">?dx</span><span class="w"> </span><span class="n">?dy</span><span class="w"> </span><span class="n">?dz</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?result</span><span class="p">)</span><span class="w">
         </span><span class="n">?ox</span><span class="w"> </span><span class="n">?oy</span><span class="w"> </span><span class="n">?oz</span><span class="w"> </span><span class="n">?dx</span><span class="w"> </span><span class="n">?dy</span><span class="w"> </span><span class="n">?dz</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">]</span><span class="w">
         </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="w"> </span><span class="mf">1.5</span><span class="p">]</span><span class="w">
         </span><span class="mi">-2</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="w"> </span><span class="mf">1.5</span><span class="p">]</span><span class="w">
          </span><span class="mi">2</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">  </span><span class="p">[</span><span class="mf">1.0</span><span class="w"> </span><span class="mf">3.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">2</span><span class="w">  </span><span class="p">[</span><span class="mf">0.5</span><span class="w"> </span><span class="mf">1.5</span><span class="p">]</span><span class="w">
          </span><span class="mi">2</span><span class="w">   </span><span class="mi">2</span><span class="w">  </span><span class="mi">-2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">]</span><span class="w">
          </span><span class="mi">2</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">  </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">])</span></code></pre></figure>

<h3 id="shader-for-light-transfer-through-clouds">Shader for light transfer through clouds</h3>

<p>We test the light transfer through clouds using constant density fog.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fog</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">v</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
float fog(vec3 idx)
{
  return &lt;%= v %&gt;;
}"</span><span class="p">))</span></code></pre></figure>

<p>Volumetric rendering involves sampling cloud density along a ray and multiplying the transmittance values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cloud-transfer</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">noise</span><span class="w"> </span><span class="n">step</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
#define STEP &lt;%= step %&gt;
float &lt;%= noise %&gt;(vec3 idx);
float in_scatter(vec3 point, vec3 direction);
float shadow(vec3 point);
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval)
{
  vec4 result = vec4(0, 0, 0, 0);
  for (float t = interval.x + 0.5 * STEP; t &lt; interval.y; t += STEP) {
    vec3 point = origin + direction * t;
    float density = &lt;%= noise %&gt;(point);
    float transmittance = exp(-density * STEP);
    vec3 color = vec3(in_scatter(point, direction) * shadow(point));
    result.rgb += color * (1.0 - result.a) * (1.0 - transmittance);
    result.a = 1.0 - (1.0 - result.a) * transmittance;
  };
  return result;
}"</span><span class="p">))</span></code></pre></figure>

<p>For now we also assume isotropic scattering of light in all directions.
This is a placeholder for introducing Mie scattering later.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w">
</span><span class="s">"#version 130
float in_scatter(vec3 point, vec3 direction)
{
  return 1.0;
}"</span><span class="p">)</span></code></pre></figure>

<p>Finally we assume that there is no shadow.
This is a placeholder for introducing cloud shadows later.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">no-shadow</span><span class="w">
</span><span class="s">"#version 130
float shadow(vec3 point)
{
  return 1.0;
}"</span><span class="p">)</span></code></pre></figure>

<p>We can now test the color and opacity of the cloud using the following probing shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cloud-transfer-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
void main()
{
  vec3 origin = vec3(0, 0, 0);
  vec3 direction = vec3(1, 0, 0);
  vec2 interval = vec2(&lt;%= a %&gt;, &lt;%= b %&gt;);
  fragColor = cloud_transfer(origin, direction, interval);
}"</span><span class="p">))</span></code></pre></figure>

<p>We also introduce a Midje checker for requiring a vector to have an approximate value.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">roughly-vector</span><span class="w">
  </span><span class="p">[</span><span class="n">expected</span><span class="w"> </span><span class="n">error</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">actual</span><span class="p">]</span><span class="w">
      </span><span class="p">(</span><span class="nb">and</span><span class="w"> </span><span class="p">(</span><span class="nb">==</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">expected</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">actual</span><span class="p">))</span><span class="w">
           </span><span class="p">(</span><span class="nb">&lt;=</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">a</span><span class="w"> </span><span class="n">b</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="n">a</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="n">b</span><span class="w"> </span><span class="n">a</span><span class="p">)))</span><span class="w"> </span><span class="n">actual</span><span class="w"> </span><span class="n">expected</span><span class="p">))</span><span class="w">
               </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">error</span><span class="w"> </span><span class="n">error</span><span class="p">)))))</span></code></pre></figure>

<p>A few tests are performed to check that there is opacity and that the step size does not affect the result in constant fog.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Test cloud transfer"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">seq</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                  </span><span class="p">[(</span><span class="nf">fog</span><span class="w"> </span><span class="n">?density</span><span class="p">)</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w">
                                   </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"fog"</span><span class="w"> </span><span class="n">?step</span><span class="p">)</span><span class="w">
                                   </span><span class="p">(</span><span class="nf">cloud-transfer-probe</span><span class="w"> </span><span class="n">?a</span><span class="w"> </span><span class="n">?b</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly-vector</span><span class="w"> </span><span class="n">?result</span><span class="w"> </span><span class="mi">1</span><span class="n">e-3</span><span class="p">))</span><span class="w">
         </span><span class="n">?a</span><span class="w"> </span><span class="n">?b</span><span class="w"> </span><span class="n">?step</span><span class="w"> </span><span class="n">?density</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">     </span><span class="mf">0.0</span><span class="w">      </span><span class="p">[</span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mi">1</span><span class="w">     </span><span class="mf">1.0</span><span class="w">      </span><span class="p">[</span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="p">]</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.5</span><span class="w">   </span><span class="mf">1.0</span><span class="w">      </span><span class="p">[</span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="w"> </span><span class="mf">0.632</span><span class="p">]</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">1</span><span class="w">  </span><span class="mf">0.5</span><span class="w">   </span><span class="mf">0.5</span><span class="w">      </span><span class="p">[</span><span class="mf">0.393</span><span class="w"> </span><span class="mf">0.393</span><span class="w"> </span><span class="mf">0.393</span><span class="w"> </span><span class="mf">0.393</span><span class="p">])</span></code></pre></figure>

<h3 id="rendering-of-fog-box">Rendering of fog box</h3>

<p>The following fragment shader is used to render an image of a box filled with fog.</p>

<ul>
  <li>The pixel coordinate and the resolution of the image are used to determine a viewing direction which also gets rotated using the rotation matrix and normalized.</li>
  <li>The origin of the camera is set at a specified distance to the center of the box and rotated as well.</li>
  <li>The ray box function is used to determine the near and far intersection points of the ray with the box.</li>
  <li>The cloud transfer function is used to sample the cloud density along the ray and determine the overall opacity and color of the fog box.</li>
  <li>The background is a mix of blue color and a small blob of white where the viewing direction points to the light source.</li>
  <li>The opacity value of the fog is used to overlay the fog color over the background.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-cloud</span><span class="w">
</span><span class="s">"#version 130
uniform vec2 resolution;
uniform vec3 light;
uniform mat3 rotation;
uniform float focal_length;
uniform float distance;
out vec4 fragColor;
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
vec4 cloud_transfer(vec3 origin, vec3 direction, vec2 interval);
void main()
{
  vec3 direction =
    normalize(rotation * vec3(gl_FragCoord.xy - 0.5 * resolution, focal_length));
  vec3 origin = rotation * vec3(0, 0, -distance);
  vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), origin, direction);
  vec4 transfer = cloud_transfer(origin, direction, interval);
  vec3 background = mix(vec3(0.125, 0.125, 0.25), vec3(1, 1, 1),
                        pow(dot(direction, light), 1000.0));
  fragColor = vec4(background * (1.0 - transfer.a) + transfer.rgb, 1.0);
}"</span><span class="p">)</span></code></pre></figure>

<p>Uniform variables are parameters that remain constant throughout the shader execution, unlike vertex input data.
Here we use the following uniform variables:</p>
<ul>
  <li><strong>resolution</strong>: a 2D vector containing the window pixel width and height</li>
  <li><strong>light:</strong> a 3D unit vector pointing to the light source</li>
  <li><strong>rotation:</strong> a 3x3 rotation matrix to rotate the camera around the origin</li>
  <li><strong>focal_length:</strong> the ratio of camera focal length to pixel size of the virtual camera</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-fog-uniforms</span><span class="w">
  </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">rotation</span><span class="w">     </span><span class="p">(</span><span class="nf">mulm</span><span class="w"> </span><span class="p">(</span><span class="nf">rotation-matrix-3d-y</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">40.0</span><span class="p">))</span><span class="w">
                           </span><span class="p">(</span><span class="nf">rotation-matrix-3d-x</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">-20.0</span><span class="p">)))</span><span class="w">
        </span><span class="n">focal-length</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">width</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">tan</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">30.0</span><span class="p">)))</span><span class="w">
        </span><span class="n">light</span><span class="w">        </span><span class="p">(</span><span class="nf">normalize</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">10</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"resolution"</span><span class="p">)</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform3f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"light"</span><span class="p">)</span><span class="w">
                      </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniformMatrix3fv</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"rotation"</span><span class="p">)</span><span class="w"> </span><span class="n">true</span><span class="w">
                             </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">mat-&gt;float-array</span><span class="w"> </span><span class="n">rotation</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"focal_length"</span><span class="p">)</span><span class="w"> </span><span class="n">focal-length</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)))</span></code></pre></figure>

<p>The following function sets up the shader program, the vertex array object, and the uniform variables.
Then <code class="language-plaintext highlighter-rouge">GL11/glDrawElements</code> draws the background quad used for performing volumetric rendering.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">render-fog</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">fragment-sources</span><span class="w"> </span><span class="p">[</span><span class="n">ray-box</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"fog"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                          </span><span class="p">(</span><span class="nf">fog</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w"> </span><span class="n">fragment-cloud</span><span class="p">]</span><span class="w">
        </span><span class="n">program</span><span class="w">          </span><span class="p">(</span><span class="nf">make-program-with-shaders</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">vao</span><span class="w">              </span><span class="p">(</span><span class="nf">setup-quad-vao</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">setup-point-attribute</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">try</span><span class="w">
      </span><span class="p">(</span><span class="nf">render-array</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w">
                    </span><span class="p">(</span><span class="nf">setup-fog-uniforms</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">finally</span><span class="w">
        </span><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)))))</span></code></pre></figure>

<p>We also need to convert the floating point array to a tensor and then to a <code class="language-plaintext highlighter-rouge">BufferedImage</code>.
The one-dimensional array gets converted to a tensor and then reshaped to a 3D tensor containing width × height RGBA values.
The RGBA data is converted to BGR data and then multiplied with 255 and clamped.
Finally the tensor is converted to a <code class="language-plaintext highlighter-rouge">BufferedImage</code>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">rgba-array-&gt;bufimg</span><span class="w"> </span><span class="p">[</span><span class="n">data</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">data</span><span class="w">
      </span><span class="n">tensor/-&gt;tensor</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/reshape</span><span class="w"> </span><span class="p">[</span><span class="n">height</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="mi">4</span><span class="p">])</span><span class="w">
      </span><span class="p">(</span><span class="nf">tensor/select</span><span class="w"> </span><span class="no">:all</span><span class="w"> </span><span class="no">:all</span><span class="w"> </span><span class="p">[</span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">])</span><span class="w">
      </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="nf">clamp</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">255</span><span class="p">)</span><span class="w">
      </span><span class="n">bufimg/tensor-&gt;image</span><span class="p">))</span></code></pre></figure>

<p>Finally we are ready to render the volumetric fog.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w"> </span><span class="p">(</span><span class="nf">render-fog</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/fog.jpg" alt="volumetric fog" /></p>

<h3 id="rendering-of-3d-noise">Rendering of 3D noise</h3>

<p>This method converts a floating point array to a buffer and initialises a 3D texture with it.
It is also necessary to set the texture parameters for interpolation and wrapping.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">float-array-&gt;texture3d</span><span class="w">
  </span><span class="p">[</span><span class="n">data</span><span class="w"> </span><span class="n">size</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="w">  </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="n">data</span><span class="p">)</span><span class="w">
        </span><span class="n">texture</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">texture</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL12/glTexImage3D</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL30/GL_R32F</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="n">size</span><span class="w"> </span><span class="mi">0</span><span class="w">
                       </span><span class="n">GL11/GL_RED</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_WRAP_R</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
    </span><span class="n">texture</span><span class="p">))</span></code></pre></figure>

<p>Here a mixture of 3D Perlin and Worley noise is created.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise3d</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.3</span><span class="w"> </span><span class="p">(</span><span class="nf">perlin-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">3</span><span class="p">)))</span><span class="w">
                    </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="mf">0.7</span><span class="w"> </span><span class="p">(</span><span class="nf">worley-noise</span><span class="w"> </span><span class="p">(</span><span class="nf">make-noise-params</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mi">3</span><span class="p">)))))</span></code></pre></figure>

<p>The noise is normalised to be between 0 and 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-3d-norm</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="nb">-</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-max</span><span class="w"> </span><span class="n">noise3d</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">noise3d</span><span class="p">)))</span><span class="w">
                          </span><span class="p">(</span><span class="nf">dfn/-</span><span class="w"> </span><span class="n">noise3d</span><span class="w"> </span><span class="p">(</span><span class="nf">dfn/reduce-min</span><span class="w"> </span><span class="n">noise3d</span><span class="p">))))</span></code></pre></figure>

<p>Then the noise data is converted to a 3D texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-texture</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array-&gt;texture3d</span><span class="w"> </span><span class="p">(</span><span class="nf">dtype/-&gt;float-array</span><span class="w"> </span><span class="n">noise-3d-norm</span><span class="p">)</span><span class="w"> </span><span class="mi">32</span><span class="p">))</span></code></pre></figure>

<p>Instead of a constant density fog, we can use the noise as a density function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-shader</span><span class="w">
</span><span class="s">"#version 130
uniform sampler3D noise3d;
float noise(vec3 idx)
{
  return texture(noise3d, idx).r;
}"</span><span class="p">)</span></code></pre></figure>

<p>We also set the uniform sampler to texture slot 0 and bind the noise texture to that slot.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-noise-uniforms</span><span class="w">
  </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">setup-fog-uniforms</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"noise3d"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="n">noise-texture</span><span class="p">))</span></code></pre></figure>

<p>Similar to the fog example above, we define a method to render the noise.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">render-noise</span><span class="w">
  </span><span class="p">[</span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">cloud-shaders</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">fragment-sources</span><span class="w"> </span><span class="p">(</span><span class="nb">concat</span><span class="w"> </span><span class="n">cloud-shaders</span><span class="w"> </span><span class="p">[</span><span class="n">ray-box</span><span class="w"> </span><span class="n">fragment-cloud</span><span class="p">])</span><span class="w">
        </span><span class="n">program</span><span class="w">          </span><span class="p">(</span><span class="nf">make-program-with-shaders</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="n">fragment-sources</span><span class="p">)</span><span class="w">
        </span><span class="n">vao</span><span class="w">              </span><span class="p">(</span><span class="nf">setup-quad-vao</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">try</span><span class="w">
      </span><span class="p">(</span><span class="nf">setup-point-attribute</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
      </span><span class="p">(</span><span class="nf">render-array</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="w">
                    </span><span class="p">(</span><span class="nf">setup-noise-uniforms</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">width</span><span class="w"> </span><span class="n">height</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">finally</span><span class="w">
        </span><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
        </span><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)))))</span></code></pre></figure>

<p>Now we can render the mixture of 3D Perlin and Worley noise using a step size of 0.01.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w">
                </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/noise3d.jpg" alt="3D noise" /></p>

<h3 id="remap-and-clamp-3d-noise">Remap and clamp 3D noise</h3>

<p>We define a method to map a range of input values to a range of output values and clamp the result.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">remap-clamp</span><span class="w">
</span><span class="s">"#version 130
float remap_clamp(float value, float low1, float high1, float low2, float high2)
{
  float t = (value - low1) / (high1 - low1);
  return clamp(low2 + t * (high2 - low2), low2, high2);
}"</span><span class="p">)</span></code></pre></figure>

<p>A probing shader is used to test the remap_clamp function.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">remap-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">value</span><span class="w"> </span><span class="n">low1</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">low2</span><span class="w"> </span><span class="n">high2</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
out vec4 fragColor;
float remap_clamp(float value, float low1, float high1, float low2, float high2);
void main()
{
  fragColor = vec4(remap_clamp(&lt;%= value %&gt;,
                               &lt;%= low1 %&gt;, &lt;%= high1 %&gt;,
                               &lt;%= low2 %&gt;, &lt;%= high2 %&gt;));
}"</span><span class="p">))</span></code></pre></figure>

<p><code class="language-plaintext highlighter-rouge">remap_clamp</code> is tested using a parametrized tests.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Remap and clamp input parameter values"</span><span class="w">
       </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w">
                      </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                      </span><span class="p">[</span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-probe</span><span class="w"> </span><span class="n">?value</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="p">)]))</span><span class="w">
             </span><span class="n">=&gt;</span><span class="w"> </span><span class="n">?expected</span><span class="p">)</span><span class="w">
       </span><span class="n">?value</span><span class="w"> </span><span class="n">?low1</span><span class="w"> </span><span class="n">?high1</span><span class="w"> </span><span class="n">?low2</span><span class="w"> </span><span class="n">?high2</span><span class="w"> </span><span class="n">?expected</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">0.0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">1.0</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mf">2.0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mf">3.0</span><span class="w">
       </span><span class="mi">2</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">0.0</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">2</span><span class="w">     </span><span class="mi">3</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">1</span><span class="w">      </span><span class="mf">1.0</span><span class="w">
       </span><span class="mi">1</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">0</span><span class="w">     </span><span class="mi">4</span><span class="w">      </span><span class="mf">2.0</span><span class="w">
       </span><span class="mi">0</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mf">1.0</span><span class="w">
       </span><span class="mi">3</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mi">1</span><span class="w">     </span><span class="mi">2</span><span class="w">      </span><span class="mf">2.0</span><span class="p">)</span></code></pre></figure>

<p>We use the <code class="language-plaintext highlighter-rouge">remap-noise</code> method to map the 3D noise to the output range.
The base noise function and the remapping parameters are template parameters.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">remap-noise</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">base</span><span class="w"> </span><span class="n">low1</span><span class="w"> </span><span class="n">high1</span><span class="w"> </span><span class="n">high2</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
float &lt;%= base %&gt;(vec3 idx);
float remap_clamp(float value, float low1, float high1, float low2, float high2);
float remap_noise(vec3 idx)
{
  return remap_clamp(&lt;%= base %&gt;(idx), &lt;%= low1 %&gt;, &lt;%= high1 %&gt;, 0.0, &lt;%= high2 %&gt;);
}"</span><span class="p">))</span></code></pre></figure>

<p>We are going to use the following value as the upper value of the cloud density.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">cloud-strength</span><span class="w"> </span><span class="mf">6.5</span><span class="p">)</span></code></pre></figure>

<p>Now we can render the remapped noise values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w">
                </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                </span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"noise"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/remap3d.jpg" alt="Remapped 3D noise" /></p>

<h3 id="octaves-of-3d-noise">Octaves of 3D noise</h3>

<p>Earlier we defined a function for creating octaves of 3D noise.
Here we create octaves of noise before remapping and clamping the values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w"> </span><span class="n">constant-scatter</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                </span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"octaves"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/octaves3d.jpg" alt="Octaves of 3D noise" /></p>

<h3 id="mie-scattering">Mie scattering</h3>

<p>In-scattering of light towards the observer depends of the angle between light source and viewing direction.
Here we are going to use the phase function by Cornette and Shanks which depends on the asymmetry g and mu = cos(theta).</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">mie-scatter</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">g</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 450 core
#define M_PI 3.1415926535897932384626433832795
#define ANISOTROPIC 0.25
#define G &lt;%= g %&gt;
uniform vec3 light;
float mie(float mu)
{
  return 3 * (1 - G * G) * (1 + mu * mu) /
    (8 * M_PI * (2 + G * G) * pow(1 + G * G - 2 * G * mu, 1.5));
}
float in_scatter(vec3 point, vec3 direction)
{
  return mix(1.0, mie(dot(light, direction)), ANISOTROPIC);
}"</span><span class="p">))</span></code></pre></figure>

<p>We define a probing shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">mie-probe</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">mu</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 450 core
out vec4 fragColor;
float mie(float mu);
void main()
{
  float result = mie(&lt;%= mu %&gt;);
  fragColor = vec4(result, 0, 0, 1);
}"</span><span class="p">))</span></code></pre></figure>

<p>The shader is tested using a few values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">tabular</span><span class="w"> </span><span class="s">"Shader function for scattering phase function"</span><span class="w">
         </span><span class="p">(</span><span class="nf">fact</span><span class="w"> </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w">
                                    </span><span class="p">[(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="n">?g</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">mie-probe</span><span class="w"> </span><span class="n">?mu</span><span class="p">)]))</span><span class="w">
               </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="n">?result</span><span class="w"> </span><span class="mi">1</span><span class="n">e-6</span><span class="p">))</span><span class="w">
         </span><span class="n">?g</span><span class="w">  </span><span class="n">?mu</span><span class="w"> </span><span class="n">?result</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">0</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
         </span><span class="mi">0</span><span class="w">   </span><span class="mi">1</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
         </span><span class="mi">0</span><span class="w">  </span><span class="mi">-1</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">16</span><span class="w"> </span><span class="n">PI</span><span class="p">))</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">0</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">2.25</span><span class="w"> </span><span class="p">(</span><span class="nf">pow</span><span class="w"> </span><span class="mf">1.25</span><span class="w"> </span><span class="mf">1.5</span><span class="p">)))</span><span class="w">
         </span><span class="mf">0.5</span><span class="w"> </span><span class="mi">1</span><span class="w">   </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mf">0.75</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">8</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="mf">2.25</span><span class="w"> </span><span class="p">(</span><span class="nf">pow</span><span class="w"> </span><span class="mf">0.25</span><span class="w"> </span><span class="mf">1.5</span><span class="p">))))</span></code></pre></figure>

<p>We can define a function to compute a particular value of the scattering phase function using the GPU.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">scatter-amount</span><span class="w"> </span><span class="p">[</span><span class="n">theta</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">first</span><span class="w"> </span><span class="p">(</span><span class="nf">render-pixel</span><span class="w"> </span><span class="p">[</span><span class="n">vertex-passthrough</span><span class="p">]</span><span class="w"> </span><span class="p">[(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="mf">0.76</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">mie-probe</span><span class="w"> </span><span class="p">(</span><span class="nf">cos</span><span class="w"> </span><span class="n">theta</span><span class="p">))])))</span></code></pre></figure>

<p>We can use this function to plot Mie scattering for different angles.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">tc/dataset</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">theta</span><span class="p">]</span><span class="w">
                               </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">cos</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))</span><span class="w">
                                  </span><span class="p">(</span><span class="nf">scatter-amount</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))))</span><span class="w">
                           </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">361</span><span class="p">))</span><span class="w">
                   </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">theta</span><span class="p">]</span><span class="w">
                               </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nf">sin</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))</span><span class="w">
                                  </span><span class="p">(</span><span class="nf">scatter-amount</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="n">theta</span><span class="p">))))</span><span class="w">
                           </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">361</span><span class="p">))</span><span class="w"> </span><span class="p">})]</span><span class="w">
  </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">scatter</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/base</span><span class="w"> </span><span class="p">{</span><span class="no">:=title</span><span class="w"> </span><span class="s">"Mie scattering"</span><span class="w"> </span><span class="no">:=mode</span><span class="w"> </span><span class="s">"lines"</span><span class="p">})</span><span class="w">
      </span><span class="p">(</span><span class="nf">plotly/layer-point</span><span class="w"> </span><span class="p">{</span><span class="no">:=x</span><span class="w"> </span><span class="no">:x</span><span class="w"> </span><span class="no">:=y</span><span class="w"> </span><span class="no">:y</span><span class="p">})</span><span class="w">
      </span><span class="n">plotly/plot</span><span class="w">
      </span><span class="p">(</span><span class="nf">assoc-in</span><span class="w"> </span><span class="p">[</span><span class="no">:layout</span><span class="w"> </span><span class="no">:yaxis</span><span class="w"> </span><span class="no">:scaleanchor</span><span class="p">]</span><span class="w"> </span><span class="s">"x"</span><span class="p">)))</span></code></pre></figure>

<p><img src="/pics/mie-plot.png" alt="Mie scattering" /></p>

<p>We replace the <code class="language-plaintext highlighter-rouge">in_scatter</code> placeholder from earlier with the Mie scattering and now the clouds look a bit more realistic.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w"> </span><span class="p">(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="mf">0.76</span><span class="p">)</span><span class="w"> </span><span class="n">no-shadow</span><span class="w"> </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w">
                </span><span class="n">remap-clamp</span><span class="w"> </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"octaves"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/mie-clouds.jpg" alt="Clouds with Mie scattering" /></p>

<h3 id="self-shading-of-clouds">Self-shading of clouds</h3>

<p>Finally we can implement the shadow function by also sampling towards the light source to compute the shading value at each point.
Testing the function requires extending the <code class="language-plaintext highlighter-rouge">render-pixel</code> function to accept a function for setting the <code class="language-plaintext highlighter-rouge">light</code> uniform.
We leave this as an exercise for the interested reader 😉.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">shadow</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">noise</span><span class="w"> </span><span class="n">step</span><span class="p">]</span><span class="w">
</span><span class="s">"#version 130
#define STEP &lt;%= step %&gt;
uniform vec3 light;
float &lt;%= noise %&gt;(vec3 idx);
vec2 ray_box(vec3 box_min, vec3 box_max, vec3 origin, vec3 direction);
float shadow(vec3 point)
{
  vec2 interval = ray_box(vec3(-0.5, -0.5, -0.5), vec3(0.5, 0.5, 0.5), point, light);
  float result = 1.0;
  for (float t = interval.x + 0.5 * STEP; t &lt; interval.y; t += STEP) {
    float density = &lt;%= noise %&gt;(point + t * light);
    float transmittance = exp(-density * STEP);
    result *= transmittance;
  };
  return result;
}"</span><span class="p">))</span></code></pre></figure>

<p>The final result is starting to look realistic.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">rgba-array-&gt;bufimg</span><span class="w">
  </span><span class="p">(</span><span class="nf">render-noise</span><span class="w"> </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="w">
                </span><span class="p">(</span><span class="nf">mie-scatter</span><span class="w"> </span><span class="mf">0.76</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">shadow</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.05</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">cloud-transfer</span><span class="w"> </span><span class="s">"remap_noise"</span><span class="w"> </span><span class="mf">0.01</span><span class="p">)</span><span class="w"> </span><span class="n">remap-clamp</span><span class="w">
                </span><span class="p">(</span><span class="nf">remap-noise</span><span class="w"> </span><span class="s">"octaves"</span><span class="w"> </span><span class="mf">0.45</span><span class="w"> </span><span class="mf">0.9</span><span class="w"> </span><span class="n">cloud-strength</span><span class="p">)</span><span class="w">
                </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="p">(</span><span class="nf">octaves</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="mf">0.5</span><span class="p">))</span><span class="w"> </span><span class="n">noise-shader</span><span class="p">)</span><span class="w">
  </span><span class="mi">640</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/self-shading.jpg" alt="Clouds with self-shading" /></p>

<h3 id="tidy-up">Tidy up</h3>

<p>Finally we free the texture, destroy the window, and terminate GLFW.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL12/GL_TEXTURE_3D</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">noise-texture</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwDestroyWindow</span><span class="w"> </span><span class="n">window</span><span class="p">)</span><span class="w">

</span><span class="p">(</span><span class="nf">GLFW/glfwTerminate</span><span class="p">)</span></code></pre></figure>

<h2 id="further-topics">Further topics</h2>

<p>I hope you enjoyed this little tour of volumetric clouds.
Here are some references to get from a cloud prototype to more realistic clouds.</p>

<ul>
  <li><a href="https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/">Vertical density profile</a></li>
  <li><a href="https://advances.realtimerendering.com/s2015/index.html">Powder function</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/03/20/procedural-global-cloud-cover/">Curl noise</a></li>
  <li><a href="https://ebruneton.github.io/precomputed_atmospheric_scattering/">Precomputed atmospheric scattering</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/">Deep opacity maps</a></li>
</ul>]]></content><author><name>Jan Wedekind</name></author><category term="graphics" /><summary type="html"><![CDATA[Procedural generation of volumetric clouds using different types of noise]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/clouds.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/clouds.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Clojure in your browser</title><link href="https://www.wedesoft.de/software/2025/11/12/scittle-clojure/" rel="alternate" type="text/html" title="Clojure in your browser" /><published>2025-11-12T00:00:00+00:00</published><updated>2025-11-12T00:00:00+00:00</updated><id>https://www.wedesoft.de/software/2025/11/12/scittle-clojure</id><content type="html" xml:base="https://www.wedesoft.de/software/2025/11/12/scittle-clojure/"><![CDATA[<p>There is a recent article on Clojure Civitas on <a href="https://clojurecivitas.org/scittle/presentations/browser_native_slides.html">using Scittle for browser native slides</a>.
<a href="https://github.com/babashka/scittle">Scittle</a> is a Clojure interpreter that runs in the browser.
It even defines a script tag that let’s you embed Clojure code in your HTML code.
Here is an example evaluating the content of an HTML textarea:</p>

<h2 id="html-code">HTML code</h2>

<figure class="highlight"><pre><code class="language-html" data-lang="html"><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">"https://cdn.jsdelivr.net/npm/scittle@0.6.22/dist/scittle.js"</span><span class="nt">&gt;&lt;/script&gt;</span>
<span class="nt">&lt;script </span><span class="na">type=</span><span class="s">"application/x-scittle"</span><span class="nt">&gt;</span>
<span class="p">(</span><span class="nx">defn</span> <span class="nx">run</span> <span class="p">[]</span>
  <span class="p">(</span><span class="kd">let</span> <span class="p">[</span><span class="nf">code </span><span class="p">(.</span><span class="o">-</span><span class="nf">value </span><span class="p">(</span><span class="nx">js</span><span class="o">/</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span> <span class="dl">"</span><span class="s2">code</span><span class="dl">"</span><span class="p">))</span>
        <span class="nx">output</span><span class="o">-</span><span class="nf">elem </span><span class="p">(</span><span class="nx">js</span><span class="o">/</span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span> <span class="dl">"</span><span class="s2">output</span><span class="dl">"</span><span class="p">)]</span>
    <span class="p">(</span><span class="k">try</span>
      <span class="p">(</span><span class="kd">let</span> <span class="p">[</span><span class="nf">result </span><span class="p">(</span><span class="nx">js</span><span class="o">/</span><span class="nx">scittle</span><span class="p">.</span><span class="nx">core</span><span class="p">.</span><span class="nx">eval_string</span> <span class="nx">code</span><span class="p">)]</span>
        <span class="p">(</span><span class="kd">set</span><span class="o">!</span> <span class="p">(.</span><span class="o">-</span><span class="nx">textContent</span> <span class="nx">output</span><span class="o">-</span><span class="nx">elem</span><span class="p">)</span> <span class="p">(</span><span class="nx">str</span> <span class="nx">result</span><span class="p">)))</span>
      <span class="p">(</span><span class="k">catch</span> <span class="p">:</span><span class="k">default</span> <span class="nx">e</span>
        <span class="p">(</span><span class="kd">set</span><span class="o">!</span> <span class="p">(.</span><span class="o">-</span><span class="nx">textContent</span> <span class="nx">output</span><span class="o">-</span><span class="nx">elem</span><span class="p">)</span>
              <span class="p">(</span><span class="nx">str</span> <span class="dl">"</span><span class="s2">Error: </span><span class="dl">"</span> <span class="p">(.</span><span class="o">-</span><span class="nx">message</span> <span class="nx">e</span><span class="p">)))))))</span>

<span class="p">(</span><span class="kd">set</span><span class="o">!</span> <span class="p">(.</span><span class="o">-</span><span class="nx">run</span> <span class="nx">js</span><span class="o">/</span><span class="nb">window</span><span class="p">)</span> <span class="nx">run</span><span class="p">)</span>
<span class="nt">&lt;/script&gt;</span>
<span class="nt">&lt;textarea</span> <span class="na">id=</span><span class="s">"code"</span> <span class="na">rows=</span><span class="s">"20"</span> <span class="na">style=</span><span class="s">"width:100%;"</span><span class="nt">&gt;</span>
(defn primes [i p]
  (if (some #(zero? (mod i %)) p)
    (recur (inc i) p)
    (cons i (lazy-seq (primes (inc i) (conj p i))))))

(take 100 (primes 2 []))
<span class="nt">&lt;/textarea&gt;</span>
<span class="nt">&lt;br</span> <span class="nt">/&gt;</span>
<span class="nt">&lt;button</span> <span class="na">id=</span><span class="s">"run-button"</span> <span class="na">onclick=</span><span class="s">"run()"</span><span class="nt">&gt;</span>Run<span class="nt">&lt;/button&gt;</span>
<span class="nt">&lt;pre</span> <span class="na">id=</span><span class="s">"output"</span><span class="nt">&gt;&lt;/pre&gt;</span></code></pre></figure>

<h2 id="scittle-in-your-browser">Scittle in your browser</h2>

<script src="https://cdn.jsdelivr.net/npm/scittle@0.6.22/dist/scittle.js"></script>

<script type="application/x-scittle">
(defn run []
  (let [code (.-value (js/document.getElementById "code"))
        output-elem (js/document.getElementById "output")]
    (try
      (let [result (js/scittle.core.eval_string code)]
        (set! (.-textContent output-elem) (str result)))
      (catch :default e
        (set! (.-textContent output-elem)
              (str "Error: " (.-message e)))))))

(set! (.-run js/window) run)
</script>

<textarea id="code" rows="20" style="width:100%;">
(defn primes [i p]
  (if (some #(zero? (mod i %)) p)
    (recur (inc i) p)
    (cons i (lazy-seq (primes (inc i) (conj p i))))))

(take 100 (primes 2 []))
</textarea>
<p><br />
<button id="run-button" onclick="run()">Run</button></p>
<pre id="output"></pre>]]></content><author><name>Jan Wedekind</name></author><category term="software" /><summary type="html"><![CDATA[There is a recent article on Clojure Civitas on using Scittle for browser native slides. Scittle is a Clojure interpreter that runs in the browser. It even defines a script tag that let’s you embed Clojure code in your HTML code. Here is an example evaluating the content of an HTML textarea:]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/clojure.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/clojure.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">OpenGL Visualization with LWJGL</title><link href="https://www.wedesoft.de/graphics/2025/09/24/lwjgl-nasa-moon/" rel="alternate" type="text/html" title="OpenGL Visualization with LWJGL" /><published>2025-09-24T00:00:00+01:00</published><updated>2025-09-24T00:00:00+01:00</updated><id>https://www.wedesoft.de/graphics/2025/09/24/lwjgl-nasa-moon</id><content type="html" xml:base="https://www.wedesoft.de/graphics/2025/09/24/lwjgl-nasa-moon/"><![CDATA[<p>Using LWJGL’s OpenGL bindings and Fastmath to render data from NASA’s CGI Moon Kit</p>

<p><em>(Cross posting article published at <a href="https://clojurecivitas.org/opengl_visualization/main.html">Clojure Civitas</a>)</em></p>

<h2 id="getting-dependencies">Getting dependencies</h2>

<p>First we need to get some libraries and we can use add-libs to fetch them.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">add-libs</span><span class="w"> </span><span class="p">{</span><span class="ss">'org.lwjgl/lwjgl</span><span class="w">                      </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl$natives-linux</span><span class="w">        </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-opengl</span><span class="w">               </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-opengl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-glfw</span><span class="w">                 </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-glfw$natives-linux</span><span class="w">   </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-stb</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'org.lwjgl/lwjgl-stb$natives-linux</span><span class="w">    </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
           </span><span class="ss">'generateme/fastmath</span><span class="w">                  </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.0.0-alpha3"</span><span class="p">}})</span><span class="w">
</span><span class="p">(</span><span class="nf">require</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.java.io</span><span class="w"> </span><span class="no">:as</span><span class="w"> </span><span class="n">io</span><span class="p">]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">clojure.math</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">PI</span><span class="w"> </span><span class="n">to-radians</span><span class="p">)]</span><span class="w">
         </span><span class="o">'</span><span class="p">[</span><span class="n">fastmath.vector</span><span class="w"> </span><span class="no">:refer</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="n">sub</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">mult</span><span class="w"> </span><span class="n">normalize</span><span class="p">)])</span><span class="w">
</span><span class="p">(</span><span class="nb">import</span><span class="w"> </span><span class="o">'</span><span class="p">[</span><span class="n">javax.imageio</span><span class="w"> </span><span class="n">ImageIO</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl</span><span class="w"> </span><span class="n">BufferUtils</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.glfw</span><span class="w"> </span><span class="n">GLFW</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.opengl</span><span class="w"> </span><span class="n">GL</span><span class="w"> </span><span class="n">GL11</span><span class="w"> </span><span class="n">GL13</span><span class="w"> </span><span class="n">GL15</span><span class="w"> </span><span class="n">GL20</span><span class="w"> </span><span class="n">GL30</span><span class="p">]</span><span class="w">
        </span><span class="o">'</span><span class="p">[</span><span class="n">org.lwjgl.stb</span><span class="w"> </span><span class="n">STBImageWrite</span><span class="p">])</span></code></pre></figure>

<h2 id="creating-the-window">Creating the window</h2>

<p>Next we choose the window width and height.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="mi">640</span><span class="p">)</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="mi">480</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">1737.4</span><span class="p">)</span></code></pre></figure>

<p>We define a function to get the temporary directory.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tmpdir</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="nf">System/getProperty</span><span class="w"> </span><span class="s">"java.io.tmpdir"</span><span class="p">))</span></code></pre></figure>

<p>And then a function to get a temporary file name.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">tmpname</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="nb">str</span><span class="w"> </span><span class="p">(</span><span class="nf">tmpdir</span><span class="p">)</span><span class="w"> </span><span class="s">"/civitas-"</span><span class="w"> </span><span class="p">(</span><span class="nf">java.util.UUID/randomUUID</span><span class="p">)</span><span class="w"> </span><span class="s">".tmp"</span><span class="p">))</span></code></pre></figure>

<p>The following function is used to create screenshots for this article.
We read the pixels, write them to a temporary file using the STB library and then convert it to an ImageIO object.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">screenshot</span><span class="w">
  </span><span class="p">[]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">filename</span><span class="w"> </span><span class="p">(</span><span class="nf">tmpname</span><span class="p">)</span><span class="w">
        </span><span class="n">buffer</span><span class="w">   </span><span class="p">(</span><span class="nf">java.nio.ByteBuffer/allocateDirect</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">))]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL11/glReadPixels</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w">
                       </span><span class="n">GL11/GL_RGBA</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_BYTE</span><span class="w"> </span><span class="n">buffer</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">STBImageWrite/stbi_write_png</span><span class="w"> </span><span class="n">filename</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="mi">4</span><span class="w">
                                  </span><span class="n">buffer</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">window-width</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">filename</span><span class="w"> </span><span class="n">io/file</span><span class="w"> </span><span class="p">(</span><span class="nf">ImageIO/read</span><span class="p">))))</span></code></pre></figure>

<p>We need to initialize the GLFW library.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwInit</span><span class="p">)</span></code></pre></figure>

<p>Now we create an invisible window.
You can create a visisble window if you want to by not setting the visibility hint to false.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">window</span><span class="w">
  </span><span class="p">(</span><span class="nf">do</span><span class="w">
    </span><span class="p">(</span><span class="nf">GLFW/glfwDefaultWindowHints</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GLFW/glfwWindowHint</span><span class="w"> </span><span class="n">GLFW/GLFW_VISIBLE</span><span class="w"> </span><span class="n">GLFW/GLFW_FALSE</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GLFW/glfwCreateWindow</span><span class="w"> </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="w"> </span><span class="s">"Invisible Window"</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="p">)))</span></code></pre></figure>

<p>If you have a visible window, you can show it as follows.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwShowWindow</span><span class="w"> </span><span class="n">window</span><span class="p">)</span></code></pre></figure>

<p>Note that if you are using a visible window, you always need to swap buffers after rendering.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwSwapBuffers</span><span class="w"> </span><span class="n">window</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GLFW/glfwMakeContextCurrent</span><span class="w"> </span><span class="n">window</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL/createCapabilities</span><span class="p">))</span></code></pre></figure>

<h2 id="basic-rendering">Basic rendering</h2>

<h3 id="clearing-the-window">Clearing the window</h3>

<p>A simple test is to set a clear color and clear the window.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClearColor</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="mf">0.25</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon0.jpg" alt="screenshot 0" /></p>

<h3 id="creating-shader-programs">Creating shader programs</h3>

<p>We define a convenience function to compile a shader and handle any errors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-shader</span><span class="w"> </span><span class="p">[</span><span class="n">source</span><span class="w"> </span><span class="n">shader-type</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateShader</span><span class="w"> </span><span class="n">shader-type</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glShaderSource</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">source</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glCompileShader</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderi</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="n">GL20/GL_COMPILE_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetShaderInfoLog</span><span class="w"> </span><span class="n">shader</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">shader</span><span class="p">))</span></code></pre></figure>

<p>We also define a convenience function to link a program and handle any errors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">make-program</span><span class="w"> </span><span class="p">[</span><span class="o">&amp;</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glCreateProgram</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">doseq</span><span class="w"> </span><span class="p">[</span><span class="n">shader</span><span class="w"> </span><span class="n">shaders</span><span class="p">]</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glAttachShader</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">shader</span><span class="p">)</span><span class="w">
           </span><span class="p">(</span><span class="nf">GL20/glDeleteShader</span><span class="w"> </span><span class="n">shader</span><span class="p">))</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL20/glLinkProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">zero?</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgrami</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="n">GL20/GL_LINK_STATUS</span><span class="p">))</span><span class="w">
      </span><span class="p">(</span><span class="nf">throw</span><span class="w"> </span><span class="p">(</span><span class="nf">Exception.</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetProgramInfoLog</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="mi">1024</span><span class="p">))))</span><span class="w">
    </span><span class="n">program</span><span class="p">))</span></code></pre></figure>

<p>The following code shows a simple vertex shader which passes through vertex coordinates.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-source</span><span class="w"> </span><span class="s">"
#version 130

in vec3 point;

void main()
{
  gl_Position = vec4(point, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>In the fragment shader we use the pixel coordinates to output a color ramp.
The uniform variable iResolution will later be set to the window resolution.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-source</span><span class="w"> </span><span class="s">"
#version 130

uniform vec2 iResolution;
out vec4 fragColor;

void main()
{
  fragColor = vec4(gl_FragCoord.xy / iResolution.xy, 0, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>Let’s compile the shaders and link the program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-source</span><span class="w"> </span><span class="n">GL20/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-source</span><span class="w"> </span><span class="n">GL20/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader</span><span class="w"> </span><span class="n">fragment-shader</span><span class="p">)))</span></code></pre></figure>

<p><strong>Note:</strong> It is beyond the topic of this talk, but you can set up a Clojure function to test an OpenGL shader function by using a probing fragment shader and rendering to a one pixel texture.
Please see my article <a href="https://www.wedesoft.de/software/2022/07/01/tdd-with-opengl/">Test Driven Development with OpenGL</a> for more information!</p>

<h3 id="creating-vertex-buffer-data">Creating vertex buffer data</h3>

<p>To provide the shader program with vertex data we are going to define just a single quad consisting of four vertices.</p>

<p>First we define a macro and use it to define convenience functions for converting arrays to LWJGL buffer objects.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defmacro</span><span class="w"> </span><span class="n">def-make-buffer</span><span class="w"> </span><span class="p">[</span><span class="n">method</span><span class="w"> </span><span class="n">create-buffer</span><span class="p">]</span><span class="w">
  </span><span class="o">`</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="o">~</span><span class="n">method</span><span class="w"> </span><span class="p">[</span><span class="n">data</span><span class="o">#</span><span class="p">]</span><span class="w">
     </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="p">(</span><span class="o">~</span><span class="n">create-buffer</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">.put</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="w"> </span><span class="n">data</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">.flip</span><span class="w"> </span><span class="n">buffer</span><span class="o">#</span><span class="p">)</span><span class="w">
       </span><span class="n">buffer</span><span class="o">#</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-float-buffer</span><span class="w"> </span><span class="n">BufferUtils/createFloatBuffer</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-int-buffer</span><span class="w"> </span><span class="n">BufferUtils/createIntBuffer</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">def-make-buffer</span><span class="w"> </span><span class="n">make-byte-buffer</span><span class="w"> </span><span class="n">BufferUtils/createByteBuffer</span><span class="p">))</span></code></pre></figure>

<p>We define a simple background quad spanning the entire window.
We use normalised device coordinates (NDC) which are between -1 and 1.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices</span><span class="w">
  </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">[</span><span class="w"> </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">0.0</span><span class="p">]))</span></code></pre></figure>

<p>The index array defines the order of the vertices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices</span><span class="w">
  </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="p">]))</span></code></pre></figure>

<h3 id="setting-up-the-vertex-buffer">Setting up the vertex buffer</h3>

<p>We add a convenience function to setup VAO, VBO, and IBO.</p>

<ul>
  <li>We define a vertex array object (VAO) which acts like a context for the vertex and index buffer.</li>
  <li>We define a vertex buffer object (VBO) which contains the vertex data.</li>
  <li>We also define an index buffer object (IBO) which contains the index data.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">setup-vao</span><span class="w"> </span><span class="p">[</span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="p">(</span><span class="nf">GL30/glGenVertexArrays</span><span class="p">)</span><span class="w">
        </span><span class="n">vbo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)</span><span class="w">
        </span><span class="n">ibo</span><span class="w"> </span><span class="p">(</span><span class="nf">GL15/glGenBuffers</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-float-buffer</span><span class="w"> </span><span class="n">vertices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">GL15/glBufferData</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="p">(</span><span class="nf">make-int-buffer</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w">
                       </span><span class="n">GL15/GL_STATIC_DRAW</span><span class="p">)</span><span class="w">
    </span><span class="p">{</span><span class="no">:vao</span><span class="w"> </span><span class="n">vao</span><span class="w"> </span><span class="no">:vbo</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="no">:ibo</span><span class="w"> </span><span class="n">ibo</span><span class="p">}))</span></code></pre></figure>

<p>Now we use the function to setup the VAO, VBO, and IBO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices</span><span class="w"> </span><span class="n">indices</span><span class="p">))</span></code></pre></figure>

<p>The data of each vertex is defined by 3 floats (x, y, z).
We need to specify the layout of the vertex buffer object so that OpenGL knows how to interpret it.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<h3 id="rendering-the-quad">Rendering the quad</h3>

<p>We select the program and define the uniform variable iResolution.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">))</span></code></pre></figure>

<p>Since the correct VAO is already bound from the earlier example, we are now ready to draw the quad.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">screenshot</span><span class="p">)</span></code></pre></figure>

<p><img src="/pics/moon1.jpg" alt="screenshot 1" /></p>

<p>This time the quad shows a color ramp!</p>

<h3 id="finishing-up">Finishing up</h3>

<p>We only delete the program since we are going to reuse the VAO in the next example.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program</span><span class="p">)</span></code></pre></figure>

<h2 id="rendering-a-texture">Rendering a Texture</h2>

<h3 id="getting-the-nasa-data">Getting the NASA data</h3>

<p>We define a function to download a file from the web.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">download</span><span class="w"> </span><span class="p">[</span><span class="n">url</span><span class="w"> </span><span class="n">target</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">with-open</span><span class="w"> </span><span class="p">[</span><span class="n">in</span><span class="w"> </span><span class="p">(</span><span class="nf">io/input-stream</span><span class="w"> </span><span class="n">url</span><span class="p">)</span><span class="w">
              </span><span class="n">out</span><span class="w"> </span><span class="p">(</span><span class="nf">io/output-stream</span><span class="w"> </span><span class="n">target</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">io/copy</span><span class="w"> </span><span class="n">in</span><span class="w"> </span><span class="n">out</span><span class="p">)))</span></code></pre></figure>

<p>If it does not exist, we download the lunar color map from the <a href="https://svs.gsfc.nasa.gov/4720/">NASA CGI Moon Kit</a>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">moon-tif</span><span class="w"> </span><span class="s">"src/opengl_visualization/lroc_color_poles_2k.tif"</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">not</span><span class="w"> </span><span class="p">(</span><span class="nf">.exists</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-tif</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">download</span><span class="w">
      </span><span class="s">"https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/lroc_color_poles_2k.tif"</span><span class="w">
      </span><span class="n">moon-tif</span><span class="p">)))</span></code></pre></figure>

<h3 id="create-a-texture">Create a texture</h3>

<p>Next we load the image using ImageIO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color</span><span class="w"> </span><span class="p">(</span><span class="nf">ImageIO/read</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-tif</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-raster</span><span class="w"> </span><span class="p">(</span><span class="nf">.getRaster</span><span class="w"> </span><span class="n">color</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="p">(</span><span class="nf">.getWidth</span><span class="w"> </span><span class="n">color-raster</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="p">(</span><span class="nf">.getHeight</span><span class="w"> </span><span class="n">color-raster</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-channels</span><span class="w"> </span><span class="p">(</span><span class="nf">.getNumBands</span><span class="w"> </span><span class="n">color-raster</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">color-pixels</span><span class="w"> </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="n">color-channels</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">.getPixels</span><span class="w"> </span><span class="n">color-raster</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="n">color-pixels</span><span class="p">)</span><span class="w">
  </span><span class="p">[</span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="n">color-channels</span><span class="p">])</span><span class="w">
</span><span class="c1">; [2048 1024 3]</span></code></pre></figure>

<p>Then we create an OpenGL texture from the RGB data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">texture-color</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexImage2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL11/GL_RGBA</span><span class="w"> </span><span class="n">color-width</span><span class="w"> </span><span class="n">color-height</span><span class="w"> </span><span class="mi">0</span><span class="w">
                     </span><span class="n">GL11/GL_RGB</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_BYTE</span><span class="w">
                     </span><span class="p">(</span><span class="nf">make-byte-buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">byte-array</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="n">unchecked-byte</span><span class="w"> </span><span class="n">color-pixels</span><span class="p">))))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<h3 id="rendering-the-texture">Rendering the texture</h3>

<p>We are going to use the vertex pass through shader again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-tex</span><span class="w"> </span><span class="s">"
#version 130

in vec3 point;

void main()
{
  gl_Position = vec4(point, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>The fragment shader now uses the texture function to lookup color values from a texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-tex</span><span class="w"> </span><span class="s">"
#version 130

uniform vec2 iResolution;
uniform sampler2D moon;
out vec4 fragColor;

void main()
{
  fragColor = texture(moon, gl_FragCoord.xy / iResolution.xy);
}"</span><span class="p">)</span></code></pre></figure>

<p>We compile and link the shaders to create a program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-tex-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-tex</span><span class="w"> </span><span class="n">GL20/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-tex-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-tex</span><span class="w"> </span><span class="n">GL20/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-tex-shader</span><span class="w"> </span><span class="n">fragment-tex-shader</span><span class="p">)))</span></code></pre></figure>

<p>We need to set up the layout of the vertex data again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>We set the resolution and bind the texture to the texture slot number 0.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">tex-program</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">tex-program</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">))</span></code></pre></figure>

<p>The quad now is textured!</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon2.jpg" alt="screenshot 2" /></p>

<h3 id="finishing-up-1">Finishing up</h3>

<p>We create a convenience function to tear down the VAO, VBO, and IBO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">teardown-vao</span><span class="w"> </span><span class="p">[{</span><span class="no">:keys</span><span class="w"> </span><span class="p">[</span><span class="n">vao</span><span class="w"> </span><span class="n">vbo</span><span class="w"> </span><span class="n">ibo</span><span class="p">]}]</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ELEMENT_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">ibo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glBindBuffer</span><span class="w"> </span><span class="n">GL15/GL_ARRAY_BUFFER</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vbo</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL30/glBindVertexArray</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL15/glDeleteBuffers</span><span class="w"> </span><span class="n">vao</span><span class="p">))</span></code></pre></figure>

<p>We tear down the quad.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao</span><span class="p">)</span></code></pre></figure>

<p>We also delete the program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">tex-program</span><span class="p">)</span></code></pre></figure>

<h2 id="render-a-3d-cube">Render a 3D cube</h2>

<h3 id="create-vertex-data">Create vertex data</h3>

<p>If we want to render a cube, we need to define 8 vertices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices-cube</span><span class="w">
  </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">[</span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
                 </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">
                </span><span class="mf">-1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="w">  </span><span class="mf">1.0</span><span class="p">]))</span></code></pre></figure>

<p>The cube is made up of 6 quads, with 4 vertex indices per quad.
So we require 6 * 4 = 24 indices.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices-cube</span><span class="w">
  </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">[</span><span class="mi">0</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">3</span><span class="w">
              </span><span class="mi">7</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">4</span><span class="w">
              </span><span class="mi">0</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="mi">7</span><span class="w"> </span><span class="mi">4</span><span class="w">
              </span><span class="mi">5</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">1</span><span class="w">
              </span><span class="mi">3</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">6</span><span class="w"> </span><span class="mi">7</span><span class="w">
              </span><span class="mi">4</span><span class="w"> </span><span class="mi">5</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="mi">0</span><span class="p">]))</span></code></pre></figure>

<h3 id="initialize-vertex-buffer-array">Initialize vertex buffer array</h3>

<p>We use the function from earlier to set up the VAO, VBO, and IBO.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao-cube</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices-cube</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">))</span></code></pre></figure>

<h3 id="shader-program-mapping-texture-onto-cube">Shader program mapping texture onto cube</h3>

<p>We first define a vertex shader, which takes cube coordinates, rotates, translates, and projects them.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="s">"
#version 130

uniform float fov;
uniform float alpha;
uniform float beta;
uniform float distance;
uniform vec2 iResolution;
in vec3 point;
out vec3 vpoint;

void main()
{
  // Rotate and translate vertex
  mat3 rot_y = mat3(vec3(cos(alpha), 0, sin(alpha)),
                    vec3(0, 1, 0),
                    vec3(-sin(alpha), 0, cos(alpha)));
  mat3 rot_x = mat3(vec3(1, 0, 0),
                    vec3(0, cos(beta), -sin(beta)),
                    vec3(0, sin(beta), cos(beta)));
  vec3 p = rot_x * rot_y * point + vec3(0, 0, distance);

  // Project vertex creating normalized device coordinates
  float f = 1.0 / tan(fov / 2.0);
  float aspect = iResolution.x / iResolution.y;
  float proj_x = p.x / p.z * f;
  float proj_y = p.y / p.z * f * aspect;
  float proj_z = p.z / (2.0 * distance);

  // Output to shader pipeline.
  gl_Position = vec4(proj_x, proj_y, proj_z, 1);
  vpoint = point;
}"</span><span class="p">)</span></code></pre></figure>

<p>The fragment shader maps the texture onto the cube.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-moon</span><span class="w"> </span><span class="s">"
#version 130

#define PI 3.1415926535897932384626433832795

uniform sampler2D moon;
in vec3 vpoint;
out vec4 fragColor;

vec2 lonlat(vec3 p)
{
  float lon = atan(p.x, -p.z) / (2.0 * PI) + 0.5;
  float lat = atan(p.y, length(p.xz)) / PI + 0.5;
  return vec2(lon, lat);
}

vec3 color(vec2 lonlat)
{
  return texture(moon, lonlat).rgb;
}

void main()
{
  fragColor = vec4(color(lonlat(vpoint)).rgb, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We compile and link the shaders.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader-moon</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="n">GL30/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader-moon</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-moon</span><span class="w"> </span><span class="n">GL30/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader-moon</span><span class="w"> </span><span class="n">fragment-shader-moon</span><span class="p">)))</span></code></pre></figure>

<p>We need to set up the memory layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<h3 id="rendering-the-cube">Rendering the cube</h3>

<p>This shader program requires setup of several uniforms and a texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program-moon</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"fov"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">25.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"alpha"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">30.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"beta"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">-20.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="mf">10.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">))</span></code></pre></figure>

<p>We enable back face culling to only render the front faces of the cube.
Then we clear the window and render the cube.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glEnable</span><span class="w"> </span><span class="n">GL11/GL_CULL_FACE</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glCullFace</span><span class="w"> </span><span class="n">GL11/GL_BACK</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClearColor</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">0.0</span><span class="w"> </span><span class="mf">1.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon3.jpg" alt="screenshot 3" /></p>

<p>This looks interesting but it is not a good approximation of the moon.</p>

<h3 id="finishing-up-2">Finishing up</h3>

<p>To finish up we delete the vertex data for the cube.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao-cube</span><span class="p">)</span></code></pre></figure>

<h2 id="approximating-a-sphere">Approximating a sphere</h2>

<h3 id="creating-the-vertex-data">Creating the vertex data</h3>

<p>First we partition the vertex data and convert the triplets to 8 Fastmath vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">points</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">vec3</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">vertices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">points</span><span class="w">
</span><span class="c1">; ([-1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [1.0 1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 -1.0 1.0]</span><span class="w">
</span><span class="c1">;  [1.0 -1.0 1.0]</span><span class="w">
</span><span class="c1">;  [1.0 1.0 1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 1.0])</span></code></pre></figure>

<p>Then we use the index array to get the coordinates of the first corner of each face resulting in 6 Fastmath vectors.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">corners</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[[</span><span class="n">i</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">i</span><span class="p">))</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">corners</span><span class="w">
</span><span class="c1">; ([-1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 -1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [1.0 -1.0 1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 1.0 -1.0]</span><span class="w">
</span><span class="c1">;  [-1.0 -1.0 1.0])</span></code></pre></figure>

<p>We get the first spanning vector of each face by subtracting the second corner from the first.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">u-vectors</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[[</span><span class="n">i</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">j</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">i</span><span class="p">)))</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">u-vectors</span><span class="w">
</span><span class="c1">; ([2.0 0.0 0.0]</span><span class="w">
</span><span class="c1">;  [2.0 0.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 2.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 2.0 0.0]</span><span class="w">
</span><span class="c1">;  [2.0 0.0 0.0]</span><span class="w">
</span><span class="c1">;  [2.0 0.0 0.0])</span></code></pre></figure>

<p>We get the second spanning vector of each face by subtracting the fourth corner from the first.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">v-vectors</span><span class="w">
  </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[[</span><span class="n">i</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">_</span><span class="w"> </span><span class="n">l</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nf">sub</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">l</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">points</span><span class="w"> </span><span class="n">i</span><span class="p">)))</span><span class="w">
       </span><span class="p">(</span><span class="nf">partition</span><span class="w"> </span><span class="mi">4</span><span class="w"> </span><span class="n">indices-cube</span><span class="p">)))</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="n">v-vectors</span><span class="w">
</span><span class="c1">; ([0.0 2.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 -2.0 0.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 2.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 -2.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 2.0]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 -2.0])</span></code></pre></figure>

<p>We can now use vector math to subsample the faces and project the points onto a sphere by normalizing the vectors and multiplying with the moon radius.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sphere-points</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="n">u</span><span class="w"> </span><span class="n">v</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">j</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))]</span><span class="w">
       </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="p">(</span><span class="nf">normalize</span><span class="w"> </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="n">c</span><span class="w"> </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="n">u</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="n">v</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="n">n</span><span class="p">)))))</span><span class="w"> </span><span class="n">radius</span><span class="p">)))</span></code></pre></figure>

<p>Subdividing once results in 9 corners for a cube face.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">sphere-points</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">corners</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">u-vectors</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">nth</span><span class="w"> </span><span class="n">v-vectors</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span><span class="w">
</span><span class="c1">; ([-1003.088357690056 -1003.088357690056 -1003.088357690056]</span><span class="w">
</span><span class="c1">;  [0.0 -1228.5273216335077 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [1003.088357690056 -1003.088357690056 -1003.088357690056]</span><span class="w">
</span><span class="c1">;  [-1228.5273216335077 0.0 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [0.0 0.0 -1737.4]</span><span class="w">
</span><span class="c1">;  [1228.5273216335077 0.0 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [-1003.088357690056 1003.088357690056 -1003.088357690056]</span><span class="w">
</span><span class="c1">;  [0.0 1228.5273216335077 -1228.5273216335077]</span><span class="w">
</span><span class="c1">;  [1003.088357690056 1003.088357690056 -1003.088357690056])</span></code></pre></figure>

<p>We also need a function to generate the indices for the quads.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">sphere-indices</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w"> </span><span class="n">face</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">for</span><span class="w"> </span><span class="p">[</span><span class="n">j</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="n">i</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">n</span><span class="p">)]</span><span class="w">
       </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">offset</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">face</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">j</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">n</span><span class="p">))</span><span class="w"> </span><span class="n">i</span><span class="p">)]</span><span class="w">
         </span><span class="p">[</span><span class="n">offset</span><span class="w"> </span><span class="p">(</span><span class="nb">inc</span><span class="w"> </span><span class="n">offset</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">offset</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">1</span><span class="p">)])))</span></code></pre></figure>

<p>Subdividing once results in 4 quads for a cube face.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">sphere-indices</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
</span><span class="c1">; ([0 1 4 3] [1 2 5 4] [3 4 7 6] [4 5 8 7])</span></code></pre></figure>

<h3 id="rendering-a-coarse-approximation-of-the-sphere">Rendering a coarse approximation of the sphere.</h3>

<p>We subdivide once (n=2) and create a VAO with the data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">2</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices-sphere</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-points</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w">
                                                  </span><span class="n">corners</span><span class="w"> </span><span class="n">u-vectors</span><span class="w"> </span><span class="n">v-vectors</span><span class="p">))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices-sphere</span><span class="w"> </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-indices</span><span class="w"> </span><span class="n">n</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">6</span><span class="p">)))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao-sphere</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices-sphere</span><span class="w"> </span><span class="n">indices-sphere</span><span class="p">)))</span></code></pre></figure>

<p>The layout needs to be configured again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>The distance needs to be increased, because the points are on a sphere with the radius of the moon.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))</span></code></pre></figure>

<p>Rendering the mesh now results in a better approximation of a sphere.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon4.jpg" alt="screenshot 4" /></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao-sphere</span><span class="p">)</span></code></pre></figure>

<h3 id="rendering-a-fine-approximation-of-the-sphere">Rendering a fine approximation of the sphere.</h3>

<p>To get a high quality approximation we subdivide more and create a VAO with the data. (do</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">n2</span><span class="w"> </span><span class="mi">16</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertices-sphere-high</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-points</span><span class="w"> </span><span class="n">n2</span><span class="p">)</span><span class="w"> </span><span class="n">corners</span><span class="w"> </span><span class="n">u-vectors</span><span class="w"> </span><span class="n">v-vectors</span><span class="p">))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="w"> </span><span class="p">(</span><span class="nf">int-array</span><span class="w"> </span><span class="p">(</span><span class="nf">flatten</span><span class="w"> </span><span class="p">(</span><span class="nb">map</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">sphere-indices</span><span class="w"> </span><span class="n">n2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="mi">6</span><span class="p">)))))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vao-sphere-high</span><span class="w"> </span><span class="p">(</span><span class="nf">setup-vao</span><span class="w"> </span><span class="n">vertices-sphere-high</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)))</span></code></pre></figure>

<p>We set up the vertex layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-moon</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>Rendering the mesh now results in a spherical mesh with a texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon5.jpg" alt="screenshot 5" /></p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program-moon</span><span class="p">)</span></code></pre></figure>

<h2 id="adding-ambient-and-diffuse-reflection">Adding ambient and diffuse reflection</h2>

<p>In order to introduce lighting we add ambient and diffuse lighting to the fragment shader.
We use the ambient and diffuse lighting from the <a href="https://learnopengl.com/Lighting/Basic-Lighting">Phong shading model</a>.</p>

<ul>
  <li>The ambient light is a constant value.</li>
  <li>The diffuse light is calculated using the dot product of the light vector and the normal vector.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-moon-diffuse</span><span class="w"> </span><span class="s">"
#version 130

#define PI 3.1415926535897932384626433832795

uniform vec3 light;
uniform float ambient;
uniform float diffuse;
uniform sampler2D moon;
in vec3 vpoint;
out vec4 fragColor;

vec2 lonlat(vec3 p)
{
  float lon = atan(p.x, -p.z) / (2.0 * PI) + 0.5;
  float lat = atan(p.y, length(p.xz)) / PI + 0.5;
  return vec2(lon, lat);
}

vec3 color(vec2 lonlat)
{
  return texture(moon, lonlat).rgb;
}

void main()
{
  float phong = ambient + diffuse * max(0.0, dot(light, normalize(vpoint)));
  fragColor = vec4(color(lonlat(vpoint)) * phong, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We reuse the vertex shader from the previous example and the new fragment shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader-diffuse</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="n">GL30/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader-diffuse</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-moon-diffuse</span><span class="w"> </span><span class="n">GL30/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader-diffuse</span><span class="w"> </span><span class="n">fragment-shader-diffuse</span><span class="p">)))</span></code></pre></figure>

<p>We set up the vertex data layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>A normalized light vector is defined.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">light</span><span class="w"> </span><span class="p">(</span><span class="nf">normalize</span><span class="w"> </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="mi">-1</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">-1</span><span class="p">)))</span></code></pre></figure>

<p>Before rendering we need to set up the various uniform values.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program-diffuse</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"fov"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">20.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"alpha"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"beta"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"ambient"</span><span class="p">)</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"diffuse"</span><span class="p">)</span><span class="w"> </span><span class="mf">1.6</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform3f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"light"</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-diffuse</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">))</span></code></pre></figure>

<p>Finally we are ready to render the mesh with diffuse shading.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon6.jpg" alt="screenshot 6" /></p>

<p>Afterwards we delete the shader program.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program-diffuse</span><span class="p">)</span></code></pre></figure>

<h2 id="using-normal-mapping">Using normal mapping</h2>

<h3 id="load-elevation-data-into-texture">Load elevation data into texture</h3>

<p>In the final section we also want to add normal mapping in order to get realistic shading of craters.</p>

<p>The lunar elevation data is downloaded from NASA’s website.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">moon-ldem</span><span class="w"> </span><span class="s">"src/opengl_visualization/ldem_4.tif"</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nb">when</span><span class="w"> </span><span class="p">(</span><span class="nb">not</span><span class="w"> </span><span class="p">(</span><span class="nf">.exists</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-ldem</span><span class="p">)))</span><span class="w">
    </span><span class="p">(</span><span class="nf">download</span><span class="w"> </span><span class="s">"https://svs.gsfc.nasa.gov/vis/a000000/a004700/a004720/ldem_4.tif"</span><span class="w">
              </span><span class="n">moon-ldem</span><span class="p">)))</span></code></pre></figure>

<p>The image is read using ImageIO and the floating point elevation data is extracted.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem</span><span class="w"> </span><span class="p">(</span><span class="nf">ImageIO/read</span><span class="w"> </span><span class="p">(</span><span class="nf">io/file</span><span class="w"> </span><span class="n">moon-ldem</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-raster</span><span class="w"> </span><span class="p">(</span><span class="nf">.getRaster</span><span class="w"> </span><span class="n">ldem</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="p">(</span><span class="nf">.getWidth</span><span class="w"> </span><span class="n">ldem</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-height</span><span class="w"> </span><span class="p">(</span><span class="nf">.getHeight</span><span class="w"> </span><span class="n">ldem</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">ldem-pixels</span><span class="w"> </span><span class="p">(</span><span class="nf">float-array</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="p">)))</span><span class="w">
  </span><span class="p">(</span><span class="nf">do</span><span class="w"> </span><span class="p">(</span><span class="nf">.getPixels</span><span class="w"> </span><span class="n">ldem-raster</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="w"> </span><span class="n">ldem-pixels</span><span class="p">)</span><span class="w"> </span><span class="n">nil</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">resolution</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">PI</span><span class="w"> </span><span class="n">radius</span><span class="p">)</span><span class="w"> </span><span class="n">ldem-width</span><span class="p">))</span><span class="w">
  </span><span class="p">[</span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="p">])</span><span class="w">
</span><span class="c1">; [1440 720]</span></code></pre></figure>

<p>The floating point pixel data is converted into a texture</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">texture-ldem</span><span class="w"> </span><span class="p">(</span><span class="nf">GL11/glGenTextures</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-ldem</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MIN_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_MAG_FILTER</span><span class="w"> </span><span class="n">GL11/GL_LINEAR</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_S</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexParameteri</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_WRAP_T</span><span class="w"> </span><span class="n">GL11/GL_REPEAT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glTexImage2D</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">GL30/GL_R32F</span><span class="w"> </span><span class="n">ldem-width</span><span class="w"> </span><span class="n">ldem-height</span><span class="w"> </span><span class="mi">0</span><span class="w">
                     </span><span class="n">GL11/GL_RED</span><span class="w"> </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">ldem-pixels</span><span class="p">))</span></code></pre></figure>

<h3 id="create-shader-program-with-normal-mapping">Create shader program with normal mapping</h3>

<p>We reuse the vertex shader from the previous section.</p>

<p>The fragment shader this time is more involved.</p>

<ul>
  <li>A horizon matrix with normal, tangent, and bitangent vectors is computed.</li>
  <li>The elevation is sampled in four directions from the current 3D point.</li>
  <li>The elevation values are used to create two surface vectors.</li>
  <li>The cross product of the surface vectors is computed and normalized to get the normal vector.</li>
  <li>This perturbed normal vector is now used to compute diffuse lighting.</li>
</ul>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-normal</span><span class="w"> </span><span class="s">"
#version 130

#define PI 3.1415926535897932384626433832795

uniform vec3 light;
uniform float ambient;
uniform float diffuse;
uniform float resolution;
uniform sampler2D moon;
uniform sampler2D ldem;
in vec3 vpoint;
out vec4 fragColor;

vec3 orthogonal_vector(vec3 n)
{
  vec3 b;
  if (abs(n.x) &lt;= abs(n.y)) {
    if (abs(n.x) &lt;= abs(n.z))
      b = vec3(1, 0, 0);
    else
      b = vec3(0, 0, 1);
  } else {
    if (abs(n.y) &lt;= abs(n.z))
      b = vec3(0, 1, 0);
    else
      b = vec3(0, 0, 1);
  };
  return normalize(cross(n, b));
}

mat3 oriented_matrix(vec3 n)
{
  vec3 o1 = orthogonal_vector(n);
  vec3 o2 = cross(n, o1);
  return mat3(n, o1, o2);
}

vec2 lonlat(vec3 p)
{
  float lon = atan(p.x, -p.z) / (2.0 * PI) + 0.5;
  float lat = atan(p.y, length(p.xz)) / PI + 0.5;
  return vec2(lon, lat);
}

vec3 color(vec2 lonlat)
{
  return texture(moon, lonlat).rgb;
}

float elevation(vec3 p)
{
  return texture(ldem, lonlat(p)).r;
}

vec3 normal(mat3 horizon, vec3 p)
{
  vec3 pl = p + horizon * vec3(0, -1,  0) * resolution;
  vec3 pr = p + horizon * vec3(0,  1,  0) * resolution;
  vec3 pu = p + horizon * vec3(0,  0, -1) * resolution;
  vec3 pd = p + horizon * vec3(0,  0,  1) * resolution;
  vec3 u = horizon * vec3(elevation(pr) - elevation(pl), 2 * resolution, 0);
  vec3 v = horizon * vec3(elevation(pd) - elevation(pu), 0, 2 * resolution);
  return normalize(cross(u, v));
}

void main()
{
  mat3 horizon = oriented_matrix(normalize(vpoint));
  float phong = ambient + diffuse * max(0.0, dot(light, normal(horizon, vpoint)));
  fragColor = vec4(color(lonlat(vpoint)).rgb * phong, 1);
}"</span><span class="p">)</span></code></pre></figure>

<p>We reuse the vertex shader from the previous example and the new fragment shader.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vertex-shader-normal</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">vertex-moon</span><span class="w"> </span><span class="n">GL30/GL_VERTEX_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fragment-shader-normal</span><span class="w"> </span><span class="p">(</span><span class="nf">make-shader</span><span class="w"> </span><span class="n">fragment-normal</span><span class="w"> </span><span class="n">GL30/GL_FRAGMENT_SHADER</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="p">(</span><span class="nf">make-program</span><span class="w"> </span><span class="n">vertex-shader-normal</span><span class="w"> </span><span class="n">fragment-shader-normal</span><span class="p">)))</span></code></pre></figure>

<p>We set up the vertex data layout again.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glVertexAttribPointer</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetAttribLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"point"</span><span class="p">)</span><span class="w"> </span><span class="mi">3</span><span class="w">
                              </span><span class="n">GL11/GL_FLOAT</span><span class="w"> </span><span class="n">false</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">3</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="n">Float/BYTES</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glEnableVertexAttribArray</span><span class="w"> </span><span class="mi">0</span><span class="p">))</span></code></pre></figure>

<p>Apart from the uniform values we also need to set up two textures this time: the color texture and the elevation texture.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUseProgram</span><span class="w"> </span><span class="n">program-normal</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform2f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"iResolution"</span><span class="p">)</span><span class="w">
                    </span><span class="n">window-width</span><span class="w"> </span><span class="n">window-height</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"fov"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">20.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"alpha"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"beta"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">to-radians</span><span class="w"> </span><span class="mf">0.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"distance"</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">radius</span><span class="w"> </span><span class="mf">10.0</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"resolution"</span><span class="p">)</span><span class="w"> </span><span class="n">resolution</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"ambient"</span><span class="p">)</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"diffuse"</span><span class="p">)</span><span class="w"> </span><span class="mf">1.6</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform3f</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"light"</span><span class="p">)</span><span class="w">
                    </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">light</span><span class="w"> </span><span class="mi">2</span><span class="p">))</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"moon"</span><span class="p">)</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL20/glUniform1i</span><span class="w"> </span><span class="p">(</span><span class="nf">GL20/glGetUniformLocation</span><span class="w"> </span><span class="n">program-normal</span><span class="w"> </span><span class="s">"ldem"</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-color</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL13/glActiveTexture</span><span class="w"> </span><span class="n">GL13/GL_TEXTURE1</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glBindTexture</span><span class="w"> </span><span class="n">GL11/GL_TEXTURE_2D</span><span class="w"> </span><span class="n">texture-ldem</span><span class="p">))</span></code></pre></figure>

<p>Finally we are ready to render the mesh with normal mapping.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">do</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glClear</span><span class="w"> </span><span class="n">GL11/GL_COLOR_BUFFER_BIT</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">GL11/glDrawElements</span><span class="w"> </span><span class="n">GL11/GL_QUADS</span><span class="w"> </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">indices-sphere-high</span><span class="p">)</span><span class="w"> </span><span class="n">GL11/GL_UNSIGNED_INT</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w">
  </span><span class="p">(</span><span class="nf">screenshot</span><span class="p">))</span></code></pre></figure>

<p><img src="/pics/moon7.jpg" alt="screenshot 7" /></p>

<p>Afterwards we delete the shader program and the vertex data.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL20/glDeleteProgram</span><span class="w"> </span><span class="n">program-normal</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">teardown-vao</span><span class="w"> </span><span class="n">vao-sphere-high</span><span class="p">)</span></code></pre></figure>

<p>And the textures.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">texture-color</span><span class="p">)</span></code></pre></figure>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GL11/glDeleteTextures</span><span class="w"> </span><span class="n">texture-ldem</span><span class="p">)</span></code></pre></figure>

<h2 id="finalizing-glfw">Finalizing GLFW</h2>

<p>When we are finished, we destroy the window.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwDestroyWindow</span><span class="w"> </span><span class="n">window</span><span class="p">)</span></code></pre></figure>

<p>Finally we terminate use of the GLFW library.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">GLFW/glfwTerminate</span><span class="p">)</span></code></pre></figure>

<p>I hope you liked this 3D graphics example.</p>

<p>Note that in practise you will</p>

<ul>
  <li>use higher resolution data and map the data onto texture tiles</li>
  <li>generate textures containing normal maps offline</li>
  <li>create a multiresolution map</li>
  <li>use tessellation to increase the mesh resolution</li>
  <li>use elevation data to deform the mesh</li>
</ul>

<p>Thanks to <a href="https://timothypratley.blogspot.com/p/httpswww.html">Timothy Pratley</a> for helping getting this post online.</p>]]></content><author><name>Jan Wedekind</name></author><category term="graphics" /><summary type="html"><![CDATA[Using LWJGL’s OpenGL bindings and Fastmath to render data from NASA’s CGI Moon Kit]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/moon.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/moon.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Developing a Space Flight Simulator in Clojure</title><link href="https://www.wedesoft.de/software/2025/09/05/clojure-game/" rel="alternate" type="text/html" title="Developing a Space Flight Simulator in Clojure" /><published>2025-09-05T00:00:00+01:00</published><updated>2025-09-05T00:00:00+01:00</updated><id>https://www.wedesoft.de/software/2025/09/05/clojure-game</id><content type="html" xml:base="https://www.wedesoft.de/software/2025/09/05/clojure-game/"><![CDATA[<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/38FGT7SWVh0" frameborder="0" allowfullscreen=""></iframe></div>

<p>In 2017 I discovered the free of charge <a href="http://orbit.medphys.ucl.ac.uk/">Orbiter 2016</a> space flight simulator which was proprietary at the time and it inspired me to develop a space flight simulator myself.
I prototyped some rigid body physics in C and later in <a href="https://www.gnu.org/software/guile/">GNU Guile</a> and also prototyped loading and rendering of Wavefront OBJ files.
I used GNU Guile (a Scheme implementation) because it has a good native interface and of course it has hygienic macros.
Eventually I got interested in Clojure because it has more generic multi-methods as well as fast hash maps and vectors.
I finally decided to develop the game for real in Clojure.
I have been developing a space flight simulator in Clojure for almost 5 years now.
While using Clojure I have come to appreciate the immutable values and safe parallelism using atoms, agents, and refs.</p>

<p>In the beginning I decided to work on the hard parts first, which for me were 3D rendering of a planet, an atmosphere, shadows, and volumetric clouds.
I read the <a href="https://www.informit.com/store/opengl-superbible-comprehensive-tutorial-and-reference-9780672337475">OpenGL Superbible</a> to get an understanding on what functionality OpenGL provides.
When Orbiter was eventually open sourced and released unter MIT license <a href="https://github.com/orbitersim/orbiter">here</a>, I inspected the source code and discovered that about 90% of the code is graphics-related.
So starting with the graphics problems was not a bad decision.</p>

<h2 id="software-dependencies">Software dependencies</h2>

<p>The following software is used for development.
The software libraries run on both GNU/Linux and Microsoft Windows.</p>

<ul>
  <li><a href="https://clojure.org/">Clojure</a> the programming language</li>
  <li><a href="https://www.lwjgl.org/">LWJGL</a> provides Java wrappers for various libraries
    <ul>
      <li>lwjgl-opengl for 3D graphics</li>
      <li>lwjgl-glfw for windowing and input devices</li>
      <li>lwjgl-nuklear for graphical user interfaces</li>
      <li>lwjgl-stb for image I/O and using truetype fonts</li>
      <li>lwjgl-assimp to load glTF 3D models with animation data</li>
    </ul>
  </li>
  <li><a href="https://github.com/jrouwe/JoltPhysics">Jolt Physics</a> to simulate wheeled vehicles and collisions with meshes</li>
  <li><a href="https://generateme.github.io/fastmath/">Fastmath</a> for fast matrix and vector math as well as spline interpolation</li>
  <li><a href="https://github.com/weavejester/comb">Comb</a> for templating shader code</li>
  <li><a href="https://github.com/Engelberg/instaparse">Instaparse</a> to parse NASA Planetary Constant Kernel (PCK) files</li>
  <li><a href="https://github.com/clj-commons/gloss">Gloss</a> to parse NASA Double Precision Array Files (DAF)</li>
  <li><a href="https://github.com/IGJoshua/coffi">Coffi</a> as a foreign function interface</li>
  <li><a href="https://github.com/clojure/core.memoize">core.memoize</a> for least recently used caching of function results</li>
  <li><a href="https://commons.apache.org/proper/commons-compress/">Apache Commons Compress</a> to read map tiles from tar files</li>
  <li><a href="https://github.com/metosin/malli">Malli</a> to add schemas to functions</li>
  <li><a href="https://github.com/levand/immuconf">Immuconf</a> to load the configuration file</li>
  <li><a href="https://github.com/weavejester/progrock">Progrock</a> a progress bar for long running builds</li>
  <li><a href="https://github.com/clj-commons/claypoole">Claypoole</a> to implement parallel for loops</li>
  <li><a href="https://github.com/marick/Midje">Midje</a> for test-driven development</li>
  <li><a href="https://clojure.org/guides/tools_build">tools.build</a> to build the project</li>
  <li><a href="https://github.com/clojure-goes-fast/clj-async-profiler">clj-async-profiler</a> Clojure profiler creating flame graphs</li>
  <li><a href="https://github.com/fzakaria/slf4j-timbre">slf4j-timbre</a> Java logging implementation for Clojure</li>
</ul>

<p>The <em>deps.edn</em> file contains operating system dependent LWJGL bindings.
For example on GNU/Linux the <em>deps.edn</em> file contains the following:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-opengl</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-opengl$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-glfw</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-glfw$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-nuklear</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-nuklear$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-stb</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-stb$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-assimp</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}</span><span class="w">
        </span><span class="n">org.lwjgl/lwjgl-assimp$natives-linux</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"3.3.6"</span><span class="p">}}</span><span class="w">
        </span><span class="c1">; ...</span><span class="w">
        </span><span class="p">}</span></code></pre></figure>

<p>In order to manage the different dependencies for Microsoft Windows, a separate Git branch is maintained.</p>

<h2 id="atmosphere-rendering">Atmosphere rendering</h2>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/q9aWd_14qhA" frameborder="0" allowfullscreen=""></iframe></div>

<p>For the atmosphere, <a href="https://ebruneton.github.io/precomputed_atmospheric_scattering/">Bruneton’s precomputed atmospheric scattering</a> was used.
The implementation uses a 2D transmittance table, a 2D surface scattering table, a 4D Rayleigh scattering, and a 4D Mie scattering table.
The tables are computed using several iterations of numerical integration.
Higher order functions for integration over a sphere and over a line segment were implemented in Clojure.
Integration over a ray in 3D space (using fastmath vectors) was implemented as follows for example:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">integral-ray</span><span class="w">
  </span><span class="s">"Integrate given function over a ray in 3D space"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="n">ray</span><span class="w"> </span><span class="n">N</span><span class="w"> </span><span class="no">:double</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:double</span><span class="p">]]</span><span class="w"> </span><span class="no">:some</span><span class="p">]]</span><span class="w"> </span><span class="no">:some</span><span class="p">]}</span><span class="w">
  </span><span class="p">[{</span><span class="no">::keys</span><span class="w"> </span><span class="p">[</span><span class="n">origin</span><span class="w"> </span><span class="n">direction</span><span class="p">]}</span><span class="w"> </span><span class="n">steps</span><span class="w"> </span><span class="n">distance</span><span class="w"> </span><span class="n">fun</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">stepsize</span><span class="w">      </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="n">distance</span><span class="w"> </span><span class="n">steps</span><span class="p">)</span><span class="w">
        </span><span class="n">samples</span><span class="w">       </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="mf">0.5</span><span class="w"> </span><span class="n">%</span><span class="p">)</span><span class="w"> </span><span class="n">stepsize</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">range</span><span class="w"> </span><span class="n">steps</span><span class="p">))</span><span class="w">
        </span><span class="n">interpolate</span><span class="w">   </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nf">add</span><span class="w"> </span><span class="n">origin</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="n">direction</span><span class="w"> </span><span class="n">s</span><span class="p">)))</span><span class="w">
        </span><span class="n">direction-len</span><span class="w"> </span><span class="p">(</span><span class="nf">mag</span><span class="w"> </span><span class="n">direction</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="o">#</span><span class="p">(</span><span class="nb">-&gt;</span><span class="w"> </span><span class="n">%</span><span class="w"> </span><span class="n">interpolate</span><span class="w"> </span><span class="n">fun</span><span class="w"> </span><span class="p">(</span><span class="nf">mult</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">stepsize</span><span class="w"> </span><span class="n">direction-len</span><span class="p">)))</span><span class="w"> </span><span class="n">samples</span><span class="p">))))</span></code></pre></figure>

<p>Precomputing the atmospheric tables takes several hours even though <a href="https://clojuredocs.org/clojure.core/pmap">pmap</a> was used.
When sampling the multi-dimensional functions, <em>pmap</em> was used as a top-level loop and <em>map</em> was used for interior loops.
Using <a href="https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html">java.nio.ByteBuffer</a> the floating point values were converted to a byte array and then written to disk using a <a href="https://clojuredocs.org/clojure.java.io/output-stream">clojure.java.io/output-stream</a>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">floats-&gt;bytes</span><span class="w">
  </span><span class="s">"Convert float array to byte buffer"</span><span class="w">
  </span><span class="p">[</span><span class="o">^</span><span class="n">floats</span><span class="w"> </span><span class="n">float-data</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">n</span><span class="w">           </span><span class="p">(</span><span class="nb">count</span><span class="w"> </span><span class="n">float-data</span><span class="p">)</span><span class="w">
        </span><span class="n">byte-buffer</span><span class="w"> </span><span class="p">(</span><span class="nf">.order</span><span class="w"> </span><span class="p">(</span><span class="nf">ByteBuffer/allocate</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">n</span><span class="w"> </span><span class="mi">4</span><span class="p">))</span><span class="w"> </span><span class="n">ByteOrder/LITTLE_ENDIAN</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">.put</span><span class="w"> </span><span class="p">(</span><span class="nf">.asFloatBuffer</span><span class="w"> </span><span class="n">byte-buffer</span><span class="p">)</span><span class="w"> </span><span class="n">float-data</span><span class="p">)</span><span class="w">
    </span><span class="p">(</span><span class="nf">.array</span><span class="w"> </span><span class="n">byte-buffer</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">spit-bytes</span><span class="w">
  </span><span class="s">"Write bytes to a file"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="n">non-empty-string</span><span class="w"> </span><span class="n">bytes?</span><span class="p">]</span><span class="w"> </span><span class="no">:nil</span><span class="p">]}</span><span class="w">
  </span><span class="p">[</span><span class="o">^</span><span class="n">String</span><span class="w"> </span><span class="n">file-name</span><span class="w"> </span><span class="o">^</span><span class="n">bytes</span><span class="w"> </span><span class="n">byte-data</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">with-open</span><span class="w"> </span><span class="p">[</span><span class="n">out</span><span class="w"> </span><span class="p">(</span><span class="nf">io/output-stream</span><span class="w"> </span><span class="n">file-name</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">.write</span><span class="w"> </span><span class="n">out</span><span class="w"> </span><span class="n">byte-data</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">spit-floats</span><span class="w">
  </span><span class="s">"Write floating point numbers to a file"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="n">non-empty-string</span><span class="w"> </span><span class="n">seqable?</span><span class="p">]</span><span class="w"> </span><span class="no">:nil</span><span class="p">]}</span><span class="w">
  </span><span class="p">[</span><span class="o">^</span><span class="n">String</span><span class="w"> </span><span class="n">file-name</span><span class="w"> </span><span class="o">^</span><span class="n">floats</span><span class="w"> </span><span class="n">float-data</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">spit-bytes</span><span class="w"> </span><span class="n">file-name</span><span class="w"> </span><span class="p">(</span><span class="nf">floats-&gt;bytes</span><span class="w"> </span><span class="n">float-data</span><span class="p">)))</span></code></pre></figure>

<p>When launching the game, the lookup tables get loaded and copied into OpenGL textures.
Shader functions are used to lookup and interpolate values from the tables.
When rendering the planet surface or the space craft, the atmosphere essentially gets superimposed using ray tracing.
After rendering the planet, a background quad is rendered to display the remaining part of the atmosphere above the horizon.</p>

<h2 id="templating-opengl-shaders">Templating OpenGL shaders</h2>

<p>It is possible to make programming with OpenGL shaders more flexible by using a templating library such as <em>Comb</em>.
The following shader defines multiple octaves of noise on a base noise function:</p>

<figure class="highlight"><pre><code class="language-glsl" data-lang="glsl"><span class="cp">#version 410 core
</span>
<span class="kt">float</span> <span class="o">&lt;%=</span> <span class="n">base</span><span class="o">-</span><span class="n">function</span> <span class="o">%&gt;</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">idx</span><span class="p">);</span>

<span class="kt">float</span> <span class="o">&lt;%=</span> <span class="n">method</span><span class="o">-</span><span class="n">name</span> <span class="o">%&gt;</span><span class="p">(</span><span class="kt">vec3</span> <span class="n">idx</span><span class="p">)</span>
<span class="p">{</span>
  <span class="kt">float</span> <span class="n">result</span> <span class="o">=</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p">;</span>
<span class="o">&lt;%</span> <span class="p">(</span><span class="n">doseq</span> <span class="p">[</span><span class="n">multiplier</span> <span class="n">octaves</span><span class="p">]</span> <span class="o">%&gt;</span>
  <span class="n">result</span> <span class="o">+=</span> <span class="o">&lt;%=</span> <span class="n">multiplier</span> <span class="o">%&gt;</span> <span class="o">*</span> <span class="o">&lt;%=</span> <span class="n">base</span><span class="o">-</span><span class="n">function</span> <span class="o">%&gt;</span><span class="p">(</span><span class="n">idx</span><span class="p">);</span>
  <span class="n">idx</span> <span class="o">*=</span> <span class="mi">2</span><span class="p">;</span>
<span class="o">&lt;%</span> <span class="p">)</span> <span class="o">%&gt;</span>
  <span class="k">return</span> <span class="n">result</span><span class="p">;</span>
<span class="p">}</span></code></pre></figure>

<p>One can then for example define the function <em>fbm_noise</em> using octaves of the base function <em>noise</em> as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">noise-octaves</span><span class="w">
  </span><span class="s">"Shader function to sum octaves of noise"</span><span class="w">
  </span><span class="p">(</span><span class="nf">template/fn</span><span class="w"> </span><span class="p">[</span><span class="n">method-name</span><span class="w"> </span><span class="n">base-function</span><span class="w"> </span><span class="n">octaves</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">slurp</span><span class="w"> </span><span class="s">"resources/shaders/core/noise-octaves.glsl"</span><span class="p">)))</span><span class="w">

</span><span class="c1">; ...</span><span class="w">

</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">fbm-noise-shader</span><span class="w"> </span><span class="p">(</span><span class="nf">noise-octaves</span><span class="w"> </span><span class="s">"fbm_noise"</span><span class="w"> </span><span class="s">"noise"</span><span class="w"> </span><span class="p">[</span><span class="mf">0.57</span><span class="w"> </span><span class="mf">0.28</span><span class="w"> </span><span class="mf">0.15</span><span class="p">]))</span></code></pre></figure>

<h2 id="planet-rendering">Planet rendering</h2>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/Ce3oWQflYOY" frameborder="0" allowfullscreen=""></iframe></div>

<p>To render the planet, <a href="https://visibleearth.nasa.gov/collection/1484/blue-marble">NASA Bluemarble</a> data, <a href="https://earthobservatory.nasa.gov/features/NightLights/page3.php">NASA Blackmarble</a> data, and <a href="https://www.ngdc.noaa.gov/mgg/topo/gltiles.html">NASA Elevation</a> data was used.
The images were converted to a multi resolution pyramid of map tiles.
The following functions were implemented for color map tiles and for elevation tiles:</p>

<ul>
  <li>a function to load and cache map tiles of given 2D tile index and level of detail</li>
  <li>a function to extract a pixel from a map tile</li>
  <li>a function to extract the pixel for a specific longitude and latitude</li>
</ul>

<p>The functions for extracting a pixel for given longitude and latitude then were used to generate a cube map with a quad tree of tiles for each face.
For each tile, the following files were generated:</p>

<ul>
  <li>A daytime texture</li>
  <li>A night time texture</li>
  <li>An image of 3D vectors defining a surface mesh</li>
  <li>A water mask</li>
  <li>A normal map</li>
</ul>

<p>Altogether 655350 files were generated.
Because the Steam ContentBuilder does not support a large number of files, each row of tile data was aggregated into a tar file.
The <em>Apache Commons Compress</em> library allows you to open a tar file to get a list of entries and then perform random access on the contents of the tar file.
A Clojure LRU cache was used to maintain a cache of open tar files for improved performance.</p>

<p>At run time, a future is created, which returns an updated tile tree, a list of tiles to drop, and a path list of the tiles to load into OpenGL.
When the future is realized, the main thread deletes the OpenGL textures from the drop list, and then uses the path list to get the new loaded images from the tile tree, load them into OpenGL textures, and create an updated tile tree with the new OpenGL textures added.
The following functions to manipulate quad trees were implemented to realize this:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-add</span><span class="w">
  </span><span class="s">"Add tiles to quad tree"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="no">:map</span><span class="p">]]</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">tiles</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">add-title-to-quadtree</span><span class="w"> </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="p">[</span><span class="nb">path</span><span class="w"> </span><span class="n">tile</span><span class="p">]]</span><span class="w"> </span><span class="p">(</span><span class="nf">assoc-in</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="nb">path</span><span class="w"> </span><span class="n">tile</span><span class="p">))</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="nb">vector</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">tiles</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-extract</span><span class="w">
  </span><span class="s">"Extract a list of tiles from quad tree"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]]</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mapv</span><span class="w"> </span><span class="p">(</span><span class="nb">partial</span><span class="w"> </span><span class="n">get-in</span><span class="w"> </span><span class="n">tree</span><span class="p">)</span><span class="w"> </span><span class="n">paths</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-drop</span><span class="w">
  </span><span class="s">"Drop tiles specified by path list from quad tree"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]]</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="n">dissoc-in</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="p">))</span><span class="w">

</span><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">quadtree-update</span><span class="w">
  </span><span class="s">"Update tiles with specified paths using a function with optional arguments from lists"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]</span><span class="w"> </span><span class="p">[</span><span class="no">:sequential</span><span class="w"> </span><span class="p">[</span><span class="no">:vector</span><span class="w"> </span><span class="no">:keyword</span><span class="p">]]</span><span class="w"> </span><span class="n">fn?</span><span class="w"> </span><span class="p">[</span><span class="no">:*</span><span class="w"> </span><span class="no">:any</span><span class="p">]]</span><span class="w"> </span><span class="p">[</span><span class="no">:maybe</span><span class="w"> </span><span class="no">:map</span><span class="p">]]}</span><span class="w">
  </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">fun</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">arglists</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="n">update-tile-in-quadtree</span><span class="w">
            </span><span class="p">[</span><span class="n">tree</span><span class="w"> </span><span class="p">[</span><span class="nb">path</span><span class="w"> </span><span class="o">&amp;</span><span class="w"> </span><span class="n">args</span><span class="p">]]</span><span class="w">
            </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="n">update-in</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="nb">path</span><span class="w"> </span><span class="n">fun</span><span class="w"> </span><span class="n">args</span><span class="p">))</span><span class="w"> </span><span class="n">tree</span><span class="w"> </span><span class="p">(</span><span class="nb">apply</span><span class="w"> </span><span class="nb">map</span><span class="w"> </span><span class="nb">list</span><span class="w"> </span><span class="n">paths</span><span class="w"> </span><span class="n">arglists</span><span class="p">)))</span></code></pre></figure>

<h2 id="other-topics">Other topics</h2>

<h3 id="solar-system">Solar system</h3>

<p>The astronomy code for getting the position and orientation of planets was implemented according to the <a href="https://rhodesmill.org/skyfield/">Skyfield</a> Python library.
The Python library in turn is based on the <a href="https://naif.jpl.nasa.gov/naif/index.html">SPICE</a> toolkit of the NASA JPL.
The JPL basically provides sequences of <a href="https://en.wikipedia.org/wiki/Chebyshev_polynomials">Chebyshev polynomials</a> to interpolate positions of Moon and planets as well as the orientation of the Moon as binary files.
Reference coordinate systems and orientations of other bodies are provided in text files which consist of human and machine readable sections.
The binary files were parsed using <em>Gloss</em> (see <a href="https://github.com/clj-commons/gloss/wiki/Introduction">Wiki</a> for some examples) and the text files using <em>Instaparse</em>.</p>

<h3 id="jolt-bindings">Jolt bindings</h3>

<p>The required Jolt functions for wheeled vehicle dynamics and collisions with meshes were wrapped in C functions and compiled into a shared library.
The <em>Coffi</em> Clojure library (which is a wrapper for Java’s new Foreign Function &amp; Memory API) was used to make the C functions and data types usable in Clojure.</p>

<p>For example the following code implements a call to the C function <em>add_force</em>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">defcfn</span><span class="w"> </span><span class="n">add-force</span><span class="w">
  </span><span class="s">"Apply a force in the next physics update"</span><span class="w">
  </span><span class="n">add_force</span><span class="w"> </span><span class="p">[</span><span class="no">::mem/int</span><span class="w"> </span><span class="no">::vec3</span><span class="p">]</span><span class="w"> </span><span class="no">::mem/void</span><span class="p">)</span></code></pre></figure>

<p>Here <em>::vec3</em> refers to a custom composite type defined using basic types.
The memory layout, serialisation, and deserialisation for <em>::vec3</em> are defined as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">vec3-struct</span><span class="w">
  </span><span class="p">[</span><span class="no">::mem/struct</span><span class="w">
   </span><span class="p">[[</span><span class="no">:x</span><span class="w"> </span><span class="no">::mem/double</span><span class="p">]</span><span class="w">
    </span><span class="p">[</span><span class="no">:y</span><span class="w"> </span><span class="no">::mem/double</span><span class="p">]</span><span class="w">
    </span><span class="p">[</span><span class="no">:z</span><span class="w"> </span><span class="no">::mem/double</span><span class="p">]]])</span><span class="w">


</span><span class="p">(</span><span class="k">defmethod</span><span class="w"> </span><span class="n">mem/c-layout</span><span class="w"> </span><span class="no">::vec3</span><span class="w">
  </span><span class="p">[</span><span class="n">_vec3</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mem/c-layout</span><span class="w"> </span><span class="n">vec3-struct</span><span class="p">))</span><span class="w">


</span><span class="p">(</span><span class="k">defmethod</span><span class="w"> </span><span class="n">mem/serialize-into</span><span class="w"> </span><span class="no">::vec3</span><span class="w">
  </span><span class="p">[</span><span class="n">obj</span><span class="w"> </span><span class="n">_vec3</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="n">arena</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">mem/serialize-into</span><span class="w"> </span><span class="p">{</span><span class="no">:x</span><span class="w"> </span><span class="p">(</span><span class="nf">obj</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"> </span><span class="no">:y</span><span class="w"> </span><span class="p">(</span><span class="nf">obj</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="no">:z</span><span class="w"> </span><span class="p">(</span><span class="nf">obj</span><span class="w"> </span><span class="mi">2</span><span class="p">)}</span><span class="w"> </span><span class="n">vec3-struct</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="n">arena</span><span class="p">))</span><span class="w">


</span><span class="p">(</span><span class="k">defmethod</span><span class="w"> </span><span class="n">mem/deserialize-from</span><span class="w"> </span><span class="no">::vec3</span><span class="w">
  </span><span class="p">[</span><span class="n">segment</span><span class="w"> </span><span class="n">_vec3</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">result</span><span class="w"> </span><span class="p">(</span><span class="nf">mem/deserialize-from</span><span class="w"> </span><span class="n">segment</span><span class="w"> </span><span class="n">vec3-struct</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nf">vec3</span><span class="w"> </span><span class="p">(</span><span class="no">:x</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:y</span><span class="w"> </span><span class="n">result</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:z</span><span class="w"> </span><span class="n">result</span><span class="p">))))</span></code></pre></figure>

<h3 id="performance">Performance</h3>

<p>The <em>clj-async-profiler</em> was used to create flame graphs visualising the performance of the game.
In order to get reflection warnings for Java calls without sufficient type declarations, <em>*warn-on-reflection*</em> was set to <em>true</em>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">set!</span><span class="w"> </span><span class="n">*warn-on-reflection*</span><span class="w"> </span><span class="n">true</span><span class="p">)</span></code></pre></figure>

<p>Furthermore to discover missing declarations of numerical types, <em>*unchecked-math*</em> was set to <em>:warn-on-boxed</em>.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="nf">set!</span><span class="w"> </span><span class="n">*unchecked-math*</span><span class="w"> </span><span class="no">:warn-on-boxed</span><span class="p">)</span></code></pre></figure>

<p>To reduce garbage collector pauses, the ZGC low-latency garbage collector for the JVM was used.
The following section in <em>deps.edn</em> ensures that the ZGC garbage collector is used when running the project with <em>clj -M:run</em>:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
        </span><span class="p">}</span><span class="w">
 </span><span class="no">:aliases</span><span class="w"> </span><span class="p">{</span><span class="no">:run</span><span class="w"> </span><span class="p">{</span><span class="no">:jvm-opts</span><span class="w"> </span><span class="p">[</span><span class="s">"-Xms2g"</span><span class="w"> </span><span class="s">"-Xmx4g"</span><span class="w"> </span><span class="s">"--enable-native-access=ALL-UNNAMED"</span><span class="w"> </span><span class="s">"-XX:+UseZGC"</span><span class="w">
                            </span><span class="s">"--sun-misc-unsafe-memory-access=allow"</span><span class="p">]</span><span class="w">
                 </span><span class="no">:main-opts</span><span class="w"> </span><span class="p">[</span><span class="s">"-m"</span><span class="w"> </span><span class="s">"sfsim.core"</span><span class="p">]}}}</span></code></pre></figure>

<p>The option to use ZGC is also specified in the Packr JSON file used to deploy the application.</p>

<h3 id="building-the-project">Building the project</h3>

<p>In order to build the map tiles, atmospheric lookup tables, and other data files using <em>tools.build</em>, the project source code was made available in the <em>build.clj</em> file using a <em>:local/root</em> dependency:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
        </span><span class="p">}</span><span class="w">
 </span><span class="no">:aliases</span><span class="w"> </span><span class="p">{</span><span class="c1">; ...</span><span class="w">
           </span><span class="no">:build</span><span class="w"> </span><span class="p">{</span><span class="no">:deps</span><span class="w"> </span><span class="p">{</span><span class="n">io.github.clojure/tools.build</span><span class="w"> </span><span class="p">{</span><span class="no">:mvn/version</span><span class="w"> </span><span class="s">"0.10.10"</span><span class="p">}</span><span class="w">
                          </span><span class="n">sfsim/sfsim</span><span class="w"> </span><span class="p">{</span><span class="no">:local/root</span><span class="w"> </span><span class="s">"."</span><span class="p">}}</span><span class="w">
                   </span><span class="no">:ns-default</span><span class="w"> </span><span class="n">build</span><span class="w">
                   </span><span class="no">:exec-fn</span><span class="w"> </span><span class="n">all</span><span class="w">
                   </span><span class="no">:jvm-opts</span><span class="w"> </span><span class="p">[</span><span class="s">"-Xms2g"</span><span class="w"> </span><span class="s">"-Xmx4g"</span><span class="w"> </span><span class="s">"--sun-misc-unsafe-memory-access=allow"</span><span class="p">]}}}</span></code></pre></figure>

<p>Various targets were defined to build the different components of the project.
For example the atmospheric lookup tables can be build by specifying <em>clj -T:build atmosphere-lut</em> on the command line.</p>

<p>The following section in the <em>build.clj</em> file was added to allow creating an “Uberjar” JAR file with all dependencies by specifying <em>clj -T:build uber</em> on the command-line.</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">uber</span><span class="w"> </span><span class="p">[</span><span class="n">_</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="nf">b/copy-dir</span><span class="w"> </span><span class="p">{</span><span class="no">:src-dirs</span><span class="w"> </span><span class="p">[</span><span class="s">"src/clj"</span><span class="p">]</span><span class="w">
               </span><span class="no">:target-dir</span><span class="w"> </span><span class="n">class-dir</span><span class="p">})</span><span class="w">
  </span><span class="p">(</span><span class="nf">b/compile-clj</span><span class="w"> </span><span class="p">{</span><span class="no">:basis</span><span class="w"> </span><span class="n">basis</span><span class="w">
                  </span><span class="no">:src-dirs</span><span class="w"> </span><span class="p">[</span><span class="s">"src/clj"</span><span class="p">]</span><span class="w">
                  </span><span class="no">:class-dir</span><span class="w"> </span><span class="n">class-dir</span><span class="p">})</span><span class="w">
  </span><span class="p">(</span><span class="nf">b/uber</span><span class="w"> </span><span class="p">{</span><span class="no">:class-dir</span><span class="w"> </span><span class="n">class-dir</span><span class="w">
           </span><span class="no">:uber-file</span><span class="w"> </span><span class="s">"target/sfsim.jar"</span><span class="w">
           </span><span class="no">:basis</span><span class="w"> </span><span class="n">basis</span><span class="w">
           </span><span class="no">:main</span><span class="w"> </span><span class="ss">'sfsim.core</span><span class="p">}))</span></code></pre></figure>

<p>To create a Linux executable with Packr, one can then run <em>java -jar packr-all-4.0.0.jar scripts/packr-config-linux.json</em> where the JSON file has the following content:</p>

<figure class="highlight"><pre><code class="language-json" data-lang="json"><span class="p">{</span><span class="w">
  </span><span class="nl">"platform"</span><span class="p">:</span><span class="w"> </span><span class="s2">"linux64"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"jdk"</span><span class="p">:</span><span class="w"> </span><span class="s2">"/usr/lib/jvm/jdk-24.0.2-oracle-x64"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"executable"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sfsim"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"classpath"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"target/sfsim.jar"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"mainclass"</span><span class="p">:</span><span class="w"> </span><span class="s2">"sfsim.core"</span><span class="p">,</span><span class="w">
  </span><span class="nl">"resources"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"LICENSE"</span><span class="p">,</span><span class="w"> </span><span class="s2">"libjolt.so"</span><span class="p">,</span><span class="w"> </span><span class="s2">"venturestar.glb"</span><span class="p">,</span><span class="w"> </span><span class="s2">"resources"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"vmargs"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"Xms2g"</span><span class="p">,</span><span class="w"> </span><span class="s2">"Xmx4g"</span><span class="p">,</span><span class="w"> </span><span class="s2">"XX:+UseZGC"</span><span class="p">],</span><span class="w">
  </span><span class="nl">"output"</span><span class="p">:</span><span class="w"> </span><span class="s2">"out-linux"</span><span class="w">
</span><span class="p">}</span></code></pre></figure>

<p>In order to distribute the game on Steam, three depots were created:</p>

<ul>
  <li>a data depot with the operating system independent data files</li>
  <li>a Linux depot with the Linux executable and Uberjar including LWJGL’s Linux native bindings</li>
  <li>and a Windows depot with the Windows executable and an Uberjar including LWJGL’s Windows native bindings</li>
</ul>

<p>When updating a depot, the Steam ContentBuilder command line tool creates and uploads a patch in order to preserve storage space and bandwidth.</p>

<h2 id="future-work">Future work</h2>

<p>Although the hard parts are mostly done, there are still several things to do:</p>

<ul>
  <li>control surfaces and thruster graphics</li>
  <li>launchpad and runway graphics</li>
  <li>sound effects</li>
  <li>a 3D cockpit</li>
  <li>the Moon</li>
  <li>a space station</li>
</ul>

<p>It would also be interesting to make the game modable in a safe way (maybe evaluating Clojure files in a sandboxed environment?).</p>

<h2 id="conclusion">Conclusion</h2>

<style>.embed-container { position: relative; padding-bottom: 56.25%; height: 0; overflow: hidden; max-width: 100%; } .embed-container iframe, .embed-container object, .embed-container embed { position: absolute; top: 0; left: 0; width: 100%; height: 100%; }</style>
<div class="embed-container">    <iframe title="YouTube video player" width="640" height="390" src="//www.youtube.com/embed/1PqmVLUt5_g" frameborder="0" allowfullscreen=""></iframe></div>

<p>You can find the <a href="https://github.com/wedesoft/sfsim">source code on Github</a>.
Currently there is only a playtest build, but if you want to get notified, when the game gets released, you can <a href="https://store.steampowered.com/app/3687560/sfsim/">wishlist it here</a>.</p>

<p>Anyway, let me know any comments and suggestions.</p>

<p>Enjoy!</p>

<h2 id="updates">Updates</h2>

<ul>
  <li>Submitted for discussion to Reddit <a href="https://www.reddit.com/r/Clojure/comments/1n9j5d6/developing_a_space_flight_simulator_in_clojure/">here</a></li>
  <li>See HackerNews discussion of this project <a href="https://news.ycombinator.com/item?id=45145794">here</a></li>
</ul>

<h2 id="related-blog-posts">Related blog posts</h2>

<ul>
  <li><a href="https://www.wedesoft.de/simulation/2025/06/06/flight-model-physics-venturestar/">Flight dynamics model for simulating Venturestar style spacecraft</a></li>
  <li><a href="https://www.wedesoft.de/software/2022/07/01/tdd-with-opengl/">Test Driven Development with OpenGL</a></li>
  <li><a href="https://www.wedesoft.de/software/2024/05/11/clojure-nuklear/">Implementing GUIs using Clojure and LWJGL Nuklear bindings</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/05/03/volumetric-clouds/">Procedural Volumetric Clouds</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/03/20/procedural-global-cloud-cover/">Procedural generation of global cloud cover</a></li>
  <li><a href="https://www.wedesoft.de/software/2021/09/20/reversed-z-rendering/">Reversed-Z Rendering in OpenGL</a></li>
  <li><a href="https://www.wedesoft.de/software/2023/12/25/clojure-function-schemas-with-malli/">Specifying Clojure function schemas with Malli</a></li>
  <li><a href="https://www.wedesoft.de/software/2024/07/05/clojure-instaparse/">Implement an Interpreter using Clojure Instaparse</a></li>
  <li><a href="https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics/">Orbits with Jolt Physics</a></li>
  <li><a href="https://www.wedesoft.de/simulation/2024/09/26/jolt-physics-engine/">Getting started with the Jolt Physics Engine</a></li>
  <li><a href="https://www.wedesoft.de/graphics/2023/09/29/blender-animate-bones-assimp/">Create Blender bones and animate and import with Assimp</a></li>
</ul>]]></content><author><name>Jan Wedekind</name></author><category term="software" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/sfsim.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/sfsim.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Keyestudio Smart Home</title><link href="https://www.wedesoft.de/hardware/2025/08/30/keyestudio-smarthome/" rel="alternate" type="text/html" title="Keyestudio Smart Home" /><published>2025-08-30T00:00:00+01:00</published><updated>2025-08-30T00:00:00+01:00</updated><id>https://www.wedesoft.de/hardware/2025/08/30/keyestudio-smarthome</id><content type="html" xml:base="https://www.wedesoft.de/hardware/2025/08/30/keyestudio-smarthome/"><![CDATA[<p><img src="/pics/keyestudio-smarthome.jpg" alt="Keyestudio Smarthome" /></p>

<p>A few months ago I bought a Keyestudio Smart Home, assembled it and tried to program it using the Arduino IDE.
However I kept getting the following error when trying to upload a sketch to the board.</p>

<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code> avrdude: stk500_getsync() attempt 1 of 10: not in sync: resp=0x2e
 avrdude: stk500_getsync() attempt 2 of 10: not in sync: resp=0x2e
 avrdude: stk500_getsync() attempt 3 of 10: not in sync: resp=0x2e
</code></pre></div></div>

<p>Initially I thought it was an issue with the QinHeng Electronics CH340 serial converter driver software.
After exchanging a few emails with <a href="mailto:service@keyestudio.com">keyestudio support</a> however I was pointed out that the board type of my smart home version was not “Arduino Uno”.
The box of the control board says “Keyestudio Control Board for ESP-32” and I had to <a href="https://docs.keyestudio.com/projects/KS5009/en/latest/docs/Arduino/arduino.html#add-the-esp32-environment-add-version-3-1-0">install version 3.1.3 of the esp32 board software</a> for being able to program the board.
I.e. the Keyestudio IoT Smart Home Kit for ESP32 is not to be confused with the Keyestudio Smart Home Kit for Arduino.</p>

<p>The documentation for the Keyestudio smart home using ESP-32 is <a href="https://docs.keyestudio.com/projects/KS5009/en/latest/index.html">here</a>.
Also the correct version of the <a href="https://docs.keyestudio.com/projects/KS5009/en/latest/docs/Arduino/arduino.html#resource-compression-package">smart home sketches are here</a>.
Finally you can find many sample projects in the <a href="https://www.keyestudio.com/blog/">keyestudio blog</a>.
Note that in some cases you have to adapt the io pin numbers using the smart home documentation.</p>

<p>Many thanks to Keyestudio support for helping me to get it working.</p>]]></content><author><name>Jan Wedekind</name></author><category term="hardware" /><summary type="html"><![CDATA[]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/keyestudio-smarthome.jpg" /><media:content medium="image" url="https://www.wedesoft.de/pics/keyestudio-smarthome.jpg" xmlns:media="http://search.yahoo.com/mrss/" /></entry><entry><title type="html">Orbits with Jolt Physics</title><link href="https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics/" rel="alternate" type="text/html" title="Orbits with Jolt Physics" /><published>2025-08-09T00:00:00+01:00</published><updated>2025-08-09T00:00:00+01:00</updated><id>https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics</id><content type="html" xml:base="https://www.wedesoft.de/simulation/2025/08/09/orbits-with-jolt-physics/"><![CDATA[<p>I want to simulate an orbiting spacecraft using the <a href="https://jrouwe.github.io/JoltPhysics/">Jolt Physics</a> engine (see <a href="https://wedesoft.github.io/sfsim/">sfsim homepage</a> for details).
The Jolt Physics engine solves difficult problems such as gyroscopic forces, collision detection with linear casting, and special solutions for wheeled vehicles with suspension.</p>

<p>The integration method of the Jolt Physics engine is the <a href="https://en.wikipedia.org/wiki/Semi-implicit_Euler_method">semi-implicit Euler method</a>.
The following formula shows how speed <strong>v</strong> and position <strong>x</strong> are integrated for each time step:</p>

<p>\[

\begin{align*}
\mathbf{v}_{n+1} &= \mathbf{v}_n + \Delta t \, \mathbf{a}_n\\
\mathbf{x}_{n+1} &= \mathbf{x}_n + \Delta t \, \mathbf{v}_{n+1}
\end{align*}

\]</p>

<p>The <a href="https://en.wikipedia.org/wiki/Newton%27s_law_of_universal_gravitation#Gravity_field">gravitational acceleration by a planet</a> is given by:</p>

<p>\[

\mathbf{a}_n = -\cfrac{G\,M}{|\mathbf{x}_n|^3} \, \mathbf{x}_n

\]</p>

<p>To test orbiting, one can set the initial conditions of the spacecraft to a <a href="https://en.wikipedia.org/wiki/Circular_orbit#Velocity">perfect circular orbit</a>:</p>

<p>\[

\begin{align*}
\mathbf{x}_0 &= (R, 0, 0)^\top\\
\mathbf{v}_0 &= (0, V, 0)^\top \mathrm{\ where\ } V = \sqrt{\cfrac{GM}{R}}
\end{align*}

\]</p>

<p>The orbital radius R was set to the Earth radius of 6378 km plus 408 km (the height of the ISS).
The Earth mass was assumed to be 5.9722e+24 kg.
For increased accuracy, the Jolt Physics library was compiled with the option <em>-DDOUBLE_PRECISION=ON</em>.</p>

<p>A full orbit was simulated using different values for the time step.
The following plot shows the height deviation from the initial orbital height over time.</p>

<p><img src="/pics/euler-height.png" alt="Orbits with symplectic Euler" /></p>

<p>When examining the data one can see that the integration method returns close to the initial after one orbit.
The orbital error of the Euler integration method looks like a sine wave.
Even for a small timestep of dt = 0.031 s, the maximum orbit deviation is 123.8 m.
The following plot shows that for increasing time steps, the maximum error grows linearly.</p>

<p><img src="/pics/euler-errors.png" alt="Euler orbit deviation as a function of time step" /></p>

<p>For time lapse simulation with a time step of 16 seconds, the errors will exceed 50 km.</p>

<p>A possible solution is to use Runge Kutta 4th order integration instead of symplectic Euler.
The 4th order Runge Kutta method can be implemented using a state vector consisting of position and speed:</p>

<p>\[

\mathbf{y} = (x_1, x_2, x_3, v_1, v_2, v_3)^\top

\]</p>

<p>The derivative of the state vector consists of speed and gravitational acceleration:</p>

<p>\[

\begin{align*}
\mathbf{a}(\mathbf{x}) &= -\cfrac{G\,M}{|\mathbf{x}|^3} \, \mathbf{x}\\
\mathbf{f}(t, \mathbf{y}) &= (v_1, v_2, v_3, a_1, a_2, a_3)^\top
\end{align*}

\]</p>

<p>The Runge Kutta 4th order integration method is as follows:</p>

<p>\[

\begin{align*}
\mathbf{y}_{n+1} &= \mathbf{y}_n + \cfrac{\Delta t}{6}\,(\mathbf{k}_1 + 2\,\mathbf{k}_2 + 2\,\mathbf{k}_3 + \mathbf{k}_4)\\
\mathbf{k}_1 &= \mathbf{f}(t_n, \mathbf{y}_n)\\
\mathbf{k}_2 &= \mathbf{f}(t_n + \cfrac{\Delta t}{2}, \mathbf{y}_n + \cfrac{\Delta t}{2}\,\mathbf{k}_1)\\
\mathbf{k}_3 &= \mathbf{f}(t_n + \cfrac{\Delta t}{2}, \mathbf{y}_n + \cfrac{\Delta t}{2}\,\mathbf{k}_2)\\
\mathbf{k}_4 &= \mathbf{f}(t_n + \Delta t, \mathbf{y}_n + \Delta t\,\mathbf{k}_3)
\end{align*}

\]</p>

<p>The Runge Kutta method can be implemented in Clojure as follows:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">runge-kutta</span><span class="w">
  </span><span class="s">"Runge-Kutta integration method"</span><span class="w">
  </span><span class="p">{</span><span class="no">:malli/schema</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="no">:some</span><span class="w"> </span><span class="no">:double</span><span class="w"> </span><span class="p">[</span><span class="no">:=&gt;</span><span class="w"> </span><span class="p">[</span><span class="no">:cat</span><span class="w"> </span><span class="no">:some</span><span class="w"> </span><span class="no">:double</span><span class="p">]</span><span class="w"> </span><span class="no">:some</span><span class="p">]</span><span class="w"> </span><span class="n">add-schema</span><span class="w"> </span><span class="n">scale-schema</span><span class="p">]</span><span class="w"> </span><span class="no">:some</span><span class="p">]}</span><span class="w">
  </span><span class="p">[</span><span class="n">y0</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="n">dy</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="nb">*</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">dt2</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="o">^</span><span class="nb">double</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="mf">2.0</span><span class="p">)</span><span class="w">
        </span><span class="n">k1</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="n">y0</span><span class="w">                </span><span class="mf">0.0</span><span class="p">)</span><span class="w">
        </span><span class="n">k2</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">dt2</span><span class="w"> </span><span class="n">k1</span><span class="p">))</span><span class="w"> </span><span class="n">dt2</span><span class="p">)</span><span class="w">
        </span><span class="n">k3</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">dt2</span><span class="w"> </span><span class="n">k2</span><span class="p">))</span><span class="w"> </span><span class="n">dt2</span><span class="p">)</span><span class="w">
        </span><span class="n">k4</span><span class="w">  </span><span class="p">(</span><span class="nf">dy</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">dt</span><span class="w">  </span><span class="n">k3</span><span class="p">))</span><span class="w"> </span><span class="n">dt</span><span class="p">)]</span><span class="w">
    </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">y0</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="o">^</span><span class="nb">double</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="mf">6.0</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">reduce</span><span class="w"> </span><span class="nb">+</span><span class="w"> </span><span class="p">[</span><span class="n">k1</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">k2</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">k3</span><span class="p">)</span><span class="w"> </span><span class="n">k4</span><span class="p">])))))</span></code></pre></figure>

<p>The following code can be used to test the implementation:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">+</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="n">y</span><span class="p">)))</span><span class="w">
</span><span class="p">(</span><span class="k">def</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">s</span><span class="w"> </span><span class="n">x</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="n">s</span><span class="w"> </span><span class="n">x</span><span class="p">)))</span><span class="w">

</span><span class="p">(</span><span class="nf">facts</span><span class="w"> </span><span class="s">"Runge-Kutta integration method"</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="mf">0.0</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">42.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">47.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="mf">5.0</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">52.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">dt</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">dt</span><span class="p">))</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">43.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">dt</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">2.0</span><span class="w"> </span><span class="n">dt</span><span class="p">))</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">46.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">42.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">_y</span><span class="w"> </span><span class="n">dt</span><span class="p">]</span><span class="w"> </span><span class="p">(</span><span class="nb">*</span><span class="w"> </span><span class="mf">3.0</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="n">dt</span><span class="p">))</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="mf">43.0</span><span class="w">
       </span><span class="p">(</span><span class="nf">runge-kutta</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="p">(</span><span class="k">fn</span><span class="w"> </span><span class="p">[</span><span class="n">y</span><span class="w"> </span><span class="n">_dt</span><span class="p">]</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="w"> </span><span class="n">add</span><span class="w"> </span><span class="n">scale</span><span class="p">)</span><span class="w"> </span><span class="n">=&gt;</span><span class="w"> </span><span class="p">(</span><span class="nf">roughly</span><span class="w"> </span><span class="p">(</span><span class="nf">exp</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="mi">1</span><span class="n">e-2</span><span class="p">))</span></code></pre></figure>

<p>The Jolt Physics library allows to apply impulses to the spacecraft.
The idea is to use Runge Kutta 4th order integration to get an accurate estimate of the speed and position of the spacecraft after the next time step.
One can apply an impulse before running an Euler step so that the position after the Euler step matches the Runge Kutta estimate.
A second impulse then is used after the Euler time step to also make the speed match the Runge Kutta estimate.
Given the initial state <strong>(x(n), v(n))</strong> and the desired next state <strong>(x(n+1), v(n+1))</strong> (obtained from Runge Kutta) the formulas for the two impulses are as follows:</p>

<p>\[

\begin{align*}
\Delta\mathbf{i}_{n,0} &= m \, \Delta\mathbf{v}_{n,0} = \cfrac{m}{\Delta t}\,(\mathbf{x}_{n+1} - \mathbf{x}_n - \Delta t\,\mathbf{v}_n)\\
\Delta\mathbf{i}_{n,1} &= m \, \Delta\mathbf{v}_{n,1} = m \, (\mathbf{v}_{n+1} - \mathbf{v}_n - \Delta\mathbf{v}_{n,0})
\end{align*}

\]</p>

<p>The following code shows the implementation of the matching scheme using two speed changes in Clojure:</p>

<figure class="highlight"><pre><code class="language-clojure" data-lang="clojure"><span class="p">(</span><span class="k">defn</span><span class="w"> </span><span class="n">matching-scheme</span><span class="w">
  </span><span class="s">"Use two custom acceleration values to make semi-implicit Euler result match a ground truth after the integration step"</span><span class="w">
  </span><span class="p">[</span><span class="n">y0</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="n">y1</span><span class="w"> </span><span class="n">scale</span><span class="w"> </span><span class="n">subtract</span><span class="p">]</span><span class="w">
  </span><span class="p">(</span><span class="k">let</span><span class="w"> </span><span class="p">[</span><span class="n">delta-speed0</span><span class="w"> </span><span class="p">(</span><span class="nf">scale</span><span class="w"> </span><span class="p">(</span><span class="nb">/</span><span class="w"> </span><span class="mf">1.0</span><span class="w"> </span><span class="o">^</span><span class="nb">double</span><span class="w"> </span><span class="n">dt</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="no">:position</span><span class="w"> </span><span class="n">y1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:position</span><span class="w"> </span><span class="n">y0</span><span class="p">))</span><span class="w"> </span><span class="p">(</span><span class="nf">scale</span><span class="w"> </span><span class="n">dt</span><span class="w"> </span><span class="p">(</span><span class="no">:speed</span><span class="w"> </span><span class="n">y0</span><span class="p">))))</span><span class="w">
        </span><span class="n">delta-speed1</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="nf">subtract</span><span class="w"> </span><span class="p">(</span><span class="no">:speed</span><span class="w"> </span><span class="n">y1</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="no">:speed</span><span class="w"> </span><span class="n">y0</span><span class="p">))</span><span class="w"> </span><span class="n">delta-speed0</span><span class="p">)]</span><span class="w">
    </span><span class="p">[</span><span class="n">delta-speed0</span><span class="w"> </span><span class="n">delta-speed1</span><span class="p">]))</span></code></pre></figure>

<p>The following plot shows the height deviations observed when using Runge Kutta integration.</p>

<p><img src="/pics/rk-height.png" alt="Orbits with Runge Kutta 4th order" /></p>

<p>The following plot of maximum deviation shows that the errors are much smaller.</p>

<p><img src="/pics/rk-errors.png" alt="RK orbit deviation as a function of time step" /></p>

<p>Although the accuracy of the Runge Kutta matching scheme is higher, a loss of 40 m of height per orbit is undesirable.
Inspecting the Jolt Physics source code reveals that the double-precision setting affects position vectors but is not applied to speed and impulse vectors.
To test whether double precision speed and impulse vectors would increase the accuracy, a test implementation of the semi-implicit Euler method with Runge Kutta matching scheme was used.
The following plot shows that the orbit deviations are now much smaller.</p>

<p><img src="/pics/rk-double-height.png" alt="Orbits with Runge Kutta 4th order and double precision" /></p>

<p>The updated plot of maximum deviation shows that using double precision the error for one orbit is below 1 meter for time steps up to 40 seconds.</p>

<p><img src="/pics/rk-double-errors.png" alt="RK with double precision orbit deviation as a function of time step" /></p>

<p>I am currently looking into building a modified Jolt Physics version which uses double precision for speed and impulse vectors.
I hope that I will get the Runge Kutta 4th order matching scheme to work so that I get an integrated solution for numerically accurate orbits as well as collision and vehicle simulation.</p>

<p><strong>Update:</strong></p>

<p><a href="https://www.jrouwe.nl/">Jorrit Rouwé</a> has <a href="https://github.com/jrouwe/JoltPhysics/issues/1721">informed me</a> that he currently does not want to add <a href="https://github.com/jrouwe/JoltPhysics/discussions/1638">support for double precision speed values</a>.
He also has more detailed information about <a href="https://jrouwe.github.io/JoltPhysics/#space-simulations">using Jolt Physics for space simulation</a> on his website.</p>

<p>I have managed to get a prototype working using the moving coordinate system approach.
One can perform the Runge Kutta integration using double precision coordinates and speed vectors with the Earth at the centre of the coordinate system.
The Jolt Physics integration then happens in a coordinate system which is at the initial position and moving with the initial speed of the spaceship.
The first impulse of the matching scheme is applied and then the semi-implicit Euler integration step is performed using Jolt Physics with single precision speed vectors and impulses.
Then the second impulse is applied.
Finally the position and speed of the double precision moving coordinate system are incremented using the position and speed value of the Jolt Physics body.
The position and speed of the Jolt Physics body are then reset to zero and the next iteration begins.</p>

<p>The following plot shows the height deviations observed using this approach:</p>

<p><img src="/pics/rk-moving-height.png" alt="Orbits using moving coordinate system" /></p>

<p>The maximum errors for different time steps are shown in the following plot:</p>

<p><img src="/pics/rk-moving-errors.png" alt="Maximum errors with moving coordinate system as a function of time step" /></p>]]></content><author><name>Jan Wedekind</name></author><category term="simulation" /><summary type="html"><![CDATA[I want to simulate an orbiting spacecraft using the Jolt Physics engine (see sfsim homepage for details). The Jolt Physics engine solves difficult problems such as gyroscopic forces, collision detection with linear casting, and special solutions for wheeled vehicles with suspension.]]></summary><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://www.wedesoft.de/pics/orbit.png" /><media:content medium="image" url="https://www.wedesoft.de/pics/orbit.png" xmlns:media="http://search.yahoo.com/mrss/" /></entry></feed>