Jekyll2023-06-09T18:49:58+00:00https://praeclarum.org/rsspraeclarumFrank A. Krueger's BlogI Built the World’s Largest Translated Cuneiform Corpus using AI2023-06-09T00:00:00+00:002023-06-09T00:00:00+00:00https://praeclarum.org/2023/06/09/cuneiform<p><strong>TL;DR</strong> I used a custom-trained Large Language Model (T5) to
create the world’s largest online corpus of translated cuneiform
texts. It’s called the <a href="https://aicuneiform.com">AICC (AI Cuneiform Corpus)</a> and contains
130,000 AI translated texts from the <a href="https://cdli.ucla.edu/">CDLI</a>
and <a href="http://oracc.org/">ORACC</a> projects.</p>
<h2 id="cuneiform">Cuneiform</h2>
<p>Cuneiform is the oldest known writing system. It was used in
Mesopotamia (modern day Iraq) for over 3,000 years. It was used to
write Sumerian, Akkadian, and other languages. Written on clay,
it has survived the millennia and is now being translated by
scholars around the world.</p>
<p>Sadly, we have more clay tablets than scholars.
Fortunately, we have computers.</p>
<h2 id="introducing-the-aicc">Introducing the AICC</h2>
<p>I’m proud to introduce the <a href="https://aicuneiform.com">AICC</a> - a collection of 130,000
cuneiform texts translated from ancient Sumerian and Akkadian
to English using a neural network. It is the largest collection
of translated cuneiform texts in the world.</p>
<p>This is the 2nd edition of the translated corpus I released last summer.
The 1st edition contained about 30,000 texts but this new edition
boasts 130,000 texts. The corpus is growing fast!</p>
<p>How good are the translations? Well, they’re decent. :-) I hope you’ll
go browse the site and see for yourself.</p>
<p>Judging the quality of cuneiform translations has a rich history.
Indulge me in a story.</p>
<h3 id="can-it-translate-tiglath-pileser">Can it Translate Tiglath-Pileser?</h3>
<p>In 1857 a new cylinder inscribed with cuneiform text
and the name Tiglath-Pileser was found (dated 1150 BC).
At this time, cuneiform was just being relearned and
there was a question as to how good various translation methods were.</p>
<p>The Royal Asiatic Society decided to perform an experiment
(that was later published as the book
<a href="https://books.google.com/books/about/Inscription_of_Tiglath_Pileser_I_King_of.html?id=GGI-AAAAcAAJ">Inscription of Tiglath Pilser I., King of Assyria</a>).
They would give the same inscription to three different translators
and see how well they did. The idea was that if the translations
were similar, then the current understanding of cuneiform was also good.</p>
<p>Here is a page from the book showing the beginning of two of the three translations:</p>
<p><img src="/images/2023/tiglath-pileser.jpg" alt="Tiglath-Pileser Translations" /></p>
<p>We can see that although the translations are different,
they convey the same meaning. The experiment was a success.</p>
<p>I am pleased to now add my own (well, my AI’s) translation to the mix.
Just one problem, my corpus contains <a href="https://aicuneiform.com/search?q=tigl">many objects</a>
with similar inscriptions and I’m not sure which one is this specific Tiglath-Pileser cylinder.
There’s
<a href="https://aicuneiform.com/p/p393.html#P393923">P393923</a>,
<a href="https://aicuneiform.com/p/p463.html#P463064">P463064</a> (newly translated),
<a href="https://aicuneiform.com/p/p463.html#P463510">P463510</a> (newly translated),
<a href="https://aicuneiform.com/p/p467.html#P467316">P467316</a> (newly translated),
<a href="https://aicuneiform.com/p/q005.html#Q005926">Q005926</a>,
<a href="https://aicuneiform.com/p/q006.html#Q006021">Q006021</a> and more.</p>
<p>I decided in fairness and in the spirit of the competition to translate
one that had no previous translation. I chose <a href="https://aicuneiform.com/p/p467.html#P467316">P467316</a>
as its beginning seemed to match the other translations.</p>
<h3 id="ai-translation-of-tiglath-pileser">AI Translation of Tiglath-Pileser</h3>
<div style="background-color:#fff;color:#000;padding:1em 4em;font-family:georiga serif;border-radius:0.25em">
<p style="text-align:center"><em>AI Translation.</em></p>
<p>Ashur, great lord, who makes the totality of the gods submit, who gives scepter and crown, who establishes kingship; Enlil, lord, king of all the Anunnaku gods, father of the gods, lord of the lands; Sin, wise one, lord of the crown, saqu-worthy; Magur, Shamash, judge of heaven and underworld, who carries out the slander of the enemy, who breaks up the foe; Adad, hero, conqueror of the four quarters of the lands, the four quarters; Ninurta, hero, villainous sacrificial spirit and enemy, who destroys the heart of the people; Ishtar, foremost among the gods, lady of battle;
</p>
<p>
The great gods, who make the heavens and earth a lordly place, whose utterances are a scepter and a scepter, who make kingship supreme, Tiglath-Pileser, beloved prince, your beloved, your shepherd, who by your true heart you have entrusted to me, this exalted one, you have established for the sovereignty of the land of the great Enlil, you have granted him a scepter.
</p>
</div>
<p>Continued in <a href="https://aicuneiform.com/p/p467.html#P467316">P467316</a>.</p>
<p>While stilted in places, it is a decent translation, and I deem this experiment a success!</p>
<h2 id="why-ai-translations">Why AI Translations?</h2>
<p>Existing online repositories (<a href="https://cdli.ucla.edu/">CDLI</a>, <a href="http://oracc.org/">Oracc</a>)
contain many <em>transliterations</em> of ancient cuneiform texts
(a transliteration is a rewriting of a text from one writing system
to another without changing the language), but they are very lacking
in the <em>translations</em> department.</p>
<p>While I am not a cuneiform expert, I am an expert at neural networks
and have a deep passion for languages and writing systems.
I want any person to have access to the archives of
the ancients.
A grandiose goal for sure, but also a very achievable one thanks to
modern engineering advancements.</p>
<h4 id="sumerian">Sumerian</h4>
<p>Consider Sumerian (spoken by the creators of cuneiform).
There are currently 103,075 texts published with
transliterations from cuneiform symbols to (mostly) latin letters.
But only 4,583 of these texts have publicly available translations online.
That is a mere 4% of texts available to a lay person such as myself.</p>
<table>
<thead>
<tr>
<th>Publications</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>Transliterated</td>
<td>103,075</td>
</tr>
<tr>
<td>Translated</td>
<td>4,583</td>
</tr>
<tr>
<td>Need Translations</td>
<td><strong>98,492</strong></td>
</tr>
</tbody>
</table>
<p>Given the existing transliterations, there are 98,492 works
that can be translated but have not yet been.</p>
<p>(There are more translations than these, but the others are not freely
available and are held under copyright. In other words, you need to go
by a book to read them.)</p>
<p>Things aren’t much better for Akkadian (the language spoken by the famous Sargon
and Ashurbanipal).</p>
<h4 id="akkadian">Akkadian</h4>
<table>
<thead>
<tr>
<th>Publications</th>
<th>Count</th>
</tr>
</thead>
<tbody>
<tr>
<td>Transliterated</td>
<td>31,747</td>
</tr>
<tr>
<td>Translated</td>
<td>10,069</td>
</tr>
<tr>
<td>Need Translations</td>
<td><strong>21,678</strong></td>
</tr>
</tbody>
</table>
<p>We can see that 21,678 works are all set to be translated but have
not been.</p>
<h2 id="training-a-large-language-model">Training a Large Language Model</h2>
<p>The modern advancement of large language models (LLMs) has affected and
will continue to affect nearly every human endeavor.</p>
<p>The current architecture that is heralding this new age of knowledge is the Transformer architecture. It was designed specifically to be very good at translating text from one language to another using the innovative “attention mechanism”. It’s a little funny that this network designed for translation is now broaching the realm of artificial general intelligence (AGI), but I digress.</p>
<p>Ignoring the absurdly large LLMs that are dominating the field now (GPT-4 and friends), the humble smaller transformers are still quite powerful and have made the problem of translation a somewhat trivial.</p>
<p>My favorite one of these is the T5 network from Google. While large itself, it is capable of being trained using off-the-shelf (though expensive) GPUs. If you can build a large training set, you can
train this network at home to accomplish wonders.</p>
<p>Knowing this I set about building a training
set that the network could use to learn
these ancient languages.</p>
<h3 id="building-the-dataset">Building the Dataset</h3>
<p>Thankfully there has been a push to digitize acquired artifacts and to publish their cuneiform on the web.</p>
<p>The two great projects are the <a href="https://cdli.ucla.edu/">CDLI (Cuneiform Digital Library Initiative)</a> and <a href="http://oracc.org/">Oracc</a>. I owe a large debt
to these projects.</p>
<p>As any machine learning expert will tell you, 90% of the problem is collecting a good training dataset (the other 10% is justifying the compute bill). Building the cuneiform dataset presented its own unique set of challenges.</p>
<h4 id="inconsistent-transliterations">Inconsistent Transliterations</h4>
<p>Sadly, Assyriologists took some time to settle on a consistent transliteration system. When works were first transliterated to a digital form, only ASCII characters were available and the researchers made due using funny characters like # to denote demonstratives, numbers to disambiguate symbols, and ALL CAPS whenever they were in the mood (just kidding, but the use is so random it might as well be).</p>
<p>When other character encodings became available, researchers adapted. They started to use diacritic marks to disambiguate symbols (loosely based on guessed sounds). And then HTML was invented and they went wild with special marks attempting to better capture the original writing.</p>
<p>While neural networks are powerful and can certainly handle these inconsistencies, it’s not ideal. If you want the network to properly learn the language it’s best not to distract it with also learning the histrionics of human computer interface systems.</p>
<p>A wrote a variety of cuneiform and english normalizers to help with this problem. They’re not perfect, but they do a decent job.</p>
<h4 id="paragraph-wrapping-and-unwrapping">Paragraph Wrapping and Unwrapping</h4>
<p>Cuneiform texts are usually written line by line in a column and are read
from top to bottom.</p>
<p>These lines are often short and, when translated, contain even fewer words.
If I train the network on just these lines (and, surprise, I did for the 1st edition),
the translations it produces are also short and choppy. They’re not great.</p>
<p>To work around this problem, I automatically “unwrap” lines into paragraphs
to be translated all together. This way the network can learn to translate
longer sentences and paragraphs.</p>
<p>The network, however, has its own limitations and can only translate
sentences up to 512 tokens long. To work around this problem,
I “wrap” the paragraphs into chunks of up to 512 tokens and translate
those. I then stitch the translations back together to form the final
translation.</p>
<p>This “unwrap” then “wrap” process is not perfect and can lead to
some strange translations, but it’s better than the alternative
of just translating single lines.</p>
<h3 id="training-process">Training Process</h3>
<p>I started with a pre-trained a <a href="https://huggingface.co/t5-base">T5 base model from Hugging Face</a> and fine-tuned it on my dataset.
This model has 220 million parameters and is capable of translating
sequences of up to 512 tokens.</p>
<p>I trained it on a dataset of 210,247 translation examples
for 30 epochs. It took about 48 hours on my RTX3090.</p>
<p>While starting with a pre-trained model saves me a lot of compute time,
it has drawbacks. The pre-trained model was trained to translate
from English to French or German. Ideally, I would have a model
that was pre-trained to translate <em>to</em> English.</p>
<p>Also, I used its default tokenizer which does not support all the
characters I need and performs poorly on the transliterated cuneiform.</p>
<h4 id="learning-sumerian-and-akkadian-simultaneously">Learning Sumerian and Akkadian Simultaneously</h4>
<p>Since my datasets are small in size, I decided to combine learning
Sumerian and Akkadian simultaneously. This has the benefit of
increasing the training size and exposing the network to more
cuneiform symbols. Interestingly, Akkadian often uses some Sumerian
intermixed with its own language so it’s not a bad idea to train
on both.</p>
<h4 id="bidirectional-translation">Bidirectional Translation</h4>
<p>The network was having a hard time converging on a good solution.
It would train well enough for many epochs, and then it would
fall apart.</p>
<p>I found a regularization strategy that helped a lot. I would
train it to also translate <em>from</em> English to Sumerian and Akkadian.
Doing this helped the network to always converge.
I assume this is an affect of using the pre-trained network.</p>
<p>While translating from English to Akkadian or Sumerian is not
a useful task, it is a “fun party trick” as my friend put it.</p>
<h2 id="future-work">Future Work</h2>
<p>I want to continue to improve the translations and hope to take these
steps in the future:</p>
<ol>
<li>Fine-tune the model for specific translation tasks like Akkadian to English.</li>
<li>Pre-train a new model from scratch using a better tokenizer.</li>
<li>Train a larger model like T5 large.</li>
<li>Add more training data.</li>
</ol>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you enjoyed this deep dive into neural networks and ancient languages.</p>
<p>When I started this project, I had no idea whether it would work or not.
I was delighted that it did, and I am extremely delighted
to be able to introduce the <a href="https://aicuneiform.com">AICC</a> to the world. Now amateur Assyriologists
like myself can read and read to their heart’s content.</p>
<p><strong>Side note:</strong> If you are an academic and would like to collaborate on this project,
please reach out to me by <a href="https://github.com/praeclarum/CuneiformTranslators/issues/new">filing issues on GitHub</a>. I have a million questions about cuneiform
that I would love to ask you.</p>TL;DR I used a custom-trained Large Language Model (T5) to create the world’s largest online corpus of translated cuneiform texts. It’s called the AICC (AI Cuneiform Corpus) and contains 130,000 AI translated texts from the CDLI and ORACC projects.How I Re-implemented PyTorch for WebGPU2023-05-19T00:00:00+00:002023-05-19T00:00:00+00:00https://praeclarum.org/2023/05/19/webgpu-torch<p><strong>TL;DR</strong> I’ve been working on a WebGPU optimized inference and autograd library called <a href="https://github.com/praeclarum/webgpu-torch">webgpu-torch</a> with an API that matches PyTorch. The goal is to run neural networks in the browser at speeds comparable to a Linux workstation. Many kernels have been implemented and its design is easily extensible. It’s <a href="https://www.npmjs.com/package/webgpu-torch">available on NPM now</a> and works in both the browser and Node.js!</p>
<h2 id="neural-networks-in-the-browser">Neural Networks in the Browser</h2>
<p><a href="https://github.com/praeclarum/transformers-js">Nine months ago</a>, I got Hugging Face Transformers (Large Language Models like GPT but a wee bit smaller) working in the browsers thanks to the ONNX web runtime and some painfully hand-coded tokenizers.</p>
<p>It’s quite liberating running these nets in the browser since the web is the best software distribution platform ever created. You can just send someone a link and they can run your code. No need to install anything. No need to worry about what OS they’re running. No need to worry about what hardware they have. It’s all just there.</p>
<p>The only problem is that ONNX is a wee bit, shall we say, slow.</p>
<p>Thankfully, WebGPU has arrived in browsers and we can now properly access the GPU to write optimized kernels for neural network operations. This is a huge deal. It means we can now run neural networks in the browser at speeds comparable to NVIDIA/CUDA.</p>
<p>Someone just needs to, you know, do the hard work of implementing all those operations for the GPU.</p>
<p>Well that’s what I’m very pleased to announce I’ve been working on for the past few months. I’ve been re-implementing PyTorch in TypeScript for WebGPU.</p>
<h2 id="what-is-a-pytorch">What is a PyTorch?</h2>
<p>PyTorch is a wrapper over the torch runtime (which I first used with Lua) for performing neural network operations. It’s a very popular library for doing AI work and seems to have won the arms race for now.</p>
<p>The library is broken up into parts:</p>
<ol>
<li>
<p>An optimized (for GPU) math library supporting element-wise operations, matrix multiplication, convolutions, reductions, etc. over tensors.</p>
</li>
<li>
<p>An automatic differentiation library (autograd) that is just a lot of bookkeeping to keep track of the operations performed on tensors so that gradients can be calculated.</p>
</li>
<li>
<p>A neural network library that is just a bunch of layers that can be composed together to form a neural network.</p>
</li>
</ol>
<p>Doesn’t sound so hard to re-implement right? And so I did.</p>
<h2 id="what-is-a-webgpu">What is a WebGPU?</h2>
<p>WebGPU is the new standard for accessing GPUs from the browser. It supports generic compute shaders and is designed to be a low level API that can be used to build higher level libraries. The compute shaders are able to break work up into a 3D grid and, so long as you can reformulate your code to take advantage of that 3D grid, you can benefit from dedicated hardware doing the computations.</p>
<p>This is perfect for the web since JavaScript is single-threaded and not optimized for doing heavy computation. The GPU is a perfect fit for this since it’s designed to do heavy computation in parallel.</p>
<h2 id="writing-optimized-webgpu-kernels">Writing Optimized WebGPU Kernels</h2>
<p>PyTorch is very mature now and supports a huge variety of operations. It’s also very well optimized for CUDA and CUDNN (NVIDIA’s compute libraries). So how do you go about re-implementing all of those for WebGPU?</p>
<p>Well, you start with the basics. You implement the basic operations like element-wise operations, matrix multiplication, convolutions, reductions, etc. But there is a tremendous amount of similarity between these operations.</p>
<p>For example, element-wise multiplication and addition only vary by the operator used in the inner loop. The trick is to optimize the memory layout and kernels of those operations so they are fast. They need to adapt to big and small GPUs and they need to adapt to big and small workloads.</p>
<p>This is a perfect scenario to take advantage of code generation. I wrote a code generator that takes a template and generates the optimized kernels for each operation. The code generator is written in TypeScript and generates WebGPU compute shader code. This means that the generated code can be heavily optimized for the given scenario and those optimizations can be shared between operations.</p>
<p>For example, here is how I define the <code class="language-plaintext highlighter-rouge">ReLU</code> operation (from <code class="language-plaintext highlighter-rouge">op_table.ts</code>):</p>
<div class="language-typescript highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">{</span>
<span class="nl">name</span><span class="p">:</span> <span class="dl">"</span><span class="s2">relu</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">nnName</span><span class="p">:</span> <span class="dl">"</span><span class="s2">ReLU</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">nnOp</span><span class="p">:</span> <span class="kc">true</span><span class="p">,</span>
<span class="kd">type</span><span class="p">:</span> <span class="dl">"</span><span class="s2">unary</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">forward</span><span class="p">:</span> <span class="dl">"</span><span class="s2">output = max(input, 0.0)</span><span class="dl">"</span><span class="p">,</span>
<span class="nx">backward</span><span class="p">:</span> <span class="dl">"</span><span class="s2">inputGrad = input > 0.0 ? outputGrad : 0.0</span><span class="dl">"</span><span class="p">,</span>
<span class="p">}</span>
</code></pre></div></div>
<p>In this template I define both the forward computation <code class="language-plaintext highlighter-rouge">max(input, 0.0)</code> and the backward computation <code class="language-plaintext highlighter-rouge">input > 0.0 ? outputGrad : 0.0</code>. The code generator then generates the optimized kernels for both the forward and backward passes based on the size of your GPU (the size of compute workgroups) and the shape of tensors (in addition to the memory layouts of the tensors).</p>
<p>Keeping the template short and simple gives me flexibility to optimize the kernels as needed while preserving the core logic. For example, different kernels can be emitted for contiguous memory tensors vs strided memory tensors. For operations like reductions, 1D, 2D, 3D, and xD kernels can be emitted to take advantage of the 3D workgroup grid.</p>
<p>At first I designed the template system to help me save some typing, but I quickly realized its power and now I use it for all operations.</p>
<h2 id="debugging-webgpu-kernels">Debugging WebGPU Kernels</h2>
<p>Another huge benefit came from the fact that I was generating the kernels. I could generate the kernels to not only emit WebGPU code, but also JavaScript code. The core logic gets wrapped in another function that can be called from JavaScript. This means that I can run the same code in JavaScript and WebGPU and compare the results. Even better, I can debug kernels in JavaScript and then execute them on WebGPU.</p>
<p>The JavaScript CPU kernels are terribly slow, but they’re not supposed to be fast. They instead provide a convenient playground for debugging and testing kernels.</p>
<p>This also means that my WebGPU library can also run just fine in Node.js, without WebGPU, whatever. Isn’t it great when architectural decisions keep paying off?</p>
<h2 id="testing">Testing</h2>
<p>The worst part of using a new neural network library is when it doesn’t give the exact same results as previous libraries you’ve used. One of my biggest frustrations with the WebGL ONNX backend is the fact that it gives very inaccurate results compared to PyTorch. I didn’t want that. I want full fidelity. I want to make sure all my WebGPU kernels match the results of PyTorch operations.</p>
<p>To that end, I have built a test harness that first runs code snippets in PyTorch to record results, then runs the same code snippets in my library and compares the results. If they don’t match, it throws an error.</p>
<p>This has produced a silly but fun web page to go visit. If you go to <a href="https://praeclarum.org/webgpu-torch/tests/">https://praeclarum.org/webgpu-torch/tests/</a> you will see a huge set of tests running to verify all the supported operations. It’s a great way to see what operations are supported and what the results are.</p>
<h2 id="goals">Goals</h2>
<p>I like to train imaging networks and to that end my goal is to get Stable Diffusion and similar nets running under this library. Once that’s accomplished I will focus on the many Hugging Face transformer networks. I’m hoping to get all of them running in the browser at CUDA speeds.</p>
<p>I have a set of TODOs in the README of the project. If you’re interested in helping out, please take a look!</p>TL;DR I’ve been working on a WebGPU optimized inference and autograd library called webgpu-torch with an API that matches PyTorch. The goal is to run neural networks in the browser at speeds comparable to a Linux workstation. Many kernels have been implemented and its design is easily extensible. It’s available on NPM now and works in both the browser and Node.js!Generating SVG Images with ChatGPT2023-04-03T00:00:00+00:002023-04-03T00:00:00+00:00https://praeclarum.org/2023/04/03/chatsvg<p><strong>TL;DR</strong> I used ChatGPT, with varying levels of success, to generate SVG images. With enough patience and prompting, you can get it to draw basic shapes and simple scenes about as well as a five year old can. (Which, hey, let’s face is, is good enough a lot of the time.)</p>
<h2 id="background">Background</h2>
<p>I like the idea of using AI’s to generate images. Go back in this blog and you’ll see lots of posts and casual references to imaging networks I like build and experiment with.</p>
<p>With recent advances like DALL-E, Stable Diffusion, and Midjourney, it seems like the static image generation problem has been solved. Sure, there’s plenty of room for growth and improvement, but the basic problem of generating images from text is solved.</p>
<p>But what about vector graphics?
As an app developer, I’m often in need of graphical resources. Images are neat, but they’re also so 1970s. They don’t scale well and are a terribly wasteful and inconvenient (for editing) encoding of graphical information.</p>
<p>Like everyone else, I’ve been using <a href="https://chat.openai.com/">ChatGPT</a> a lot recently, and I was curious to see if it could generate SVG code for images.
I know it’s pretty good at HTML, so I figured it might be able to do SVG too. It’s certainly not something it was designed to do, but I thought it would be fun experiment to conduct.</p>
<p>To my delight, it actually works. You’ll see below that the results are not always great or, um, err, good, but it’s still pretty cool and useful that it can do this at all.</p>
<p>As of GPT-4, I would say it’s capable of generating decent shapes and simple combinations of shapes. But I’ll let you you judge.</p>
<h2 id="experiments">Experiments</h2>
<p>The following transcripts are from my first experiments generating SVGs with ChatGPT. I was curious to see what kind of images the model could generate, and how it would respond to different inputs.</p>
<p>I performed the experiments using the ChatGPT API using the <code class="language-plaintext highlighter-rouge">gpt-4</code> model.</p>
<p>I used my app <a href="https://github.com/praeclarum/AskGPT">AskGPT</a> to run these prompts and dutifully pasted the responses into SVG files to make this post.</p>
<h4 id="please-generate-an-svg-of-a-catus-sic-out-in-the-desert-on-a-sunny-day-no-explanation">Please generate an SVG of a catus [<em>sic</em>] out in the desert on a sunny day, no explanation.</h4>
<blockquote>
<p>As an AI language model, I cannot create or directly display SVG images. However, I can help you create the SVG code, which you
could then use in any SVG viewer or compatible software/website to visualize the cactus in the desert.</p>
<p>Here’s a simple SVG code for a cactus in a desert environment. Copy and paste this code into an SVG viewer or an HTML document
to display the image.</p>
</blockquote>
<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><svg</span> <span class="na">width=</span><span class="s">"300"</span> <span class="na">height=</span><span class="s">"150"</span> <span class="na">xmlns=</span><span class="s">"http://www.w3.org/2000/svg"</span><span class="nt">></span>
<span class="c"><!-- Background color (sky) --></span>
<span class="nt"><rect</span> <span class="na">width=</span><span class="s">"300"</span> <span class="na">height=</span><span class="s">"150"</span> <span class="na">x=</span><span class="s">"0"</span> <span class="na">y=</span><span class="s">"0"</span> <span class="na">fill=</span><span class="s">"skyblue"</span> <span class="nt">/></span>
<span class="c"><!-- Sun --></span>
<span class="nt"><circle</span> <span class="na">cx=</span><span class="s">"270"</span> <span class="na">cy=</span><span class="s">"30"</span> <span class="na">r=</span><span class="s">"20"</span> <span class="na">fill=</span><span class="s">"yellow"</span> <span class="nt">/></span>
<span class="c"><!-- Ground --></span>
<span class="nt"><rect</span> <span class="na">width=</span><span class="s">"300"</span> <span class="na">height=</span><span class="s">"50"</span> <span class="na">x=</span><span class="s">"0"</span> <span class="na">y=</span><span class="s">"100"</span> <span class="na">fill=</span><span class="s">"tan"</span> <span class="nt">/></span>
<span class="c"><!-- Cactus --></span>
<span class="nt"><rect</span> <span class="na">width=</span><span class="s">"30"</span> <span class="na">height=</span><span class="s">"80"</span> <span class="na">x=</span><span class="s">"130"</span> <span class="na">y=</span><span class="s">"70"</span> <span class="na">fill=</span><span class="s">"green"</span> <span class="nt">/></span>
<span class="nt"><rect</span> <span class="na">width=</span><span class="s">"20"</span> <span class="na">height=</span><span class="s">"30"</span> <span class="na">x=</span><span class="s">"125"</span> <span class="na">y=</span><span class="s">"50"</span> <span class="na">fill=</span><span class="s">"green"</span> <span class="nt">/></span>
<span class="nt"><rect</span> <span class="na">width=</span><span class="s">"20"</span> <span class="na">height=</span><span class="s">"30"</span> <span class="na">x=</span><span class="s">"145"</span> <span class="na">y=</span><span class="s">"50"</span> <span class="na">fill=</span><span class="s">"green"</span> <span class="nt">/></span>
<span class="nt"></svg></span>
</code></pre></div></div>
<p><img src="/images/2023/chatsvg_desert1.svg" alt="Cactus in the desert" /></p>
<p><strong>SUCCESS</strong> Although the initial part of the response was discouraging (why the low confidence ChatGPT?), the model dutifully generated the SVG code for a cactus in the desert.</p>
<p>The cactus could obviously use some work, but this image is mostly what I was expecting. I know these models are sensitive to inputs, so I decied to give it some encouragement.</p>
<h4 id="ok-great-but-please-now-generate-a-much-more-detailed-image-using-gradients-and-paths">OK, great. But please now generate a much more detailed image using gradients and paths</h4>
<blockquote>
<p>Here is a more detailed SVG code for a cactus in the desert using gradients and paths for a richer visual representation. To
view the image, copy and paste the code into an SVG viewer or an HTML document.</p>
</blockquote>
<p>(I’m going to elide the SVG code from now on, but you can see it by viewing the source of the images.)</p>
<p><img src="/images/2023/chatsvg_desert2.svg" alt="Cactus in the desert 2" /></p>
<p><strong>SUCCESS</strong> OK, so it’s not going to win any rewards with that, but I applaud the effort. The cactus has a bit more shape and the horizon is more interesting.</p>
<p>It looks like the model is having a hard time getting the directions of arcs correct in the SVG code. Golly, I can relate. I’ve written several SVG libraries and still I can never get the ordering right.</p>
<p>That said, I feel like I’m failing the network by not being a good prompter. Let’s try again with stupid amounts of prompting.</p>
<h4 id="please-generate-an-svg-of-a-cactus-in-the-desert-on-a-sunny-day-please-make-it-as-detailed-as-possible-use-vibrant-colors-use-dramatic-lighting-i-want-depth-i-want-to-feel-like-im-there-i-want-something-shockingly-beautiful">Please generate an SVG of a cactus in the desert on a sunny day. Please make it as detailed as possible. Use vibrant colors. Use dramatic lighting. I want depth! I want to feel like I’m there! I want something shockingly beautiful.</h4>
<blockquote>
<p>I apologize, but as an AI text model, I cannot create images or SVG files. However, I can help you understand how you can create such
an image using a vector graphics software like Adobe Illustrator or Inkscape.</p>
<ol>
<li>Open your preferred vector graphics software and create a new document.</li>
</ol>
<p>…</p>
</blockquote>
<p><strong>FAILURE</strong> Oops, I went too far and it just started explaining to me how to use a drawing program. I guess it doesn’t like that kind of prompting.</p>
<p>Let’s change subjects and see how it does at generating icons.</p>
<h3 id="app-ui-icons">App UI Icons</h3>
<h4 id="would-you-generate-the-svg-code-for-a-mac-toolbar-icon-that-toggles-the-selected-item-preferences-pane-on-and-off">Would you generate the SVG code for a mac toolbar icon that toggles the selected item preferences pane on and off?</h4>
<blockquote>
<p>As an AI, I cannot generate an SVG file directly in this text interface, but I can provide you with the code that you can copy and
paste into an SVG file, like ‘toggle_preferences_icon.svg’.</p>
<p>Here’s a simple SVG code example for a mac toolbar icon with two gear-like symbols representing the toggling of the selected item
preferences pane:</p>
</blockquote>
<p><img src="/images/2023/chatsvg_icon1.svg" alt="Toggle preferences icon" style="width:240px;background-color:white;" /></p>
<p><strong>FAILURE</strong> Haha, OK, um so those aren’t gears. E for effort.</p>
<p>Can we just make a single gear if we concentrate really hard?</p>
<h4 id="please-generate-an-svg-code-of-a-gear-icon-please-make-it-detailed-showing-the-teeth-and-such-you-can-use-paths-if-you-want-make-your-response-as-long-as-it-needs-to-be">Please generate an SVG code of a gear icon. Please make it detailed showing the teeth and such. You can use paths if you want. Make your response as long as it needs to be.</h4>
<blockquote>
<p>Here’s an SVG code representation of a gear icon using paths:</p>
</blockquote>
<p><img src="/images/2023/chatsvg_icon2.svg" alt="Gear icon" style="width:240px;background-color:white;" /></p>
<p><strong>FAILURE</strong> I see gears aren’t its strong suit.</p>
<p>Let’s see if it can generate something more basic.</p>
<h4 id="please-generate-an-svg-code-of-a-house-icon-you-can-use-paths-if-you-want-make-your-response-as-long-as-it-needs-to-be">Please generate an SVG code of a house icon. You can use paths if you want. Make your response as long as it needs to be.</h4>
<p><img src="/images/2023/chatsvg_icon3.svg" alt="House icon" style="width:240px;background-color:white;" /></p>
<p><strong>SUCCESS?</strong> Do walls need to be straight? Does it matter if your roof has a little overhang? That’s a house. I’ll take it. Even better, I think an app with a “messy icon” aesthetic would be a welcome breath of fresh air.</p>
<p>Enough with boring toolbar icons. Let’s see if it can make a new icon for my app iCircuit.</p>
<h3 id="app-icons">App Icons</h3>
<h4 id="please-generate-the-svg-code-of-an-app-icon-for-icircuit-an-app-that-enables-you-to-draw-and-simulate-electronic-circuits-the-icon-should-have-a-nice-graident-background-and-fit-in-well-on-the-ios-home-screen-you-can-use-paths-if-you-want-make-your-response-as-long-as-it-needs-to-be">Please generate the SVG code of an app icon for iCircuit, an app that enables you to draw and simulate electronic circuits. The icon should have a nice graident background and fit in well on the iOS home screen. You can use paths if you want. Make your response as long as it needs to be.</h4>
<p><img src="/images/2023/chatsvg_appicon1.svg" alt="iCircuit icon" style="width:240px;background-color:white;" /></p>
<p><strong>FAILURE</strong> I dig that gradient (those colors are pretty close to the actual app icon), but the logo is a bit too… abstract. I can forgive abstractness, but I can’t forgive the fact that it’s not centered properly.</p>
<p>Let’s try prompting our way out of this hole.</p>
<h4 id="nice-i-like-that-gradient-but-the-inner-logo-is-too-weird-maybe-put-a-nice-looking-sine-wave-in-there-centered-nicely">Nice, I like that gradient. But the inner logo is too weird. Maybe put a nice looking sine wave in there. Centered nicely.</h4>
<p><img src="/images/2023/chatsvg_appicon2.svg" alt="iCircuit icon 2" style="width:240px;background-color:white;" /></p>
<p><strong>FAILURE</strong> The sin wave is so close! If I were generous, I would grade this one a “SUCCESS?”, but it failed to center again, and I just can’t have that.</p>
<h3 id="art">Art</h3>
<h4 id="please-generate-the-svg-code-of-a-nice-looking-abstract-art-piece-you-can-use-paths-if-you-want-make-your-response-as-long-as-it-needs-to-be">Please generate the SVG code of a nice looking abstract art piece. You can use paths if you want. Make your response as long as it needs to be.</h4>
<p><img src="/images/2023/chatsvg_art1.svg" alt="Abstract art" /></p>
<p><strong>SUCCESS</strong> I like it. It’s very “Saved by the Bell”, very 1990s.</p>
<p>That gives me an idea.</p>
<h4 id="please-generate-the-svg-code-of-a-spaceship-using-awesome-neon-colors-and-rad-gradients">Please generate the SVG code of a spaceship using awesome neon colors and rad gradients.</h4>
<p><img src="/images/2023/chatsvg_art2.svg" alt="Spaceship" /></p>
<p><strong>FAILURE</strong> Cool colors, for sure, but it’s not a spaceship.</p>
<p>OK, it’s not an artist. Let’s see if it can do simple shapes.</p>
<h4 id="please-generate-the-svg-code-of-a-red-circle-inside-of-that-circle-put-a-white-square-inside-that-put-the-red-text-this-is-a-warning-wrap-the-text-if-needed-use-some-gradients-to-make-it-look-good-an-intimidating">Please generate the SVG code of a red circle. Inside of that circle put a white square. Inside that, put the red text ‘THIS IS A WARNING’. Wrap the text if needed. Use some gradients to make it look good an intimidating.</h4>
<p><img src="/images/2023/chatsvg_shapes1.svg" alt="Warning" /></p>
<p><strong>SUCCESS</strong> The text isn’t centered. I get it, it’s hard. I’m expecting a lot out of a language model.</p>
<p>But look, it’s text! Readable text. That’s a big deal because imaging networks aren’t really known for their text prowess. For example, here are DALL-E and Stable Diffusion’s attempts at the same prompt:</p>
<p><img src="/images/2023/chatsvg_shapes1_dalle.jpg" alt="DALL-E" style="width:300px" /> <img src="/images/2023/chatsvg_shapes1_sd.png" alt="Stable Diffusion" style="width:300px" /></p>
<p>At least ChatGPT gave me exactly what I wanted (I’m not thinking about centering, I’m not thinking about centering, …). I got a red circle, a white box, and readable text. Neither of the other two networks could do/did that.</p>
<h3 id="conclusion">Conclusion</h3>
<p>My biggest takeways are:</p>
<ol>
<li>Yes, ChatGPT can generate SVG code.</li>
<li>It’s still in a very early stage and cannot handle much complexity.</li>
</ol>
<p>I was hoping for better, but for a network that was not designed to do this at all, it’s not bad. I certainly look forward to GPT-5 and 6 improving at this.</p>
<p>I keep thinking back to the last warning sign experiment.
The images generated by DALL-E and Stable Diffusion were not good. They were not even really what I wanted. ChatGPT, on the other hand, was able to generate something much closer to what I wanted.</p>
<p>I think this is a benefit of how it was trained. The engineers worked hard on the “alignment problem” (pun unintended) and it shows. It does what I expect. Old GPT-3 and friends were pure statistical generators. During their training, the only positive feedback they received was when their output sampling match language use.
ChatGPT, however, was trained with a bias towards being helpful and providing the kinds of answers people want. This was accomplished by having people hand-rank its responses.
I have a feeling that this hand-ranking, in order to aid in the human alignment problem, is going to fuel the next few years of machine learning improvements.</p>
<p>I went into this wanting to see if I could get SVGs, what I think of as a more useful format for my life as an app developer, out of ChatGPT. I got that, but I also got a lot more. I got a glimpse into the future of AI.</p>
<p>Anyway, enough armchair philosophizing. I hope you enjoyed seeing the results of my experiements, and I hope you’ll try some of your own.</p>
<h4 id="notes">Notes</h4>
<ul>
<li>
<p>I am aware there are neural nets specifically trained to generate SVG code. <a href="https://github.com/alexandre01/deepsvg">DeepSVG</a> is one example. Specialized networks give more reliable results today and will certainly be useful in the future. I wanted to experiment with ChatGPT because I am intrigued by its generalized knowledge and I wanted to see if that general knowledge produced interesting results.</p>
</li>
<li>
<p>None of this was done scientifically. ChatGPT is still a stochastic (random) model and its outputs can vary. I should have had it generate more samples for each prompt. But I didn’t. I just wanted to see roughly what it could do.</p>
</li>
</ul>TL;DR I used ChatGPT, with varying levels of success, to generate SVG images. With enough patience and prompting, you can get it to draw basic shapes and simple scenes about as well as a five year old can. (Which, hey, let’s face is, is good enough a lot of the time.)Transformers-js - Neural Networks in the Browser2022-08-25T00:00:00+00:002022-08-25T00:00:00+00:00https://praeclarum.org/2022/08/25/transformers-js<p><strong>TL;DR</strong> I wrote a javascript library that lets you run modern transformer neural networks from Hugging Face 🤗 in the browser. It works on mobile browsers, desktop browsers, pretty much everywhere. Check out <a href="https://github.com/praeclarum/transformers-js">transformers-js on GitHub</a> to see how it works and checkout out the live translation demo running on my static website: <a href="https://transformers-js.praeclarum.org">https://transformers-js.praeclarum.org</a>.</p>
<p><a href="https://transformers-js.praeclarum.org"><img src="/images/2022/transformersjs.png" alt="Screenshot of the transformer-js demo translation web site" /></a></p>
<h2 id="introduction">Introduction</h2>
<p>Currently, the best way to deploy neural networks is to pay a cloud provider to host it and pay them to run inference. The more customers you have, the more you pay. It’s an old-fashioned big-iron middle-man’s utopia.</p>
<p>I’m a big fan of running neural nets on everyday hardware. It makes sense to let customers, who already invested a lot of money and carbon, use their own hardware. It’s also a huge privacy win: attackers can’t steal your information if it’s never on the network (insert Intel joke here). It’s good economically, environmentally, and it’s good for security. Sign me up.</p>
<p>Let’s fight the big-iron trend. Let’s run neural networks in the browser!</p>
<p>Announcing <strikethrough>transformers-js</strikethrough>: a library to make running translation and other language neural nets in the browser simple.</p>
<p><strong>Update (Feb 7, 2023):</strong> I’ve re-released the library as <a href="https://github.com/praeclarum/web-transformers">web-transformers</a> with full NPM and webpack support.</p>
<h2 id="hugging-face--transformers-with-transformers-js">Hugging Face 🤗 Transformers with transformers-js</h2>
<p>Transformers are neural networks that are good at manipulating serialized symbols. Ahem, sorry. By “serialized symbols” I mean language. They do language things: Sentiment analysis, summarization, translation, transmogrification. Basically, any -ation you can think of that works with a discrete set of symbols laid out one after the other.</p>
<p>And you know these networks from their friendly household names: GPT-3, Copilot, DALL-E, Stable Diffusion. There seems no end to what they can do (see also the CNN revolution of 2014).</p>
<p><a href="https://huggingface.co">Hugging Face 🤗</a> has established itself as the “GitHub of Transformers”. They have an excellent unifying framework, great documentation, and good-ish hosting. I only say good-ish hosting here because I had a demo fail because their servers were down. Clouds…</p>
<p>In fact, it was that demo fail that got me to thinking, “why can’t I just run this thing in the browser?” That thought led me to 3 days of programming. Those 3 days produced a javascript library. And that javascript library produces some kick-ass neural translations.</p>
<p>I wrote <a href="https://github.com/praeclarum/transformers-js">transformers-js</a> to make running transformers from Hugging Face 🤗 in the browser just as easy as running them in Python land. To do this, I leverage the amazing ONNX runtime in order to run the network. ONNX offers a browser-compatible runtime using WASM compiled from the complete ONNX opset code. That’s very powerful because it means that, if you can get your net running in ONNX, you can get it running in the browser. (ONNX also offers a webgl backend that is much faster than their WASM backend. But you lose so much precision in webgl that I have yet to see a network work correctly using that engine.)</p>
<p>But running the neural network is only half the battle. Running transformers requires more software than just the neural net. You also need text tokenization software to convert your text to tokens (symbols) and you need sampling software to convert the neural net’s output probabilities back to symbols. Transformers-js takes care of all that for you.</p>
<h3 id="tokenization">Tokenization</h3>
<p>Step 1 in running a transformer is getting a working tokenizer. Each neural net is optimized to solve a problem and that means each net uses a slightly different tokenizer from each other.
I thought writing the tokenizers would be a piece of cake. I’ve written hundreds of tokenizers in my career in my pursuit of programming language nirvana, but I have never run into the kind of tokenizers that data scientists have come up with.</p>
<p><strong>Side tangent:</strong> did you know that modern tokenizers use classical AI approaches? Neither did I! For example, the T5 symbol list is redundant; you can encode the same sentence many many different ways. In order to correctly tokenize the sentence for input to T5, you have to find the optimal path through the redundant symbol list based on the a-priori probabilities of the symbols. It’s a graph problem, and those are hard. Fortunately, classic AI people loved graph problems and found solutions. Two AI winters ago, people thought graphing techniques would be the foundation of all future AIs. They were wrong, but it’s nice to see these old powerful algorithms live on.</p>
<p>Back to tokenizers. I learned all that graph theory so you don’t have to! I encoded that knowledge into code that a computer can decode to make the magic happen. Behold:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Load the tokenizer</span>
<span class="kd">const</span> <span class="nx">tokenizer</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">AutoTokenizer</span><span class="p">.</span><span class="nx">fromPretrained</span><span class="p">(</span><span class="dl">"</span><span class="s2">t5-small</span><span class="dl">"</span><span class="p">,</span> <span class="dl">"</span><span class="s2">/models</span><span class="dl">"</span><span class="p">);</span>
</code></pre></div></div>
<p>That loads a tokenizer. Currently, I only support Sentence Piece Unigram models (good enough for most nets). I hope to support Byte Pair Encoding in the future (GPT’s preferred tokenization).</p>
<p>With that tokenizer, you can convert strings into token lists:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Tokenize "Hello, world!"</span>
<span class="kd">const</span> <span class="nx">english</span> <span class="o">=</span> <span class="dl">"</span><span class="s2">Hello, world!</span><span class="dl">"</span><span class="p">;</span>
<span class="kd">const</span> <span class="nx">inputTokenIds</span> <span class="o">=</span> <span class="nx">tokenizer</span><span class="p">.</span><span class="nx">encode</span><span class="p">(</span><span class="dl">"</span><span class="s2">translate English to French: </span><span class="dl">"</span> <span class="o">+</span> <span class="nx">english</span><span class="p">);</span>
</code></pre></div></div>
<p><code class="language-plaintext highlighter-rouge">inputTokenIds</code> is a list of integers that represent the symbols in the sentence. Some words are just one symbol. While other, less common or longer words, can be more than one symbol.</p>
<p>I added a little prefix to the string (“translate English to French:”) because I’m building up to a translation demo here and the T5 network, with all its advanced capabilities, needs to be told what to do.</p>
<h3 id="generation">Generation</h3>
<p>Now that we have tokens, we can hand them off to the neural network to be run:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Translate</span>
<span class="kd">const</span> <span class="nx">outputTokenIds</span> <span class="o">=</span> <span class="k">await</span> <span class="nx">model</span><span class="p">.</span><span class="nx">generate</span><span class="p">(</span><span class="nx">inputTokenIds</span><span class="p">,</span> <span class="p">{</span><span class="na">maxLength</span><span class="p">:</span><span class="mi">50</span><span class="p">,</span><span class="na">topK</span><span class="p">:</span><span class="mi">10</span><span class="p">});</span>
</code></pre></div></div>
<p>That’s it! The code takes the input tokens, runs them through the network, and returns a new list of output tokens.</p>
<p>That little <code class="language-plaintext highlighter-rouge">generate</code> function is hiding a lot of work. Most networks generate one token at a time. That means you have to
run them over and over until you get the whole sentence. This can be terribly inefficient if you run the <em>entire</em> network
over and over. Instead, you split it into pieces and run each piece only as it is needed.</p>
<p>The <code class="language-plaintext highlighter-rouge">generate</code> method also has to sample from the neural network’s output probabilities. Networks are not into commitment, and will always output a variety of options. A sampling technique is needed to pick the right one.</p>
<p><em>Greedy</em> sampling is when you just pick the highest probability option. <em>Top-k</em> sampling is when you randomly pick from the top <code class="language-plaintext highlighter-rouge">k</code> probable options. Greedy is good for when you want the most probable option. Top-k is good for when you want to inject a bit of creativity (randomness) into the results. This library supports both. I hope to add more sampling options in the future.</p>
<p>Now that we have a list of output tokens, we can convert them back to a string:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">// Convert output tokens to a string</span>
<span class="kd">const</span> <span class="nx">french</span> <span class="o">=</span> <span class="nx">tokenizer</span><span class="p">.</span><span class="nx">decode</span><span class="p">(</span><span class="nx">outputTokenIds</span><span class="p">,</span> <span class="kc">true</span><span class="p">);</span>
<span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="nx">french</span><span class="p">);</span> <span class="c1">// "Bonjour monde!"</span>
</code></pre></div></div>
<p>The output is “Bonjour monde!” which makes sense given our input of “Hello World”.</p>
<p>That’s it! In about 5 lines of code we executed a neural translation algorithm that ran completely in the browser.</p>
<h3 id="demo">Demo</h3>
<p>I put the code above together into a little web app to demonstrate the library.</p>
<p><a href="https://transformers-js.praeclarum.org">https://transformers-js.praeclarum.org</a></p>
<p>It’s a little translation app that is able to go from English to French, German, and Romanian.</p>
<p>When you run it the first time, it will take some time to download the neural network. After that, the browser can cache it and subsequent reloads will be faster.</p>
<p>It’s hosted on <a href="https://azure.microsoft.com/en-us/services/app-service/static/">Azure Static Web Apps</a> to demonstrate that no logic is running on the server. The server merely provides the neural network data to be downloaded and run on the browser. Pretty cool, huh?</p>
<h2 id="optimizing-models-for-the-browser">Optimizing Models for the Browser</h2>
<p>Now, let’s talk about a few last details to make networks in the browser run <em>well</em>.</p>
<p>Because I’m using the WASM version of ONNX, the neural network is executed on the CPU. It’s therefore beneficial to optimize it to run there. The best optimization right now is to use <em>quantized 8-bit</em> weights. This converts what are otherwise 32-bit floats into 8-bit integers. This is done carefully to preserve as much accuracy as possible and is honestly a bit of a dark art.</p>
<p>Fortunately, there are lots of dark art practitioners and we can stand on their shoulders. The <a href="https://github.com/Ki6an/fastT5">fastT5</a> library converts T5 models from Hugging Face 🤗 to quantized 8-bit models ready to run in the browser. I wrapped that library in a script as I hope to support other model types in the future.</p>
<p>Lastly, I highly recommend running this code in a background web worker. This will prevent the browser from locking up while it’s executing. I didn’t implement that feature in the demo web site but I would say it’s worth the effort in a production app.</p>
<h2 id="conclusion">Conclusion</h2>
<p>While there will always be some big networks out there that need big servers to run them, a surprsing number of neural networks work just fine in the browser. Doing so is good for economic, environmental, and security reasons.</p>
<p><a href="https://github.com/praeclarum/transformers-js">Transformers-js</a> was written to make running language models in the browser simple. I hope you will find it enjoyable to use and I hope you will use it to make cool things.</p>TL;DR I wrote a javascript library that lets you run modern transformer neural networks from Hugging Face 🤗 in the browser. It works on mobile browsers, desktop browsers, pretty much everywhere. Check out transformers-js on GitHub to see how it works and checkout out the live translation demo running on my static website: https://transformers-js.praeclarum.org.Practical Guide to Solving Hard Problems2022-02-19T00:00:00+00:002022-02-19T00:00:00+00:00https://praeclarum.org/2022/02/19/hard-problems<p>I sometimes find myself in a position of needing to write some code
that I’m just not sure how to write. Been there have you?
Here are the steps I take when I’m stumped.
No huge revelations here, just hard-earned advice.</p>
<ol>
<li>
<p>Think hard about the problem for a few weeks before typing any code.</p>
</li>
<li>
<p>Type in a function or write a class that has the inputs and outputs you need.</p>
</li>
<li>
<p>Break the function down into multiple steps with clear objectives. You may not know how to achieve those objectives, but that’s a problem for your future self. Right now, you’re just trying to write out the high-level algorithm.</p>
</li>
<li>
<p>Create a function for each of those steps and <code class="language-plaintext highlighter-rouge">throw new NotImplementedException()</code> in each of them. Their names should be long and explanatory and there should be no question about what’s expected of them. It’s <em>really</em> OK if you don’t actually know how to write ‘em.</p>
</li>
<li>
<p>Now, go implement a few of those functions. You know they’re not <em>all</em> hard. Some may even be fun! Build up your confidence and implement the easy ones. It feels good to make progress and it lets the analytical part of your brain run in the background for a bit while you focus on nitty-gritty number types and file IO.</p>
</li>
<li>
<p>Time to tackle some of those harder functions. Go into each of those and break the problem down into steps just like you did before. You’re right, I’m gonna say it: Rinse and repeat. Keep breaking those hard problems down into steps. Turn each of those steps into a function with a clear name. Implement the easy ones. Then break the hard ones down into steps again. Do this over and over again. You’ll be surprised how much you can actually get done.</p>
</li>
<li>
<p>Pretty soon (haha) you will have an 80% complete solution with just a few pesky functions left that throw NotImplemented. Now go scour your favorite package repository, or code repository, or question and answer site, or artificial intelligence programming assistant for implementations. Chances are you’re not the first person to need this particular function or widget. Find some giants, climb on top of them, and scream “Holy shit, there are a lot of smart programmers in the world!”</p>
</li>
<li>
<p>OK, you’ve scoured the inter webs and yet you still have a few pesky NotImplemented exceptions. It’s time to check on those scientists. Enter every SEO permutation of your problem statement into arXiv. Surely others have worked on problems related to one you are trying to solve. They will most likely offer insights or perspective shifts that can help you reframe your problem into something solvable. Do that. Reframe your problem and knock out those NotImplementeds.</p>
</li>
<li>
<p>Now you’re in trouble. If you still have a few NotImplemented exceptions, and there are no giants upon which to stand nor academics obsessing over this particular field, then it’s all up to you. Think big. Think outside the box. Your career depends on it. (Just kidding, I hope.) Perhaps a bath will help you think?</p>
</li>
</ol>
<p>I think these are steps all programmers take, but sometimes it’s good to spell it out.</p>
<p>I especially value the functional decomposition. Functions are a powerful abstraction, not just for writing less code, but for thinking about problems.</p>
<p>And please don’t misinterpret my use of the word “functions” to mean only those things functional programmers like. I mean any data transformer: from lowly lambdas to state-bearing IO-processing monolith objects.</p>
<p>Thanks for reading! Now go solve those hard problems!</p>I sometimes find myself in a position of needing to write some code that I’m just not sure how to write. Been there have you? Here are the steps I take when I’m stumped. No huge revelations here, just hard-earned advice.The Technology of iCircuit 3D2021-02-08T00:00:00+00:002021-02-08T00:00:00+00:00https://praeclarum.org/2021/02/08/tech-of-icircuit3d<p>Today I’m pleased to announce the <a href="https://apps.apple.com/us/app/icircuit-3d/id1539977373">macOS release of iCircuit 3D</a>! Last week I released the iOS version to a wonderful reception and I’m happy to now be able to give all the Mac users out there the same experience. iCircuit 3D has been a work of passion over the last couple years and I thought I would take a moment to describe some of the more interesting technical aspects of it.</p>
<p>So here is a very extended colophon for the app. Maybe you can get some ideas for your next app from it!</p>
<p><a href="/images/2021/icircuit3d.jpg"><img src="/images/2021/icircuit3d.jpg" alt="iCircuit 3D showing switches and LEDs" /></a></p>
<h2 id="programming-language-and-style">Programming Language and Style</h2>
<p>iCircuit 3D is written 100% in C# using Xamarin.iOS and Xamarin.Mac. I started with C# 7 but C# 9 had already been released by the time I shipped the app. :-) The null reference checking feature of C# 8 has been especially useful in tracking down bugs.</p>
<p>The app is very object oriented. In fact, this may be the largest OOP app I’ve ever built. This is funny considering that I am quite an advocate for functional programming and more reactive style UI design these days. But react style programming of a real-time engine like this is not trivial and I found myself trailblazing a bit more than I like. Like it or hate it, OOP is well understood at this point.</p>
<p>I decided to go OOP, in some ways, to rid myself of writer’s block too. I’ve been writing OOP programs since the 1990s and I am very comfortable with the abstraction and patterns. Sometimes when you’re stuck, the best thing to do is to lean into your strengths.</p>
<h3 id="properties-ftw">Properties FTW</h3>
<p>A mental road block I often run into when starting a new app is how to handle serialization and undo buffers. I decided to solve both problems in a generic way that would require very little on-going work. The key was to base both undo and serialization on properties of objects.</p>
<p>Serialization is a simple process thanks to advanced libraries like <a href="https://www.newtonsoft.com/json">Newtonsoft.Json</a>. I’m able to serialize and restore entire object graphs (thanks to its object reference handling) and only have to be careful that I don’t serialize more than I need to. I also chose to use the BSON serialization format so that the app could handle binary data more efficiently. (Circuits can contain binary data thanks to image and board import facilities.)</p>
<p>The undo (and redo) system of the app is based on property changes. When a property changes, an undo change event is registered with the OS (Apple has <code class="language-plaintext highlighter-rouge">NSUndoManager</code>). That event captures the property’s value before and after the change. When the user undos and redos, the system just has to figure out which of those values to restore to the property.</p>
<p>All of this is achieved with property definitions such as this:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">public</span> <span class="k">class</span> <span class="nc">Resistor</span> <span class="p">:</span> <span class="n">CircuitElement</span> <span class="p">{</span>
<span class="kt">double</span> <span class="n">resistance</span> <span class="p">=</span> <span class="m">1000</span><span class="p">;</span>
<span class="p">[</span><span class="nf">Inspector</span> <span class="p">(</span><span class="n">Units</span> <span class="p">=</span> <span class="s">"Ω"</span><span class="p">,</span> <span class="n">Min</span> <span class="p">=</span> <span class="m">0</span><span class="p">,</span> <span class="n">Max</span> <span class="p">=</span> <span class="m">1</span><span class="n">e12</span><span class="p">)]</span>
<span class="k">public</span> <span class="kt">double</span> <span class="n">Resistance</span> <span class="p">{</span>
<span class="k">get</span> <span class="p">=></span> <span class="n">resistance</span><span class="p">;</span>
<span class="k">set</span> <span class="p">=></span> <span class="nf">SetUndoableProperty</span> <span class="p">(</span><span class="k">ref</span> <span class="n">resistance</span><span class="p">,</span> <span class="k">value</span><span class="p">,</span> <span class="p">()</span> <span class="p">=></span> <span class="p">{</span>
<span class="nf">InvalidateMaterial</span> <span class="p">();</span>
<span class="nf">InvalidateAnalysis</span> <span class="p">();</span>
<span class="p">});</span>
<span class="p">}</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The trick is to call <code class="language-plaintext highlighter-rouge">SetUndoableProperty</code> which is very similar to the MVVM helpers standard <code class="language-plaintext highlighter-rouge">SetProperty</code>. It checks if the value actually did change, and, if so, calls an inline change handler (this example invalidates some things) and triggers the <code class="language-plaintext highlighter-rouge">PropertyChanged</code> event. <code class="language-plaintext highlighter-rouge">SetUndoableProperty</code> takes just one extra step and registers that change with an undo manager. I found that you want to be explicit about what gets registered with the undo manager because not all properties are set by users and don’t need to be registered. That’s all that’s needed for me to get serialization, undo support, and inspector UI support (thanks to reflection).</p>
<p>If you adopt the pattern of default object constructors and properties that can change any time, you can get these cross-cutting features essentially for free. I love how much power can be put into a single property.</p>
<p>I’ve talked about this a bit more on Merge Conflict over the last couple years. (Gosh I’ve been working on this app for awhile.) Give them a listen if you’re interested in details!</p>
<iframe src="https://player.fireside.fm/v2/UDzB5o3V+Hg_UIk4b?theme=dark" width="740" height="200" frameborder="0" scrolling="no"></iframe>
<iframe src="https://player.fireside.fm/v2/UDzB5o3V+RX7Y9rNO?theme=dark" width="740" height="200" frameborder="0" scrolling="no"></iframe>
<h3 id="lots-of-multithreading">Lots of Multithreading</h3>
<p>My biggest gripe with OOP is its inability to handle shared-memory multi threading. And, honestly, the style basically guarantees your code will have race conditions. In iCircuit 3D there are 4 kind of threads that all have to interact with each other:</p>
<ol>
<li>The main UI thread is responsible for drawing the 2D UI and handling user interactions</li>
<li>The 3D render thread is responsible for drawing the environment (one for each view)</li>
<li>The electronics simulation thread, well, simulates things</li>
<li>The background threads that perform the operations that are too slow to perform on the other 3 threads</li>
</ol>
<p>In other words, there is a lot to synchronize in a mutation heavy environment.</p>
<p>I ended up using .NET’s monitor-based locking primitive <code class="language-plaintext highlighter-rouge">lock (thing) { }</code> to do fine grained locks over critical data structures. From my past experience writing multithreading-heavy apps, I knew that the lock primitive was very efficient. Not safe, but efficient. ;-)</p>
<p>Safety came from designing thread-safe interfaces to objects and being very strict with myself in how callbacks are executed. The nemesis of locking is deadlocks. Deadlocks are usually caused by holding a lock while executing unknown code (from a callback, an enumerable, or an event). I sacrificed a little performance for stability by ensuring I only held locks while executing code that I was sure was callback and recursion free. It’s hard sometimes but the benefits of having thread safe code are innumerable. Also, the good news is that deadlocks are actually easier to debug than race conditions. If the app deadlocks, pause it, find what every thread is blocking on, slap yourself on the wrist, and fix the code.</p>
<p>I’m sure there are race conditions galore in the app, but multithreading bugs just have not been an issue… usually… But it takes diligence.</p>
<h3 id="apple-only">Apple Only</h3>
<p>While C# and .NET certainly lend themselves to cross-platform code, I decided to focus primarily on Apple APIs.</p>
<p>In order to make progress on the app, I abandoned cross-platform support and focused on leveraging the magnificent Apple APIs. This was a great decision. Before I made this decision, I would have to consider how to do fancy things on 3 different platforms. Easy stuff is easy, but hard stuff (like 3D renderers) are, well, hard. Focusing on one platform made the app better because I could optimize that experience without spreading my time out across platforms.</p>
<h2 id="the-3d-renderer">The 3D Renderer</h2>
<p>The main user interface is a 3D environment powered by Apple’s SceneKit technology. SceneKit is basically a game engine built into every version of iOS and macOS. I love it because it is both feature rich and performant. I’m continually impressed by it being able to handle whatever I throw at it (within reason of course) and continually getting graphics improvements.</p>
<p>Some features I take heavy advantage of include:</p>
<h3 id="thread-safety">Thread Safety</h3>
<p>Apple was kind enough to make SceneKit thread safe. This means I can usually access it from whatever thread I want. I did notice, however, that there are some race conditions if you don’t execute your code in SceneKit’s transactions. It’s not hard, you just have to make sure to write:</p>
<div class="language-csharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">SCNTransaction</span><span class="p">.</span><span class="nf">Begin</span> <span class="p">();</span>
<span class="k">try</span> <span class="p">{</span>
<span class="c1">// Code that does SceneKit things</span>
<span class="p">}</span>
<span class="k">finally</span> <span class="p">{</span>
<span class="n">SCNTransaction</span><span class="p">.</span><span class="nf">Commit</span> <span class="p">();</span>
<span class="p">}</span>
</code></pre></div></div>
<p>whenever you want to manipulate rendered objects. With that pattern I could confidently manipulate the scene graph from whatever thread I wanted.</p>
<h3 id="physically-based-rendering">Physically Based Rendering</h3>
<p><a href="/images/2021/icircuit3d_battery.jpg"><img src="/images/2021/icircuit3d_battery.jpg" alt="iCircuit 3D rendering of a battery" /></a></p>
<p>PBR is the new hotness if real-time realistic-looking rendering. It’s actually a technology developed for movies to make visuals look less, well, plasticky and now I’m able to run it on my phone. Amazing.</p>
<p>PBR requires that you provide three textures for every object: its color, its roughness, and its metalness. You can imagine what color does. Roughness lets you control how rough or shiny an object is and metalness controls the color of light reflected off the object. The fact that you can vary these levels on a pixel-per-pixel level enables a lot of nice effects. This is most notable on AA batteries who have a diffuse body, a shiny metallic stripe, then dull metallic contacts. That’s all done using a real-time generated texture.</p>
<h3 id="led-lights">LED Lights</h3>
<p>Once you have a good lighting model, you need some good lights. I decided to make every LED a light source. This actually overwhelms SceneKit because it has a maximum of 8 light sources, but things are pretty fun up until that point. This is a short section, I just thought the LED lights were fun. :-)</p>
<h3 id="skeletal-meshing-for-wires">Skeletal Meshing for Wires</h3>
<p><a href="/images/2021/icircuit3d_wires.jpg"><img src="/images/2021/icircuit3d_wires.jpg" alt="Triangles of a wire" /></a></p>
<p>One fun feature of iCircuit 3D is that its wires arc around the environment like real wires. I am using skeletal meshing, a technology that is usually used for character movement in games, to accomplish this.</p>
<p>I use a 3D Bézier curve that starts by matching its direction to the port it’s attached to. It then travels in a direction towards the other object and then finally curves again to meet that objects’ port. It’s a lot of math to figure out (shout out to <a href="http://calca.io">Calca</a> for helping me) but really adds some pizzaz to the app. I move skeletal control points to lie on that curve and then allow SceneKit’s code to move the geometry of the wire to match that skeleton.</p>
<h3 id="environment-maps">Environment Maps</h3>
<p><a href="/images/2021/icircuit3d_env.jpg"><img src="/images/2021/icircuit3d_env.jpg" alt="A reflection of the environment" /></a></p>
<p>PBR only looks good if you give it a nice environment. A friend and I spent an evening in a craftsman’s workshop taking photography to light this app using a cheap 360 degree camera. Then Wilderness Labs’ Bryan Costinach let me take photos of his lab to add some variety to the app. To add a bit more user control I also added SceneKit’s sky generator that tracks the current time of day, and users can select their own colors.</p>
<h3 id="custom-shaders">Custom Shaders</h3>
<p>SceneKit has some beautiful light rendering, but sometimes you want to take control. The API makes this easy by being able to inject little shader snippets into each model by assigning a string to a material property. As someone who has been writing and using 3D renderers their whole career, this is an absolute delight. I love the freedom to just code up any graphics I want without mucking around with compiling those programs.</p>
<h3 id="dynamic-geometry">Dynamic Geometry</h3>
<p><a href="/images/2021/icircuit3d_cap.jpg"><img src="/images/2021/icircuit3d_cap.jpg" alt="Triangles of a capacitor" /></a></p>
<p>This isn’t a feature specific to SceneKit, but one that I take great advantage of in order to make parts parametric. I have some high-level modeling abstractions in code that allow me to use virtual lathes, virtual mills, and constructive solid geometry. This allows me to define the geometry of parts in code and change them as I desire. Think OpenSCAD or OpenJSCAD but better. ;-)</p>
<p>This is notable in electronics like the capacitor whose shape changes depending on capacity of the device. It also allows one to control the radius of the wheel independent of the width for instance.</p>
<h3 id="geometric-instancing">Geometric Instancing</h3>
<p>Like any good engine, SceneKit can efficiently render multiple instances of the same geometry in one frame, and I take advantage of this. My code does its best to share as much geometry between part instances as possible while still varying material properties. This is achieved by a per-part geometry cache that uses part parameters as keys. It was tricky to setup but is worth it to have very efficient geometry loads and a high-level overview of how much I have allocated.</p>
<h2 id="physics-simulation">Physics Simulation</h2>
<p>The last major feature of SceneKit that I use is its built-in physics engine. This engine has all the standard support for collision detection, gravity, and joints. iCircuit 3D makes use of all of these.</p>
<p>When I began the app, I focused on the interaction of picking up a part and moving it around on the work surface. I wanted to nail that touch interaction on the iPad. My thought was that if I make it fun and easy to move parts around, people will be encouraged to try building larger and larger circuits. I found that gravity was my friend. It made picking up parts and setting them down feel more real, more natural than in traditional CAD apps.</p>
<p>Once I had gravity, I had to have collision detection - two parts should never occupy the same space. This gives a sense of “thisness” to the parts, again, making them feel more real.</p>
<p>The last step was to integrate physical joints into the app. Joints are used in physics engines to glue two things together. It can either be a rigid joint (like glue) or a flexible joint like a hinge.</p>
<p>I added rigid joints to the app so you could build structures and panels. There are some physical primitives that can be made “sticky” and when another object is dragged over them, they become rigidly connected. I use these rigid joints for both breadboards and for building blocks. It’s pretty interesting what you can build with it.</p>
<p>I do not allow users to create flexible freely, but I have two elements, a DC motor and a servo motor that create implicit physical joints. Thanks to SceneKit, I can control the velocity of the joints in order to simulate those motors. It’s tricky code, and it’s admittedly a bit buggy, but it really open up what the app can do.</p>
<h2 id="electronics-simulation">Electronics Simulation</h2>
<p>iCircuit 3D uses the same robust electronics simulation engine as iCircuit. This is a pretty standard nodal electronics simulator with some notable features:</p>
<ul>
<li>
<p>It uses connectivity to only simulate parts that are wired to each other. This means you do not pay the cost of simulating extra parts in the environment. This is what enables you to liberally use Duplicate and Copy and Paste without having to worry about affecting the simulation engine.</p>
</li>
<li>
<p>It can simulate Arduinos using my custom C compiler and interpreter called, surprisingly, <a href="https://github.com/praeclarum/CLanguage">CLanguage</a>.</p>
</li>
</ul>
<p>I am able to share the simulation engine between the original iCircuit and iCircuit 3D. This is great for me because any improvements I make to the original will be automatically imported into this engine. As a solo indie developer, that’s a lot less maintenance. And it also means all users of iCircuit with benefit from improvements to both apps.</p>
<p>This code sharing also allowed me to import the entire iCircuit library of parts into iCircuit 3D. That has really increased its usefulness.</p>
<h2 id="the-end">The End</h2>
<p>I hope you enjoyed this tour of iCircuit 3D technology. If you found this interesting and think the app could interest you, I hope you’ll <a href="https://apps.apple.com/us/app/icircuit-3d/id1539977373">give iCircuit 3D a try</a>!</p>Today I’m pleased to announce the macOS release of iCircuit 3D! Last week I released the iOS version to a wonderful reception and I’m happy to now be able to give all the Mac users out there the same experience. iCircuit 3D has been a work of passion over the last couple years and I thought I would take a moment to describe some of the more interesting technical aspects of it.F# Advent - Functional Motor Control2020-12-08T00:00:00+00:002020-12-08T00:00:00+00:00https://praeclarum.org/2020/12/08/fsharp-advent-motor-control<p><strong>TL;DR</strong> I implemented a proportional-integral feedback controller to control the speed of a DC motor using a gyroscope.
I wrote it in F# using a functional programming style.
The work was easily ported to run on Wilderness Labs’ Meadow IoT hardware.</p>
<p><img src="/images/2020/motor_hardware.jpg" alt="Photograph of the constructed hardware" /></p>
<p>In this blog I would like to implement a simple but robust control theory that was taught to me in college.
This blog post is one in a series of <a href="https://sergeytihon.com/2020/10/22/f-advent-calendar-in-english-2020/">“F# Advent” posts</a>
coordinated by Sergey Tihon so I will be implementing it all
using my favorite programming language F#.</p>
<p>Embedded controls such as these are often implemented in imperative languages like C with a lot
of variable mutation.</p>
<p>I want to explore implementing a DC motor control algorithm in functional style instead of the imperative style.
I also want to do it while embracing F#’s strong type checking and units of measure support.</p>
<p>I will try to spare you the heavy math but this is definitely going to be a nerdy post. Enjoy!</p>
<h2 id="motor-control">Motor control</h2>
<p>Motors are some of the simplest but most entertaining devices out there to play with. You can use them to make cars, clocks, drones, and automatic fidget spinners.</p>
<p>The technique I’m going to describe here is a general one that can be applied to a variety of control tasks, not just motors.</p>
<p>It’s a three step process:</p>
<ol>
<li>We look at the inputs and outputs</li>
<li>We compare the output to what we want</li>
<li>We modify the input to get us closer to the output</li>
</ol>
<p>Easy peasy! That’s the general outline. There is of course a million ways to accomplish all of the above. So let’s start with the easy stuff: inputs.</p>
<h3 id="inputs">Inputs</h3>
<p>I am using a DC motor with two pins. When one pin’s voltage is higher than the other, the motor spins one way. When it’s lower, the motor spins the other way.</p>
<p>But I won’t actually be controlling the motor using voltages, instead I’m going to be using a square wave with a varying duty cycle called a PWM (pulse width modulation) to mimic different voltages.</p>
<p>By changing how often a digital pin is on and off, I can mimic analog voltages. If I have a duty cycle of 100%, then the motor will be given all the voltage my circuit can muster. If I give it a duty cycle of 50%, then it will get half that voltage. A duty cycle of 0% would mean the voltage is 0 V.</p>
<p>Let’s model the motor input. I’m going to take advantage of F#’s unit system to make sure all my math and my functions are physically correct.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[<</span><span class="nc">Measure</span><span class="p">>]</span>
<span class="k">type</span> <span class="n">percent</span>
<span class="k">type</span> <span class="nc">MotorInput</span> <span class="p">=</span>
<span class="p">{</span>
<span class="nc">ClockwiseDutyCycle</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="p">></span>
<span class="nc">CounterClockwiseDutyCycle</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="p">></span>
<span class="p">}</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">DirectionalDutyCycle</span> <span class="p">=</span>
<span class="n">this</span><span class="p">.</span><span class="nc">ClockwiseDutyCycle</span> <span class="p">-</span> <span class="n">this</span><span class="p">.</span><span class="nc">CounterClockwiseDutyCycle</span>
<span class="k">let</span> <span class="n">makeMotorInput</span> <span class="p">(</span><span class="n">cw</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span> <span class="p">(</span><span class="n">ccw</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span> <span class="p">=</span>
<span class="p">{</span>
<span class="nc">ClockwiseDutyCycle</span> <span class="p">=</span> <span class="n">max</span> <span class="p">(</span><span class="n">min</span> <span class="n">cw</span> <span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</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="n">percent</span><span class="p">></span>
<span class="nc">CounterClockwiseDutyCycle</span> <span class="p">=</span> <span class="n">max</span> <span class="p">(</span><span class="n">min</span> <span class="n">ccw</span> <span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</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="n">percent</span><span class="p">></span>
<span class="p">}</span>
</code></pre></div></div>
<p>I created a helper function <code class="language-plaintext highlighter-rouge">makeMotorInput</code> that ensures the duty cycles are in a valid range.</p>
<p>While motors are controlled with two pins, they are usually easier to reason about using positive numbers to go in one direction and negative numbers to go in the other. I’m going to create a function to implement that.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">makeDirectionalMotorInput</span> <span class="p">(</span><span class="n">directionalDutyCycle</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span> <span class="p">=</span>
<span class="k">if</span> <span class="n">directionalDutyCycle</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="n">percent</span><span class="p">></span> <span class="k">then</span>
<span class="n">makeMotorInput</span> <span class="n">directionalDutyCycle</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="p">></span>
<span class="k">else</span>
<span class="n">makeMotorInput</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="p">></span> <span class="o">(-</span><span class="n">directionalDutyCycle</span><span class="p">)</span>
<span class="p">[</span><span class="n">makeDirectionalMotorInput</span> <span class="p">(</span><span class="mi">101</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span>
<span class="n">makeDirectionalMotorInput</span> <span class="p">(</span><span class="mi">50</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span>
<span class="n">makeDirectionalMotorInput</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="n">percent</span><span class="o">>)</span>
<span class="n">makeDirectionalMotorInput</span> <span class="o">(-</span><span class="mi">101</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="o">>)]</span>
</code></pre></div></div>
<table><thead><tr><th><i>index</i></th><th>ClockwiseDutyCycle</th><th>CounterClockwiseDutyCycle</th><th>DirectionalDutyCycle</th></tr></thead><tbody><tr><td>0</td><td><div class="dni-plaintext">100</div></td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">100</div></td></tr><tr><td>1</td><td><div class="dni-plaintext">50</div></td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">50</div></td></tr><tr><td>2</td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">0</div></td></tr><tr><td>3</td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">100</div></td><td><div class="dni-plaintext">-100</div></td></tr></tbody></table>
<p>We have a lot of good code now to work with inputs, let’s look at the outputs of the motor.</p>
<h3 id="outputs">Outputs</h3>
<p>When it comes to motors we can control any of these things:</p>
<ul>
<li>Its <strong>position</strong></li>
<li>Its <strong>speed</strong></li>
<li>Its <strong>acceleration</strong></li>
</ul>
<p>But we can only control them if we can <em>measure</em> them. If we want to control the position of a motor, to control, say, a door, then we need a way to measure its position. In that case, either its angular position or the position of a part of the door.</p>
<p>You can also control its acceleration or the force it produces against an object. But as my controls professor always said, “don’t try to control forces”. So, I’ll leave that as an exercise for the reader.</p>
<p>Controlling the speed of a motor is probably the most common scenario. There are multiple sensors you can use to measure the speed: you could use the same position sensors and do some math, you can use rotary encoders, you can measure minor fluctuation is voltages produced by the motor, you can use potentiometers, … There are a lot of options.</p>
<p>I’m going to use a less common sensor to measure the rotation of the motor: a gyroscope. Gyroscopes measure angular velocity, just what I want. They’re more complicated to use than the other options, but I think they’re fun so I’m going to use them in this project. I will be using an MPU6050 that is capable of measuring linear accelerations and angular velocities on all three axes. I’ll just be monitoring the one rotational axis.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// Radians</span>
<span class="p">[<</span><span class="nc">Measure</span><span class="p">>]</span>
<span class="k">type</span> <span class="n">rpm</span>
<span class="k">type</span> <span class="nc">MotorOutput</span> <span class="p">=</span> <span class="kt">float</span><span class="p"><</span><span class="n">rpm</span><span class="p">></span>
</code></pre></div></div>
<h3 id="control-algorithm">Control Algorithm</h3>
<p>Now that we have defined our inputs and outputs we can discuss how to control the motor to achieve desired speed.</p>
<p>The standard negative feedback control loop looks like this:</p>
<ol>
<li>Read outputs: <code class="language-plaintext highlighter-rouge">let outputs = getOutputs ()</code></li>
<li>Calculate error: <code class="language-plaintext highlighter-rouge">let error = desiredOutputs - realOutputs</code></li>
<li>Calculate new inputs: <code class="language-plaintext highlighter-rouge">setInputs (lastInput + control error)</code></li>
</ol>
<p>This three-part loop is general purpose enough to solve any control problem where you’re trying to achieve a measurable goal.</p>
<p>The real magic happens in the function <code class="language-plaintext highlighter-rouge">control</code>. It is responsible for deciding what the inputs should be to the
motor based upon how far off we are from our desired speed. This kind of controller is called a <em>regulating controller</em> because it’s designed to bring whatever error it is given down to 0. It does that by increasing the input when the error gets larger and decreasing it when it’s smaller.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="nc">ControlError</span> <span class="p">=</span> <span class="kt">float</span><span class="p"><</span><span class="n">rpm</span><span class="p">></span>
</code></pre></div></div>
<p>One simple way to implement the controller is to make the input proportional to the error:</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">controlWithProportionalResponse</span> <span class="p">(</span><span class="n">proportion</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="o">/</span><span class="n">rpm</span><span class="o">>)</span> <span class="p">(</span><span class="n">error</span> <span class="p">:</span> <span class="nc">ControlError</span><span class="p">)</span> <span class="p">=</span>
<span class="n">proportion</span> <span class="p">*</span> <span class="n">error</span>
</code></pre></div></div>
<p><strong>Proportional control</strong> makes a lot of intuitive sense: if we want the motor to go faster then we should give it more voltage. If we want it to go slower, then we should give it less.</p>
<p>This kind of control certainly works, but it has some negative side effects. First, it’s tricky decide what that proportion should be. Trial and error is often used though I will discuss alternatives to trial and error later. But worse than that, it’s jittery! The control will never be happy as it always has some error and will always force the motor to jitter back and forth as it tries to meet the desired output.</p>
<p>What we need is a calming influence. Something called an integral controller.</p>
<p><strong>Integral control</strong> doesn’t act unless the error has accumulated for a bit of time. It’s slower acting than the proportional controller, but it is easier to tune and really helps the controller to relax when it’s near the desired output. The formal definition of the controller involves integrating the error. <em>Integrating</em> is just a fancy word for adding a bunch of things up. We’re going to add up the error. But we’re not going to do it forever, just the last few seconds worth of error. To do that, I’m going to create a datatype that can keep track of everything.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="nc">ErrorIntegral</span> <span class="p">=</span>
<span class="p">{</span>
<span class="nc">MaxErrors</span> <span class="p">:</span> <span class="kt">int</span>
<span class="nc">Errors</span> <span class="p">:</span> <span class="nc">ControlError</span> <span class="kt">list</span>
<span class="p">}</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">TotalError</span> <span class="p">=</span> <span class="nn">List</span><span class="p">.</span><span class="n">sum</span> <span class="n">this</span><span class="p">.</span><span class="nc">Errors</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">NumErrors</span> <span class="p">=</span> <span class="n">this</span><span class="p">.</span><span class="nn">Errors</span><span class="p">.</span><span class="nc">Length</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">LastError</span> <span class="p">=</span> <span class="k">match</span> <span class="n">this</span><span class="p">.</span><span class="nc">Errors</span> <span class="k">with</span> <span class="n">x</span> <span class="p">::</span> <span class="p">_</span> <span class="p">-></span> <span class="n">x</span> <span class="p">|</span> <span class="p">_</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="n">rpm</span><span class="p">></span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">Add</span> <span class="n">error</span> <span class="p">=</span>
<span class="p">{</span> <span class="n">this</span> <span class="k">with</span>
<span class="nc">Errors</span> <span class="p">=</span>
<span class="k">let</span> <span class="n">n</span> <span class="p">=</span> <span class="n">this</span><span class="p">.</span><span class="nn">Errors</span><span class="p">.</span><span class="nc">Length</span>
<span class="k">if</span> <span class="n">n</span> <span class="p"><</span> <span class="n">this</span><span class="p">.</span><span class="nc">MaxErrors</span> <span class="k">then</span>
<span class="n">error</span> <span class="p">::</span> <span class="n">this</span><span class="p">.</span><span class="nc">Errors</span>
<span class="k">else</span>
<span class="n">error</span> <span class="p">::</span> <span class="p">(</span><span class="n">this</span><span class="p">.</span><span class="nn">Errors</span><span class="p">.[</span><span class="o">..(</span><span class="n">n</span><span class="p">-</span><span class="mi">2</span><span class="o">)])</span>
<span class="p">}</span>
<span class="k">let</span> <span class="n">makeErrorIntegral</span> <span class="p">(</span><span class="n">maxErrors</span> <span class="p">:</span> <span class="kt">int</span><span class="p">)</span> <span class="p">=</span>
<span class="p">{</span>
<span class="nc">MaxErrors</span> <span class="p">=</span> <span class="n">maxErrors</span>
<span class="nc">Errors</span> <span class="p">=</span> <span class="bp">[]</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Let’s test it out by creating one that can only hold 2 errors.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">ei</span> <span class="p">=</span> <span class="n">makeErrorIntegral</span> <span class="mi">2</span>
<span class="n">ei</span>
</code></pre></div></div>
<table><thead><tr><th>MaxErrors</th><th>Errors</th><th>TotalError</th><th>NumErrors</th><th>LastError</th></tr></thead><tbody><tr><td><div class="dni-plaintext">2</div></td><td><div class="dni-plaintext">[ ]</div></td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">0</div></td><td><div class="dni-plaintext">0</div></td></tr></tbody></table>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">ei3</span> <span class="p">=</span> <span class="n">ei</span><span class="p">.</span><span class="nc">Add</span><span class="p">(</span><span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="o">>).</span><span class="nc">Add</span><span class="p">(</span><span class="mi">200</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="o">>).</span><span class="nc">Add</span><span class="p">(</span><span class="mi">300</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="o">>)</span>
<span class="n">ei3</span>
</code></pre></div></div>
<table><thead><tr><th>MaxErrors</th><th>Errors</th><th>TotalError</th><th>NumErrors</th><th>LastError</th></tr></thead><tbody><tr><td><div class="dni-plaintext">2</div></td><td><div class="dni-plaintext">[ 300, 200 ]</div></td><td><div class="dni-plaintext">500</div></td><td><div class="dni-plaintext">2</div></td><td><div class="dni-plaintext">300</div></td></tr></tbody></table>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">ei3</span><span class="p">.</span><span class="nc">LastError</span>
</code></pre></div></div>
<div class="dni-plaintext">300</div>
<p>We can see that it discarded the first error when the third error arrived.</p>
<p>Now that we have an integrator, we can write the integral control function:</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">controlWithIntegralResponse</span> <span class="p">(</span><span class="n">proportion</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">percent</span><span class="o">/</span><span class="n">rpm</span><span class="o">>)</span>
<span class="p">(</span><span class="n">errorIntegral</span> <span class="p">:</span> <span class="nc">ErrorIntegral</span><span class="p">)</span> <span class="p">=</span>
<span class="n">proportion</span> <span class="p">*</span> <span class="n">errorIntegral</span><span class="p">.</span><span class="nc">TotalError</span>
</code></pre></div></div>
<p>This integral controller still has a <code class="language-plaintext highlighter-rouge">proportion</code> parameter because we still want to be able to tune how aggressive or passive the controller is.</p>
<p>We can combine these two controllers to get the best of both worlds. This proportional-integral controller is called a PI controller in control theory.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">control</span> <span class="p">(</span><span class="n">kp</span> <span class="p">:</span> <span class="kt">float</span><span class="o"><_>)</span> <span class="p">(</span><span class="n">ki</span> <span class="p">:</span> <span class="kt">float</span><span class="o"><_>)</span> <span class="p">(</span><span class="n">errors</span> <span class="p">:</span> <span class="nc">ErrorIntegral</span><span class="p">)</span> <span class="p">=</span>
<span class="k">let</span> <span class="n">p</span> <span class="p">=</span> <span class="n">controlWithProportionalResponse</span> <span class="n">kp</span> <span class="n">errors</span><span class="p">.</span><span class="nc">LastError</span>
<span class="k">let</span> <span class="n">i</span> <span class="p">=</span> <span class="n">controlWithIntegralResponse</span> <span class="n">ki</span> <span class="n">errors</span>
<span class="n">p</span> <span class="o">+</span> <span class="n">i</span>
</code></pre></div></div>
<h3 id="hardware-abstraction">Hardware Abstraction</h3>
<p>In order to test this controller and implement in hardware, I will define a hardware abstraction layer in the form of an interface.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="nc">IHardware</span> <span class="p">=</span>
<span class="k">abstract</span> <span class="nc">GetOutput</span> <span class="p">:</span> <span class="kt">unit</span> <span class="p">-></span> <span class="nc">MotorOutput</span>
<span class="k">abstract</span> <span class="nc">SetInput</span> <span class="p">:</span> <span class="nc">MotorInput</span> <span class="p">-></span> <span class="kt">unit</span>
</code></pre></div></div>
<p>This is the only mutable object in the code and it’s mutable because real hardware is mutable.
Once you have performed an action on it, it’s impossible to undo that action.</p>
<p>Let’s also define some test hardware so we can validate our algorithms.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="nc">TestHardware</span> <span class="p">(</span><span class="n">initialOutput</span> <span class="p">:</span> <span class="nc">MotorOutput</span><span class="p">)</span> <span class="p">=</span>
<span class="k">let</span> <span class="k">mutable</span> <span class="n">output</span> <span class="p">=</span> <span class="n">initialOutput</span>
<span class="k">let</span> <span class="k">mutable</span> <span class="n">input</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="n">percent</span><span class="p">></span>
<span class="k">interface</span> <span class="nc">IHardware</span> <span class="k">with</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">GetOutput</span> <span class="bp">()</span> <span class="p">=</span>
<span class="n">output</span> <span class="p"><-</span> <span class="p">(</span><span class="n">output</span> <span class="o">+</span> <span class="n">input</span> <span class="p">*</span> <span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="o">/</span><span class="n">percent</span><span class="o">>)</span> <span class="p">*</span> <span class="mi">0</span><span class="p">.</span><span class="mi">5</span>
<span class="n">printfn</span> <span class="s2">"Test Speed: %.3f RPM"</span> <span class="p">(</span><span class="kt">float</span> <span class="n">output</span><span class="p">)</span>
<span class="n">output</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">SetInput</span> <span class="n">newInput</span> <span class="p">=</span>
<span class="n">input</span> <span class="p"><-</span> <span class="n">newInput</span><span class="p">.</span><span class="nc">DirectionalDutyCycle</span>
</code></pre></div></div>
<p>With that simple abstraction, we can implement the control loop.</p>
<h3 id="function-control-loop">Function Control Loop</h3>
<p>I am going to implement the control loop as a classic fold operation over an infinite sequence of time.</p>
<p>This architecture keeps the control loop nice and functional and makes it very easy to test different states.</p>
<p>In order to use fold, you need three things:</p>
<ol>
<li>Sequence - I will use time</li>
<li>State - I will track the last input and the error integral</li>
<li>Transformer - This is the control algorithm</li>
</ol>
<p>Let’s start by defining the control loop:</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c1">/// Seconds</span>
<span class="p">[<</span><span class="nc">Measure</span><span class="p">>]</span>
<span class="k">type</span> <span class="n">s</span>
<span class="k">type</span> <span class="nc">ControlState</span> <span class="p">=</span> <span class="nc">MotorInput</span> <span class="p">*</span> <span class="nc">ErrorIntegral</span>
<span class="k">let</span> <span class="n">controlLoop</span> <span class="p">(</span><span class="n">hardware</span> <span class="p">:</span> <span class="nc">IHardware</span><span class="p">)</span> <span class="p">(</span><span class="n">kp</span> <span class="p">:</span> <span class="kt">float</span><span class="o"><_>)</span> <span class="p">(</span><span class="n">ki</span> <span class="p">:</span> <span class="kt">float</span><span class="o"><_>)</span>
<span class="p">(</span><span class="n">desiredOutput</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">-></span> <span class="nc">MotorOutput</span><span class="p">)</span>
<span class="o">((</span><span class="n">lastInput</span><span class="p">,</span> <span class="n">errors</span><span class="p">)</span> <span class="p">:</span> <span class="nc">ControlState</span><span class="p">)</span>
<span class="o">((</span><span class="n">t</span><span class="p">,</span> <span class="n">output</span><span class="p">)</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">*</span> <span class="nc">MotorOutput</span><span class="p">)</span> <span class="p">=</span>
<span class="k">let</span> <span class="n">error</span> <span class="p">=</span> <span class="n">desiredOutput</span> <span class="n">t</span> <span class="p">-</span> <span class="n">output</span>
<span class="k">let</span> <span class="n">newErrors</span> <span class="p">=</span> <span class="n">errors</span><span class="p">.</span><span class="nc">Add</span> <span class="n">error</span>
<span class="k">let</span> <span class="n">newInputDutyCycle</span> <span class="p">=</span> <span class="n">lastInput</span><span class="p">.</span><span class="nc">DirectionalDutyCycle</span> <span class="o">+</span> <span class="n">control</span> <span class="n">kp</span> <span class="n">ki</span> <span class="n">newErrors</span>
<span class="k">let</span> <span class="n">newInput</span> <span class="p">=</span> <span class="n">makeDirectionalMotorInput</span> <span class="n">newInputDutyCycle</span>
<span class="n">hardware</span><span class="p">.</span><span class="nc">SetInput</span> <span class="n">newInput</span>
<span class="n">newInput</span><span class="p">,</span> <span class="n">newErrors</span>
</code></pre></div></div>
<p>For the sequence I am going to create a general purpose sampler that calls a function to read some data over fixed time intervals. It even blocks the thread for those intervals to keep things in real time.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">sampler</span> <span class="p">(</span><span class="n">dt</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="o">>)</span> <span class="p">(</span><span class="n">sample</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">-></span> <span class="k">'</span><span class="n">a</span><span class="p">)</span> <span class="p">=</span>
<span class="n">seq</span> <span class="p">{</span>
<span class="k">let</span> <span class="n">sw</span> <span class="p">=</span> <span class="nn">System</span><span class="p">.</span><span class="nn">Diagnostics</span><span class="p">.</span><span class="nc">Stopwatch</span> <span class="bp">()</span>
<span class="n">sw</span><span class="p">.</span><span class="nc">Start</span> <span class="bp">()</span>
<span class="k">while</span> <span class="bp">true</span> <span class="k">do</span>
<span class="k">yield</span> <span class="n">sample</span> <span class="p">(</span><span class="n">sw</span><span class="p">.</span><span class="nn">Elapsed</span><span class="p">.</span><span class="nc">TotalSeconds</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="n">s</span><span class="o">>)</span>
<span class="nn">System</span><span class="p">.</span><span class="nn">Threading</span><span class="p">.</span><span class="nn">Thread</span><span class="p">.</span><span class="nc">Sleep</span> <span class="p">(</span><span class="nn">TimeSpan</span><span class="p">.</span><span class="nc">FromSeconds</span> <span class="p">(</span><span class="kt">float</span> <span class="n">dt</span><span class="o">))</span>
<span class="p">}</span>
<span class="k">let</span> <span class="n">time</span> <span class="p">=</span> <span class="n">sampler</span> <span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">(</span><span class="k">fun</span> <span class="n">t</span> <span class="p">-></span> <span class="n">t</span><span class="p">)</span>
<span class="n">time</span> <span class="p">|></span> <span class="nn">Seq</span><span class="p">.</span><span class="n">take</span> <span class="mi">3</span>
</code></pre></div></div>
<table><thead><tr><th><i>index</i></th><th>value</th></tr></thead><tbody><tr><td>0</td><td><div class="dni-plaintext">3E-07</div></td></tr><tr><td>1</td><td><div class="dni-plaintext">0.1036857</div></td></tr><tr><td>2</td><td><div class="dni-plaintext">0.2037907</div></td></tr></tbody></table>
<p>We can use that sampler to sample the outputs of the motor.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">type</span> <span class="nc">MotorSampler</span> <span class="p">=</span> <span class="p">(</span><span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">*</span> <span class="nc">MotorOutput</span><span class="p">)</span> <span class="n">seq</span>
<span class="k">let</span> <span class="n">motorSampler</span> <span class="n">dt</span> <span class="p">(</span><span class="n">hardware</span> <span class="p">:</span> <span class="nc">IHardware</span><span class="p">)</span> <span class="p">:</span> <span class="nc">MotorSampler</span> <span class="p">=</span>
<span class="n">sampler</span> <span class="n">dt</span> <span class="p">(</span><span class="k">fun</span> <span class="n">t</span> <span class="p">-></span> <span class="n">t</span><span class="p">,</span> <span class="n">hardware</span><span class="p">.</span><span class="nc">GetOutput</span> <span class="bp">()</span><span class="p">)</span>
</code></pre></div></div>
<p>Let’s test it out.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">testOutputs</span> <span class="p">=</span> <span class="n">motorSampler</span> <span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">(</span><span class="k">new</span> <span class="nc">TestHardware</span> <span class="p">(</span><span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="o">>))</span>
<span class="n">testOutputs</span> <span class="p">|></span> <span class="nn">Seq</span><span class="p">.</span><span class="n">take</span> <span class="mi">3</span>
</code></pre></div></div>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Test Speed: 50.000 RPM
Test Speed: 25.000 RPM
Test Speed: 12.500 RPM
</code></pre></div></div>
<table><thead><tr><th><i>index</i></th><th>Item1</th><th>Item2</th></tr></thead><tbody><tr><td>0</td><td><div class="dni-plaintext">3E-07</div></td><td><div class="dni-plaintext">50</div></td></tr><tr><td>1</td><td><div class="dni-plaintext">0.1064288</div></td><td><div class="dni-plaintext">25</div></td></tr><tr><td>2</td><td><div class="dni-plaintext">0.2124844</div></td><td><div class="dni-plaintext">12.5</div></td></tr></tbody></table>
<p>That’s working great! Now let’s put it all together.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">controlMotor</span> <span class="p">(</span><span class="n">maxIterations</span> <span class="p">:</span> <span class="kt">int</span><span class="p">)</span> <span class="p">(</span><span class="n">integralIterations</span> <span class="p">:</span> <span class="kt">int</span><span class="p">)</span>
<span class="p">(</span><span class="n">hardware</span> <span class="p">:</span> <span class="nc">IHardware</span><span class="p">)</span> <span class="p">(</span><span class="n">kp</span> <span class="p">:</span> <span class="kt">float</span><span class="o"><_>)</span> <span class="p">(</span><span class="n">ki</span> <span class="p">:</span> <span class="kt">float</span><span class="o"><_>)</span>
<span class="p">(</span><span class="n">desiredOutput</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="p">-></span> <span class="nc">MotorOutput</span><span class="p">)</span> <span class="p">=</span>
<span class="k">let</span> <span class="n">loop</span> <span class="p">=</span> <span class="n">controlLoop</span> <span class="n">hardware</span> <span class="n">kp</span> <span class="n">ki</span> <span class="n">desiredOutput</span>
<span class="k">let</span> <span class="n">initialState</span> <span class="p">=</span> <span class="n">makeDirectionalMotorInput</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="o">>,</span>
<span class="n">makeErrorIntegral</span> <span class="n">integralIterations</span>
<span class="k">let</span> <span class="n">motorOutputs</span> <span class="p">=</span> <span class="n">motorSampler</span> <span class="mi">0</span><span class="p">.</span><span class="mi">1</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="n">hardware</span>
<span class="n">motorOutputs</span>
<span class="p">|></span> <span class="nn">Seq</span><span class="p">.</span><span class="n">take</span> <span class="n">maxIterations</span>
<span class="p">|></span> <span class="nn">Seq</span><span class="p">.</span><span class="n">fold</span> <span class="n">loop</span> <span class="n">initialState</span>
<span class="p">|></span> <span class="n">ignore</span>
</code></pre></div></div>
<p>We can now run a test to see if we can control the test hardware. Let’s try to get it up to <code class="language-plaintext highlighter-rouge">100.0<rpm></code>.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">let</span> <span class="n">testGoals</span> <span class="p">(</span><span class="n">t</span> <span class="p">:</span> <span class="kt">float</span><span class="p"><</span><span class="n">s</span><span class="o">>)</span> <span class="p">=</span> <span class="k">if</span> <span class="n">t</span> <span class="p"><</span> <span class="mi">1</span><span class="p">.</span><span class="mi">5</span><span class="p"><</span><span class="n">s</span><span class="p">></span> <span class="k">then</span> <span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="p">></span> <span class="k">else</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="p">></span>
<span class="n">controlMotor</span> <span class="mi">30</span> <span class="mi">3</span> <span class="p">(</span><span class="k">new</span> <span class="nc">TestHardware</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="n">rpm</span><span class="o">>))</span> <span class="mi">0</span><span class="p">.</span><span class="mi">002</span><span class="o"><_></span> <span class="mi">0</span><span class="p">.</span><span class="mi">0002</span><span class="o"><_></span> <span class="n">testGoals</span>
</code></pre></div></div>
<p><img src="/images/2020/motor_graph.png" alt="Graph of controller reaching desired RPM" /></p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>Test Speed: 0.000 RPM
Test Speed: 11.000 RPM
Test Speed: 27.290 RPM
Test Speed: 45.323 RPM
Test Speed: 61.971 RPM
Test Speed: 75.752 RPM
Test Speed: 86.237 RPM
Test Speed: 93.616 RPM
Test Speed: 98.388 RPM
Test Speed: 101.153 RPM
Test Speed: 102.488 RPM
Test Speed: 102.887 RPM
Test Speed: 102.732 RPM
Test Speed: 102.301 RPM
Test Speed: 101.776 RPM
Test Speed: 101.267 RPM
Test Speed: 89.833 RPM
Test Speed: 73.204 RPM
Test Speed: 54.926 RPM
Test Speed: 38.115 RPM
Test Speed: 24.235 RPM
Test Speed: 13.699 RPM
Test Speed: 6.301 RPM
Test Speed: 1.529 RPM
Test Speed: -1.225 RPM
Test Speed: -2.546 RPM
Test Speed: -2.929 RPM
Test Speed: -2.761 RPM
Test Speed: -2.318 RPM
Test Speed: -1.785 RPM
</code></pre></div></div>
<p>That worked! You can see that the controller even overshot the mark and then had to backtrack.</p>
<p>You can play with the <code class="language-plaintext highlighter-rouge">kp</code> and <code class="language-plaintext highlighter-rouge">ki</code> values to see how different values affect how quickly the controller speeds up the motor.</p>
<h2 id="real-hardware">Real Hardware</h2>
<p>With the abstract interface <code class="language-plaintext highlighter-rouge">IHardware</code>, it’s very easy to run this code on real hardware.</p>
<p>To demonstrate this, I’m going to code against the <a href="https://store.wildernesslabs.co/collections/frontpage/products/meadow-f7">Meadow F7 Board from Wilderness Labs</a>. This board runs .NET code including F# so is a perfect digital controller for this application.</p>
<p>The board uses the Meadow API that has abstractions for all sorts of hardware.</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">open</span> <span class="nc">Meadow</span>
<span class="k">open</span> <span class="nn">Meadow</span><span class="p">.</span><span class="nc">Devices</span>
<span class="k">type</span> <span class="nc">MotorMeadowApp</span><span class="bp">()</span> <span class="p">=</span>
<span class="k">inherit</span> <span class="nc">App</span><span class="p"><</span><span class="nc">F7Micro</span><span class="p">,</span> <span class="nc">MotorMeadowApp</span><span class="o">>()</span>
<span class="k">let</span> <span class="n">device</span> <span class="p">=</span> <span class="nn">MotorMeadowApp</span><span class="p">.</span><span class="nc">Device</span>
<span class="k">let</span> <span class="n">i2c</span> <span class="p">=</span> <span class="n">device</span><span class="p">.</span><span class="nc">CreateI2cBus</span><span class="bp">()</span>
<span class="k">let</span> <span class="n">mpu</span> <span class="p">=</span> <span class="k">new</span> <span class="nn">Meadow</span><span class="p">.</span><span class="nn">Foundation</span><span class="p">.</span><span class="nn">Sensors</span><span class="p">.</span><span class="nn">Motion</span><span class="p">.</span><span class="nc">Mpu6050</span><span class="p">(</span><span class="n">i2c</span><span class="p">)</span>
<span class="k">do</span>
<span class="n">mpu</span><span class="p">.</span><span class="nc">Wake</span><span class="bp">()</span>
<span class="n">mpu</span><span class="p">.</span><span class="nc">StartUpdating</span> <span class="p">(</span><span class="mi">1000</span> <span class="o">/</span> <span class="mi">50</span><span class="p">)</span>
<span class="k">let</span> <span class="n">motorCwPwm</span> <span class="p">=</span> <span class="n">device</span><span class="p">.</span><span class="nc">CreatePwmPort</span><span class="p">(</span><span class="n">device</span><span class="p">.</span><span class="nn">Pins</span><span class="p">.</span><span class="nc">D09</span><span class="p">,</span> <span class="mi">200</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="mi">1</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">)</span>
<span class="k">let</span> <span class="n">motorCcwPwm</span> <span class="p">=</span> <span class="n">device</span><span class="p">.</span><span class="nc">CreatePwmPort</span><span class="p">(</span><span class="n">device</span><span class="p">.</span><span class="nn">Pins</span><span class="p">.</span><span class="nc">D10</span><span class="p">,</span> <span class="mi">200</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">,</span> <span class="mi">0</span><span class="p">.</span><span class="mi">0</span><span class="n">f</span><span class="p">)</span>
<span class="k">do</span>
<span class="n">motorCwPwm</span><span class="p">.</span><span class="nc">Start</span><span class="bp">()</span>
<span class="n">motorCcwPwm</span><span class="p">.</span><span class="nc">Start</span><span class="bp">()</span>
<span class="k">interface</span> <span class="nc">IHardware</span> <span class="k">with</span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">GetOutput</span> <span class="bp">()</span> <span class="p">=</span>
<span class="p">(</span><span class="kt">float</span> <span class="n">mpu</span><span class="p">.</span><span class="nc">YGyroscopicAcceleration</span><span class="p">)</span> <span class="o">/</span> <span class="mi">360</span><span class="p">.</span><span class="mi">0</span> <span class="p">*</span> <span class="mi">60</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">rpm</span><span class="p">></span>
<span class="k">member</span> <span class="n">this</span><span class="p">.</span><span class="nc">SetInput</span> <span class="n">newInput</span> <span class="p">=</span>
<span class="n">motorCwPwm</span><span class="p">.</span><span class="nc">DutyCycle</span> <span class="p"><-</span>
<span class="n">float32</span> <span class="p">(</span><span class="n">newInput</span><span class="p">.</span><span class="nc">ClockwiseDutyCycle</span> <span class="o">/</span> <span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span>
<span class="n">motorCcwPwm</span><span class="p">.</span><span class="nc">DutyCycle</span> <span class="p"><-</span>
<span class="n">float32</span> <span class="p">(</span><span class="n">newInput</span><span class="p">.</span><span class="nc">CounterClockwiseDutyCycle</span> <span class="o">/</span> <span class="mi">100</span><span class="p">.</span><span class="mi">0</span><span class="p"><</span><span class="n">percent</span><span class="o">>)</span>
</code></pre></div></div>
<p>In just 20 lines of code I was able to implement the <code class="language-plaintext highlighter-rouge">IHardware</code> interface. For details, I hope you’ll check out the <a href="http://developer.wildernesslabs.co/Meadow/Getting_Started/">Meadow Documentation</a>.</p>
<p>Now it’s a matter of executing the control loop:</p>
<div class="language-fsharp highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="p">[<</span><span class="nc">EntryPoint</span><span class="p">>]</span>
<span class="k">let</span> <span class="n">main</span> <span class="n">argv</span> <span class="p">=</span>
<span class="n">printfn</span> <span class="s2">"Motor Control from F#!"</span>
<span class="k">let</span> <span class="n">app</span> <span class="p">=</span> <span class="k">new</span> <span class="nc">MotorMeadowApp</span><span class="bp">()</span>
<span class="n">controlMotor</span> <span class="kt">int</span><span class="p">.</span><span class="nc">MaxValue</span> <span class="mi">3</span> <span class="n">app</span> <span class="mi">0</span><span class="p">.</span><span class="mi">002</span><span class="o"><_></span> <span class="mi">0</span><span class="p">.</span><span class="mi">0002</span><span class="o"><_></span> <span class="n">testGoals</span>
<span class="mi">0</span>
</code></pre></div></div>
<p>For details, I hope you’ll check out the <a href="http://developer.wildernesslabs.co/Meadow/Getting_Started/">Meadow Documentation</a>.</p>
<h2 id="conclusion">Conclusion</h2>
<p>I hope you enjoyed this functional look into control theory using my favorite language F#.</p>
<p>We were able to build a strongly typed code base to control the speed of a motor with minimal mutation. I hope this code demonstrates some of the benefits functional programming can bring to a space that is generally dominated by procedural programming.</p>
<p>This was my also my first F# Advent post. I hope you enjoyed it and are staying safe and healthy!</p>TL;DR I implemented a proportional-integral feedback controller to control the speed of a DC motor using a gyroscope. I wrote it in F# using a functional programming style. The work was easily ported to run on Wilderness Labs’ Meadow IoT hardware.Mac Catalyst Support for Xamarin.iOS2020-11-10T00:00:00+00:002020-11-10T00:00:00+00:00https://praeclarum.org/2020/11/10/catalyst<p><strong>TL;DR</strong> I have written a tool that will convert your Xamarin.iOS app
(including Xamarin.Forms and MAUI) to a Mac app using <a href="https://developer.apple.com/mac-catalyst/">Mac Catalyst</a>.
This makes creating a macOS app and selling it on the App Store easier
than ever.
If you <a href="https://github.com/sponsors/praeclarum">sponsor me on GitHub</a>,
<a href="https://twitch.tv/FrankKrueger">subscribe to me on Twitch</a>,
or <a href="https://www.patreon.com/praeclarum">support me through Patreon</a>,
I will send you a link to try a beta version of it.</p>
<p>A lot of what I’m about to discuss here was discussed on
<a href="https://www.mergeconflict.fm/225">Episode 225 of Merge Conflict</a>.
Have a listen!</p>
<iframe src="https://player.fireside.fm/v2/UDzB5o3V+fDOgxFlZ?theme=dark" width="740" height="200" frameborder="0" scrolling="no"></iframe>
<h3 id="twitch">Twitch</h3>
<p>I also have been live streaming this work on <a href="https://twitch.tv/FrankKrueger">Twitch</a>. You can see me
<a href="https://www.twitch.tv/videos/788934700">getting it to work with Xamarin.Forms</a>.</p>
<p><a href="https://www.twitch.tv/videos/788934700"><img src="/images/2020/catalyst.jpg" alt="Screenshot of me on Twitch running a Xamarin.Forms app on Mac Catalyst" /></a></p>
<h2 id="fragmented-brain">Fragmented Brain</h2>
<p>Oh how time changes everything. When I first started programming,
I learned to write apps in Visual Basic on Windows
and thought it to be a wonderful canvas upon which build some cool stuff.
I thought I would spend the rest of my life learning its ins and outs,
mastering its UI, building my own UIs, and being happy.</p>
<p>But time changes everything. I abandoned my beloved VB, to learn
MFC and fell in love with that. Then I learned Delphi
and thought, finally, the one tool to rule them all.
But I was wrong. The web came. All of a sudden I was breaking
my UIs into servers and clients like it was the 1970s.
And then iOS came into my life and I abandoned all of that for UIKit.
But then Google wanted a piece of the action and thrust Android into the world
and yet another UI framework into my brain.</p>
<p>I have been forced to learn too many frameworks in my pursuit to build cool apps.
It’s been exhausting. :-)</p>
<h2 id="unification">Unification</h2>
<p>So, when Apple announced a unification of their platform APIs with macOS Catalina,
I was all ears.</p>
<p>Apple had decided to allow macOS apps to be written using UIKit
as opposed to the mighty AppKit. The technology is called <a href="https://developer.apple.com/mac-catalyst/">Mac Catalyst</a> and is a port of Apple’s mobile libraries to work on Intel 64-bit Macs.</p>
<p>As I support multiple apps that release for both iOS and Mac, this decision
has real economic repercussions for my business.</p>
<ol>
<li>It will allow me to add another store (another way to sell) my existing apps.</li>
<li>It will guarantee that all my future apps will also be for sale on at least two stores.</li>
<li>It will drastically decrease the amount of time I spend fixing UI bugs on mac.</li>
<li>It will give me more time to focus on and improve the iOS versions of my apps.</li>
</ol>
<p>All wins in my opinion. While I have grown to love AppKit, with Mac Catalyst,
I’m not sure I’ll ever write another AppKit app again.</p>
<p>Lastly, Apple has introduced Apple Silicon macs that will also be able to
natively run UIKit. In the same way UWP is Microsoft’s universal framework,
UIKit is shaping up to be Apple’s.</p>
<p>Just one snag, I write my apps using Xamarin.iOS
and Microsoft has not yet added support for Catalyst apps.
Schedules and all that.</p>
<p>This delay was frustrating me and the opportunity cost was killing me,
so I decided to take matters into my own hands and port Xamarin.iOS to Mac Catalyst.
Fortunately, Xamarin and mono and .NET are all open source - the code is right there.
You just have to be crazy enough to start editing it. :-)</p>
<p>It wasn’t easy, but with enough effort I was able to get it all working!
(I would put a GIF here, but seriously no meme could capture how I felt.)</p>
<p>And now it’s time to share that work with everyone.</p>
<h2 id="beta">BETA</h2>
<p>If you would like to convert your own apps to run on Macs, I have prepared a downloadable
SDK/tool to do that conversion. I am making it available to people who support
my OSS work. Namely, if you:</p>
<ul>
<li><a href="https://github.com/sponsors/praeclarum">Sponsor me on GitHub</a></li>
<li><a href="https://twitch.tv/FrankKrueger">Subscribe to me on Twitch</a></li>
<li><a href="https://www.patreon.com/praeclarum">Support me through Patreon</a></li>
</ul>
<p>Then I will give you a link to the SDK. The SDK is just a little .NET Core tool
that converts the iOS version of your Xamarin app to a Mac Catalyst version.
“It just works”</p>
<p>This is an early version so you can also expect errors.
That is to say, sometimes, it doesn’t just work. But so far it has been able to run
my largest apps.</p>
<p>I hope you’ll give it a shot. It’s really fun seeing your apps work on Macs!</p>
<p>I haven’t tried releasing to the Mac App Store yet, that’s the next hurdle to overcome.</p>
<h2 id="contributing-back">Contributing Back</h2>
<p>I am working with Microsoft and the mono team to integrate this work into the
product so everyone can use it. Schedules are still tight, and the world is still
crazy so it will probably take a little time, but I’ll keep working on it!</p>
<p>On that front, the first big step has already been taken. I submitted a PR
to the mono project, and it has been accepted!</p>
<p><a href="https://github.com/mono/mono/pull/20537">Build makefile to support Mac Catalyst #20537</a></p>
<blockquote>
<p>@akoeplinger Thank you, looks great!</p>
</blockquote>
<p>I am so delighted! I have always wanted to be a mono hacker and now I am! :-)</p>
<p>Next steps are getting changes to Xamarin.iOS put in-place. These are small
but require coordination with the build tooling so have to be done
carefully.</p>
<h2 id="in-conclusion">In Conclusion</h2>
<p>The future app development is looking bright thanks to Apple’s unification efforts,
and Microsoft’s commitment to open source. I can’t wait to get all my apps onto Mac.</p>
<p>If you want to help me out, you can do so by buying my apps and by supporting my open source work.
I’ll put those links here one more time. ;-)</p>
<ul>
<li><a href="https://github.com/sponsors/praeclarum">Sponsor me on GitHub</a></li>
<li><a href="https://twitch.tv/FrankKrueger">Subscribe to me on Twitch</a></li>
<li><a href="https://www.patreon.com/praeclarum">Support me through Patreon</a></li>
</ul>TL;DR I have written a tool that will convert your Xamarin.iOS app (including Xamarin.Forms and MAUI) to a Mac app using Mac Catalyst. This makes creating a macOS app and selling it on the App Store easier than ever. If you sponsor me on GitHub, subscribe to me on Twitch, or support me through Patreon, I will send you a link to try a beta version of it.Azure Resource Organization Cheat Sheet2020-04-23T00:00:00+00:002020-04-23T00:00:00+00:00https://praeclarum.org/2020/04/23/azure-organization<p>It’s taken me some time, but I think I finally
have a handle on how all the various Azure resources are
organized and how exactly I’m paying for things.
I summarized what I know into a handy little cheat sheet
that I hope will help you if you’re as confused as I was.</p>
<p><a href="/images/2020/azure_parts.png"><img src="/images/2020/azure_parts.png" alt="Azure parts" /></a></p>
<p><a href="/images/2020/azure_parts.pdf">Azure Resource Organization Cheat Sheet PDF</a></p>
<h2 id="account---who-are-you">Account - Who are you?</h2>
<p>Or, more specifically, what’s your email address? Everything kicks off with a Microsoft account registered through one of their many services. You probably have one already.</p>
<h2 id="active-directory---who-are-you-working-with">Active Directory - Who are you working with?</h2>
<p>Active Directory is used for authentication - especially authentication for Microsoft services. One was probably created for you when you setup Azure with your Account. If you ever see all your Subscriptions or Services disappear, it’s probably because you have selected the wrong AD.</p>
<h2 id="subscription---how-are-you-going-to-pay">Subscription - How are you going to pay?</h2>
<p>This is how you tell Microsoft how you’re going to pay for all the goodies in the next sections. If you want to split Services over multiple payment methods, you’re going to need multiple Subscriptions.</p>
<h2 id="resource-group---how-do-you-group-things">Resource Group - How do you group things?</h2>
<p>This is your moment to impose order on the chaos and create groupings of your own desire. Resource Groups are convenient for moving many Services from one Subscription (payment method) to another or to delete several Services.</p>
<h2 id="service-plan---how-much-do-you-want-to-pay">Service Plan - How much do you want to pay?</h2>
<p>Or, how big of a machine do you want? This is where you decide how much compute power, storage space, memory, and bandwidth you’re willing to pay for. Prices range from free for dev and test to much much more. You also need to choose where in the world the server will be located.</p>
<h2 id="services---what-do-you-need">Services - What do you need?</h2>
<p>Now you can add Services to the machine you selected in the Service Plan or to a Resource Group (which one depends on the service). There are literally billions of services to choose from. Each one will add their own costs based on usage and daily rates to the credit card on the Subscription.</p>It’s taken me some time, but I think I finally have a handle on how all the various Azure resources are organized and how exactly I’m paying for things. I summarized what I know into a handy little cheat sheet that I hope will help you if you’re as confused as I was.Appstat - App Sales and Analytics Menu2020-02-20T00:00:00+00:002020-02-20T00:00:00+00:00https://praeclarum.org/2020/02/20/appstat<p><strong>TL;DR</strong> I have written an app, <a href="https://gum.co/appstat">Appstat</a>, that I think every Apple app developer will want.
It is a macOS status menu app that displays all your apps that are for
sale on the App Store plus a whole bunch of data about them including
proceeds, sales, ratings, and uploaded builds.</p>
<p>It is an app that I have always dreamed of, so I built it, and I know you will love it! You can <a href="https://gum.co/appstat">buy Appstat on gumroad</a> now!</p>
<p><a href="https://gum.co/appstat"><img src="/images/2020/appstat.png" alt="Appstat screenshot" /></a></p>
<p>If you are an Apple app developer I’m sure you’ve come up with your
own system to track sales. When I started out, I used a Mac app to show
my sales reports - it had graphs and was wonderful. But then that
app got expensive and less useful and I stopped using it.</p>
<p>So for years I’ve been logging into
App Store Connect and clicking around furiously to find the information I needed. It’s a lot of clicking, and I found myself checking reports
less and less often - certainly not casually.</p>
<p>But this isn’t good in fact it is terrible! I’m an independent app developer and need to stay
on top of things. I want to make sure my apps are performing well and that customers are happy with them. My livelihood depends on it.</p>
<p>Enter <a href="https://montemagno.com">James Montemagno</a>. After recording an episode of <a href="https://www.mergeconflict.fm">Merge Conflict</a>,
we were chatting about what our ideal sales and builds status app would be.
We both fell in love with the idea of a little menu that, when clicked, would
show you all the important information about all your apps, at a glance.
No more web pages with passwords, no more progress bars, just glorious data.</p>
<p>I decided that this app needed to exist - I, at least, needed it to exist. So I wrote it. And now I’m making it <a href="https://gum.co/appstat">for sale to you</a>, I hope you love it. :-)</p>
<h3 id="amazing-features">Amazing Features</h3>
<p>What does the app show you? EVERYTHING. It shows you everything:</p>
<ul>
<li>Name and platform</li>
<li>Average proceeds per day (including in-app purchases)</li>
<li>Average unit sales per day (including free apps)</li>
<li>Bundle identifier and SKU</li>
<li>Your app’s website</li>
<li>Original release date and time on the store</li>
<li>Latest version number and time on the store</li>
<li>Rate at which people rate your app (ratings/month)</li>
<li>Latest version rating</li>
<li>Overall app rating</li>
<li>Device distribution based on purchases</li>
<li>Country distribution based on purchases</li>
<li>Chart of proceeds over time</li>
<li>Average proceeds per day</li>
<li>Projected proceeds per month and year</li>
<li>Chart of unit sales over time</li>
<li>Latest builds uploaded to TestFlight</li>
</ul>
<p>All stats are linked to the appropriate section of Apple’s developer site, making Appstat a great launchpad.</p>
<p>Total store proceeds are also displayed:</p>
<ul>
<li>Average total proceeds per day (and standard deviation)</li>
<li>Projected total proceeds per month (estimated using a configurable amount of history)</li>
<li>Projected total proceeds per year</li>
</ul>
<h3 id="amazing-security">Amazing Security</h3>
<p>I can’t expect you to use an app like this unless I take security very seriously, and I did.</p>
<h4 id="1-no-usernames-or-passwords">#1 No usernames or passwords</h4>
<p>This isn’t the 90s. Instead, all requests are signed using a personally generated API Key. This gives you absolute control without ever exposing
your usernames or passwords. This also relieves me of having to ask
for that sensitive information.</p>
<h4 id="2-no-data-is-ever-transmitted-from-your-local-machine">#2 No data is ever transmitted from your local machine</h4>
<p>This is obvious, but in 2020 I guess I need to state it. I don’t even collect analytics.</p>
<h4 id="3-no-data-is-ever-cached-to-disk">#3 No data is ever cached to disk</h4>
<p>Disk caches are security holes so I don’t use them. Instead, the app
downloads all data afresh when started.
Don’t worry, this process is very fast.</p>
<h4 id="4-all-account-setting-are-encrypted-in-keychain">#4 All account setting are encrypted in Keychain</h4>
<p>You need to provide an API Key to access data and that key is encrypted
locally on your machine in your login Keychain.</p>
<h3 id="selling-on-gumroad">Selling on Gumroad</h3>
<p>I’m <a href="https://gum.co/appstat">selling Appstat</a> on the wonderful <a href="https://gumroad.com">gumroad</a>. This is my first time selling there and so far I am very impressed with their service. I guess you should <a href="https://gumroad.com/praeclarum">follow me</a> on there, or something.</p>
<p>I also unlocked a Mac developer achievement with this app by integrating
the <a href="https://sparkle-project.org">Sparkle framework</a> to do app updates. I feel like this is a real
milestone in my career.</p>
<h3 id="colophon">Colophon</h3>
<p>When you write a security-conscious app with multiple data sources and sums over currencies,
you want to use a language with strongly typed data and immutability - a functional programming language - you want to use F#. And so I did.
The data architecture is the reactive one I’ve been presenting at <a href="https://praeclarum.org/2015/04/15/functional-guis.html">conferences over the last 5 years</a>.</p>
<p>Appstat was written using <a href="https://xamarin.com">Xamarin.Mac</a> to keep it good looking and fast and was written using Visual Studio for Mac. Big thanks to everyone on the Xamarin team for making it possible to use my favorite language on macOS!</p>TL;DR I have written an app, Appstat, that I think every Apple app developer will want. It is a macOS status menu app that displays all your apps that are for sale on the App Store plus a whole bunch of data about them including proceeds, sales, ratings, and uploaded builds.