Doug Slater
https://www.slater.dev
ZolaenFri, 01 Mar 2024 00:00:00 +0000Make a Correct JudgmentFri, 01 Mar 2024 00:00:00 +0000Unknown
https://www.slater.dev/make-a-correct-judgement/
https://www.slater.dev/make-a-correct-judgement/<p>In this post I'll make the following points: </p>
<ol>
<li>Correct judgments are rooted in upright character.</li>
<li>Incorrect judgments harm the judge and the judged.</li>
</ol>
<blockquote>
<p>Do not judge by appearances, but judge with right judgment. -- Jesus Christ, ~33 AD</p>
</blockquote>
<blockquote>
<p>I have a dream that my four little children will one day live in a nation where they will not be judged by the color of their skin but by the content of their character. -- Martin Luther King, August 28, 1963</p>
</blockquote>
<p>Neither Jesus nor Dr. King called for <em>no judgment</em>, but rather for <em>correct judgment</em>. I agree with them. The world has gotten off track: it is not somehow categorically virtuous to avoid making judgments.</p>
<p>I should define what I mean by judgment. I can discern three behaviors people decry when they proclaim, "don't judge!":</p>
<ol>
<li><strong>Observation</strong>. To see a situation. That is, to take notice of it. The opposite of "ignore".</li>
<li><strong>Evaluation</strong>. Having seen, to make an assessment against your values. That is, to become invested. The opposite of "suppress".</li>
<li><strong>Action</strong>. To choose to intervene, being so moved by your evaluation. The opposite of "sweep it under the rug".</li>
</ol>
<p>All three forms of judgment have their place. Without observation, my body would collide with a passing bus in an intersection. A world without evaluation would make no distinction between war criminal and war hero, and a world without action would do nothing about Nazi death camps, even though we felt they were wrong.</p>
<p>Sometimes, a wise judgment means taking no action, or taking restrained action. While we might feel strongly that Russia should not invade Ukraine, an all-out assault on Russia would cause greater harm. Sometimes the action is an exercise of grace, in which the grace-giver pays the cost of overlooking wrongdoing.</p>
<p>Given that we should make judgments, what is a <em>correct</em> judgment? According to Jesus and Dr. King, it is one that looks deeper than appearances.</p>
<p>Appearances are not always wrong, but they often are. In those cases, our incorrect judgments are rooted in our own character flaws. Take this example:</p>
<blockquote>
<p>I know your presumption and the evil of your heart, for you have come down to see the battle.” And David said, “What have I done now? Was it not but a word?” -- 1 Samuel 17</p>
</blockquote>
<p>We've all heard about David from the Bible. Everyone recognizes the curly-haired sculpture by Michaelangelo. Did you know that David had older brothers? Eliab was Jesse's oldest son, but David was selected to become king of Israel. He was jealous of his little brother David, and it came out when David asked to confront Goliath.</p>
<p>Contrast that to the first sentence in Jude:</p>
<blockquote>
<p>Jude, a servant of Jesus Christ -- Jude 1:1</p>
</blockquote>
<p>Do you know which Jude this is? Many scholars believe this Jude was Jesus' brother - both born of Mary, as in Matthew 13:55.</p>
<p>This verse amazes me. I have a brother. Brothers are not afraid of calling you on your mistakes. Of <em>judging you</em>. But here, Jesus' brother refers to himself as a <em>servant</em>.</p>
<blockquote>
<p>Or how can you say to your brother, ‘Let me take the speck out of your eye,’ when there is the log in your own eye? You hypocrite, first take the log out of your own eye, and then you will see clearly to take the speck out of your brother's eye. -- Jesus, Matthew 7</p>
</blockquote>
<p>I'll say it again: the correctness of our judgments is rooted in our own character.</p>
<hr />
<p>To demonstrate my second point, I'll tell a personal story.</p>
<p>I once interviewed at a company that was very excited to extend me an offer of employment as Senior Software Engineer. I was to be the first hire of, and lead for, the company's first software team. I was excited too.</p>
<p>I made a gentle counteroffer: I requested a 6% salary increase, another week of vacation, and the title of Principal Software Engineer. My wife and recruiter approved the wording, and we sent off the letter.</p>
<p>The recruiter called me a few days later and said that the company had retracted their offer. My counteroffer had rubbed them the wrong way. Can you guess which of my requests offended them? It was the one that would have cost them nothing: the title. They perceived it as egotistical and a grab for power.</p>
<p>In trying to understand what happened, the idea of <em>judgment</em> kept coming to mind.</p>
<p>Clearly a character judgment was made, and if the judgment had been correct, withdrawing the offer was a reasonable mitigation. I can side with anyone who doesn't want to work with an egotist.</p>
<p>However, the judgment was incorrect. I am not an egotist. </p>
<p>Consider the following points: </p>
<ul>
<li>I wrote the counteroffer sitting together with my wife. Egotists don't ask their wives for advice.</li>
<li>My wife was shocked by their decision.</li>
<li>My recruiter read and approved of the counteroffer, with his commission on the line.</li>
<li>I had been ready to pass on this role, because I was <em>not</em> confident I could do a good job, which I was vocal about to everyone.</li>
<li>The title was appropriate.
<ul>
<li>The title ladder is a bizarre game that I would prefer not to play. However, in software development, whether you qualify as <em>senior</em> or <em>principal</em> or whatever is strongly correlated with your base salary. To feed my family, I have to play.</li>
<li>Software development is a staggeringly profound, complex field which one must enjoy on its own merits because it is also extremely commoditized: I am just a unit of work to my employer. Paycheck in, software out. Egotists don't survive this; they transition into business management.</li>
<li>Principals have broad and deep experience and can draw on that to guide a technical team. That's me. I have been in the industry for over 12 years and have been writing code for 22 years.</li>
<li>The company, recruiter, and I had used the terms <em>principal</em> and <em>lead</em> repeatedly in conversations.</li>
</ul>
</li>
</ul>
<p>I didn't do anything objectively wrong. The error is on their side, but it still hurt. I wondered what I could have done differently, but the incorrect judgment says more about the company than me:</p>
<ul>
<li>In their location, company leadership had conceded as a last resort that the only way to hire technical talent was to hire remotely. </li>
<li>The company has ~100 employees, of which 3 are remote.</li>
<li>The company's leadership is not remote.</li>
<li>I expressed reservations about this situation.</li>
<li>Despite their assurances, the company is not ready for a remote team.</li>
<li>A company culture that is not used to remote workers will be not able to exercise self-moderation when events occur that give rise to suspicions of character. </li>
<li>I used words which gave rise to such a suspicion.</li>
<li>I could not walk into the office and use my words, tone, and behavior to contradict it.</li>
<li>I observed that my hiring manager would easily lose his temper. I can only speculate, but maybe this happened when reading my counteroffer, and maybe he felt jealous of me.</li>
</ul>
<p>Who got hurt in this judgment? Everyone. The company lost a strong contributor, the recruiter lost a check, and I lost a growth opportunity. </p>
<p>Overall, I feel grateful. If hired, I have would been facing down incorrect judgments with my family on the line. </p>
When a Recruiter Asks If I Do AgileSat, 24 Feb 2024 00:00:00 +0000Unknown
https://www.slater.dev/when-a-recruiter-asks-if-i-do-agile/
https://www.slater.dev/when-a-recruiter-asks-if-i-do-agile/<div style="text-align: center; width: 60%; margin: 0 auto;">
<img src="../img/rembrandt.png"/>
<p style="font-style: italic">Image: Rembrandt prefers Crayola. Generated by Dall-E</p>
</div>
<ul>
<li><strong>Tech Recruiter</strong>: So I see here that you do portraits? </li>
<li><strong>Rembrandt</strong> (famous painter): Yes</li>
<li><strong>Tech Recruiter</strong>: Great, our clients are looking for painters with Crayola experience. Do you have experience using Crayola? </li>
<li><strong>Rembrandt</strong>: ... </li>
</ul>
Why Programming is HardFri, 16 Feb 2024 00:00:00 +0000Unknown
https://www.slater.dev/why-programming-is-hard/
https://www.slater.dev/why-programming-is-hard/<p>Programming computers is hard because, as <a href="https://www.youtube.com/watch?v=-J_xL4IGhJA">Hal put it</a>, it isn't about programming, and it isn't about computers. </p>
<div style="text-align: center; width: 60%; margin: 0 auto;">
<img src="../img/wizard.png"/>
<p style="font-style: italic">A wizard conjuring a computer program. Generated by Dall-E</p>
</div>
<p>Even the most mundane of programming issues are rooted in profound questions:</p>
<ul>
<li>"Why are my dependencies broken?" => "What is causality?" <sup>[<a href="https://www.slater.dev/why-programming-is-hard/#References">1</a>]</sup></li>
<li>"Should I check for null?" => "When can we know a program is correct? Design time, compile time, or run time?" <sup>[<a href="https://www.slater.dev/why-programming-is-hard/#References">2,3</a>]</sup></li>
<li>"Why don't I understand this code?" => "What is memory, cognition, and language?" <sup>[<a href="https://www.slater.dev/why-programming-is-hard/#References">4</a>]</sup></li>
<li>"Why is this program buggy?" => "What is a proof?" <sup>[<a href="https://www.slater.dev/why-programming-is-hard/#References">5,6</a>]</sup></li>
</ul>
<p>While you'll overhear the questions on the left rising from the desks of developers on a Tuesday afternoon, their answers are surprisingly elusive and timeless.</p>
<hr />
<h2 id="references">References</h2>
<ol>
<li><a href="https://ieeexplore.ieee.org/document/7243738">Program Actions as Actual Causes</a></li>
<li><a href="https://web.archive.org/web/20090628071208/http://qconlondon.com/london-2009/speaker/Tony+Hoare">Null References: The Billion Dollar Mistake</a></li>
<li><a href="https://www.pathsensitive.com/2018/01/the-three-levels-of-software-why-code.html">The Three Levels of Software</a></li>
<li><a href="https://en.wikipedia.org/wiki/Tacit_knowledge">Tacit knowledge</a></li>
<li><a href="https://en.wikipedia.org/wiki/Curry%E2%80%93Howard_correspondence">Curry-Howard correspondence</a></li>
<li><a href="https://tomasp.net/blog/2018/alien-lambda-calculus">Would aliens understand lambda calculus?</a></li>
</ol>
Grounded Programming PedagogyThu, 25 Jan 2024 00:00:00 +0000Unknown
https://www.slater.dev/grounded-programming-pedagogy/
https://www.slater.dev/grounded-programming-pedagogy/<p>In the alum chat for <a href="https://www.mirdin.com">Mirdin</a>, someone posted the following question, "Would it be better to start teaching beginners functional or imperative programming first?"</p>
<p>Pretending for a second that <a href="https://en.wikipedia.org/wiki/Functional_programming">FP</a> and <a href="https://en.wikipedia.org/wiki/Imperative_programming">IP</a> are the only choices (they're <a href="https://info.ucl.ac.be/~pvr/VanRoyChapter.pdf">not</a>), an answer depends on where your beginner comes from.</p>
<p>A meta-pedagogical (teaching about teaching) answer acknowledges that learning starts in a place the learner feels <a href="https://argumatronic.com/posts/2018-09-02-effective-metaphor.html">grounded</a> and connects them from there to new knowledge. For example, <a href="https://en.wikipedia.org/wiki/Subitizing">subitizing</a> is an innate ability. A developmentally normal child can instantly know how many objects are presented, for values up to about 5.</p>
<div style="text-align: center; width: 60%; margin: 0 auto;">
<img src="../img/5_die.png"/>
<p style="font-style: italic">Image: How many dots are there? You just subitized.</p>
</div>
<p>From there, you can teach a 5-year-old arithmetic, for example to add, by having them subitize groups of objects alongside numerals and the math symbol <code>+</code>. This connects their innate notion of quantity to the abstract notion of numbers.</p>
<p>The MIT book and course <a href="https://web.mit.edu/6.001/6.037/sicp.pdf">SICP</a> is a masterpiece in programming pedagogy, and it falls heavily on the FP side. Watch Professor Sussman absolutely rip apart side effects in <a href="https://ocw.mit.edu/courses/6-001-structure-and-interpretation-of-computer-programs-spring-2005/resources/5a-assignment-state-and-side-effects/">lecture 5A</a>. But not everybody "gets" this approach easily. Even Hal acknowledges in this <a href="https://corecursive.com/039-hal-abelson-sicp/">Corecursive interview</a> that the FP approach didn't work for everyone: </p>
<blockquote>
<p>In the very beginning days, we would teach a sort of short courses for MIT faculty and some of the electrical engineers would just get stuck. "You haven’t showed us how the transistors work". ...People think different ways. Some people have to be grounded on where they’re comfortable and for some people, well, it really is transistors. </p>
</blockquote>
<p>Contrast that to somebody who knows what a function is, in the math sense. The machinery of a <a href="https://en.wikipedia.org/wiki/Transistor">transistor</a>, of gates, sources, drains, and <a href="https://en.wikipedia.org/wiki/Transistor%E2%80%93transistor_logic">TTL</a> will be an irritating detail, like multiplying matrices by hand.</p>
<p>So in the dichotomy between introducing FP and IP first, the answer might depend on where the learner is coming from. I learned IP and OOP first because that's what material was available. I was first exposed to FP in college in the form of Scheme. It was disorienting. I couldn't count all the parentheses, let alone grok tail recursion. I wish it could have been presented to me grounded in terms of what I was already comfortable with, for example high school algebra.</p>
<p>I say, introduce multiple programming paradigms early, and ground it in what the learner knows. A fundamental programming skill is to think at <a href="https://www.joelonsoftware.com/2002/11/11/the-law-of-leaky-abstractions/">multiple levels of abstraction</a>. The Haskell programmer needs to be aware that even monads at some point are just bits in a memory hierarchy, just as it's important for an assembly programmer to know that while register allocation is critical to performance, registers are not really what the program is about.</p>
<p>To close, let's ground this discussion in something we all know. To design a cathedral, the architect needs to know what bricks will be used. To build a cathedral, the bricklayer needs to envision the noble end to which each brick is set down.</p>
A Design is a Mold for CodeMon, 15 Jan 2024 00:00:00 +0000Unknown
https://www.slater.dev/a-design-is-a-mold-for-code/
https://www.slater.dev/a-design-is-a-mold-for-code/<p>I'm always in search of good metaphors for software design. Good or not, here's one:</p>
<blockquote>
<p>A Design is a Mold for Code</p>
</blockquote>
<p>In manufacturing, a mold is a hollow shape that fills with fluid material which expands into the space and sets to form a casting. </p>
<div style="text-align: center; width: 60%; margin: 0 auto;">
<img src="../img/dall-e-muffin-pan.png"/>
<p style="font-style: italic">Image: A useful mold. Generated by Dall-E</p>
</div>
<p>Like in manufacturing, code fills the <a href="https://en.wikipedia.org/wiki/Negative_space">negative space</a> of a software design. Over time, code sets and becomes harder to change.</p>
<p>Imagine two engineering teams who independently "fill" (implement) a design, and the design is the set of standards for a web browser. The two implementations are Chrome and Firefox. The two browsers have independent codebases<sup>[<a href="https://www.slater.dev/a-design-is-a-mold-for-code/#notes">1</a>]</sup> which take vastly different shapes, but as far as web standards are concerned, the two are equal. You could switch from Chrome to Firefox (which I <a href="https://www.eff.org/deeplinks/2021/12/chrome-users-beware-manifest-v3-deceitful-and-threatening">recommend</a>) and still browse compliant web pages with no discernible change. </p>
<p>Programmers who also like math sometimes call two pieces of code which can be swapped <a href="https://blog.ploeh.dk/2018/01/08/software-design-isomorphisms/">isomorphic</a> with respect to the spec.</p>
<p>It's important not to reverse the causality: a design causes the work product, not the converse. A mold causes a casting, and a software design causes code. Nevertheless, to some extent, one can go in reverse, i.e. reconstruct a mold from a casting or infer a design from code. </p>
<p>The reconstructed mold might lose some fidelity, like sharp edges or precise dimensions, but it's far worse for code. Looking at code without supplemental information, a new dev can only infer a dim, vague notion of its design. She might even sense the mind(s) behind the design. However, it would be costly if even possible to losslessly reconstruct the design, i.e. to such a fidelity that it could be the basis for an isomorphic implementation.</p>
<p>In pseudocode:</p>
<pre style="background-color:#1e1e1e;color:#dcdcdc;"><code><span>[Test]
</span><span>fn Reverse_ProducesIsomorphicDesign()
</span><span> let design1 : ...
</span><span> let code1 = implement(design1)
</span><span> let design2 = reverse(code1)
</span><span> let code2 = implement(design2)
</span><span> assert(isomorphic(code1, code2)) // Fails
</span></code></pre>
<p>Above: The roundtrip from a design to code back to design is lossy.</p>
<p>There are a few reasons for this:</p>
<ul>
<li>A codebase maybe too large to fit into a dev's head. The codebases for Chrome and Firefox are huge. Like the limited context window of ChatGPT, a human has limited working memory and forgets. Though with time and repetition, knowledge transitions to long-term memory.</li>
<li>It's hard to infer high-level concepts from low-level details. For example, decompiling assembly back to C.</li>
<li>Design and code have a many-to-many relationship.
<ul>
<li>There can be many implementations that satisfy a design.</li>
<li>There can be many designs that result in the same code.</li>
</ul>
</li>
</ul>
<p>This is a problem for software that outlives its creator(s). The software will languish unless knowledge is <a href="https://en.wikipedia.org/wiki/Organizational_memory">retained</a> and <a href="https://en.wikipedia.org/wiki/Bus_factor">replicated</a>.</p>
<p>There are some things developers can do to keep their software alive:</p>
<ul>
<li>Write down the design. Use <a href="https://en.wikipedia.org/wiki/Literate_programming">natural language</a></li>
<li>Make the <a href="https://www.pathsensitive.com/2018/01/the-design-of-software-is-thing-apart.html">design apparent in the code</a></li>
<li>Use <a href="https://www.pathsensitive.com/2022/03/abstraction-not-what-you-think-it-is.html">precise abstractions</a></li>
<li>Use <a href="https://argumatronic.com/posts/2018-09-02-effective-metaphor.html">effective</a> <a href="https://gist.github.com/onlurking/fc5c81d18cfce9ff81bc968a7f342fb1#tacit-knowledge-and-documentation">metaphors</a></li>
<li>Make the code <a href="https://www.oreilly.com/library/view/code-that-fits/9780137464302/">fit in your head</a></li>
</ul>
<hr />
<h2 id="notes">Notes</h2>
<ol>
<li>Firefox and Chrome are not truly independent by this definition. Plenty of people who have worked on one browser have also worked on the other. Moreover, as open source projects, both teams can see the other's code. <a href="https://source.chromium.org/chromium/chromium/src/+/main:">Chromium source</a> and <a href="https://searchfox.org/mozilla-central/source">Firefox source</a> </li>
</ol>
Reflections on a DIY Canova Method Marathon Training CycleFri, 23 Oct 2020 00:00:00 +0000Unknown
https://www.slater.dev/reflections-on-a-diy-canova-method-marathon-training-cycle/
https://www.slater.dev/reflections-on-a-diy-canova-method-marathon-training-cycle/<p>From August-November 2019, I trained for the <a href="https://monumentalmarathon.com/">Indianapolis Marathon</a> using the Renato Canova method. I composed training plan with information from both primary and secondary sources, including others' Canova-style training plans, descriptions of his method, video interviews, his own training plans for his athletes, and <a href="https://www.slater.dev/renato-canova-in-valencia/">one very useful talk in Valencia</a>.</p>
<p>My self-composed plan called for two quality runs a week: one of longer duration and one of shorter duration but more intensity. The remaining runs were easy miles ending with striders. I did not have time to do a Canova-style base phase, which is <a href="https://nateruns.blogspot.com/2015/02/throwback-thursday-meeting-canova.html">arguably the most important part</a>, but I nailed the taper, executing some very aggressive speedwork as late as the week before the race.</p>
<p>The cycle produced the following results:</p>
<ul>
<li>No injury</li>
<li>A 54-second PR (2:59:26 → 2:58:30)</li>
<li>A moderate and early wall starting at mile 19</li>
</ul>
<p><strong>Was it hard work?</strong> Yes. It was a lot of training time and fairly monotonous.</p>
<p><strong>Would I do it again?</strong> I would like to try other methods first, particularly ones with greater variety.</p>
<p><strong>Is Canova better than method X?</strong> Based on what metrics? A sample size of one athlete-cycle does not provide much information. For a metric like Injury Rate, can say that I have completed several Lydiard-style cycles, during all of which I accrued at least one injury. I did not get injured this cycle, but in January 2020 I began another Canova-style cycle, and I strained my calf in February on an easy run the day after a quality workout.</p>
<p><strong>Is it the best method?</strong> In what context? For amateur athletes, I would say "no". Canova himself stated in Valencia that amateurs simply cannot produce the quality his style demands, and that holds true for this amateur. Canova starts with Elite athletes and aims to make them world class. It would be better to follow a plan made specifically for the type of athlete you are.</p>
<p><strong>What will I do next?</strong> In June 2020 I returned to Bobby Holcombe of <a href="http://www.knoxvilleendurance.com/">Knoxville Endurance</a> to get training more suited to my changing goals. Before 2020, I wanted to be as fast as possible, and now I want to be as consistent as possible.</p>
Transformation Matrices are Neural NetworksThu, 15 Oct 2020 00:00:00 +0000Unknown
https://www.slater.dev/matrices-are-neural-networks/
https://www.slater.dev/matrices-are-neural-networks/<p>Chances are you've seen a neural network diagram like this one with circles and arrows:</p>
<p><img src="https://miro.medium.com/max/496/1*GTdVep66Ln4N4Zd2JnSXbQ.png" alt="Image for post" />
<em>Figure: A neural network. Source:</em> <a href="https://medium.com/@quantumsteinke/whats-the-difference-between-a-matrix-and-a-tensor-4505fbdc576c">Medium</a></p>
<p>While that looks fancy, it's basically a matrix. More generally, it's a tensor, but a special kind of tensor which makes it <a href="https://math.stackexchange.com/a/412429">just a matrix</a>.</p>
<p>I was reading <a href="https://medium.com/@quantumsteinke/whats-the-difference-between-a-matrix-and-a-tensor-4505fbdc576c">this Medium article comparing matrices and tensors</a> which models the following matrix multiplication as the two-layer neural network shown above:</p>
<p><img src="https://miro.medium.com/max/756/1*Bxba1gx4ec2h9qe7UNPvMg.png" alt="Image for post" /></p>
<p>This helped me realize that any linear map can be modeled as a neutral network. Consider rotating a vector around the X, then Y, and Z-axes:</p>
<p>$$
\begin{aligned}
M_x &= Rot_x(15°) \newline
M_y &= Rot_y(15°) \newline
M_z &= Rot_z(15°) \newline
V &= [1,2,3] \newline
V' &= M_xV \newline
&= [1, \pmb{1.155}, \pmb{3.415}] \newline
V'' &= M_yV' \newline
&= [\pmb{1.850}, 1.155, \pmb{3.042}] \newline
V''' &= M_zV'' \newline
&= [\pmb{1.488}, \pmb{1.595}, 3.042] \newline
\end{aligned}
$$</p>
<p>Here is a neural network visualizing the linear mapping sequence $M_zM_yM_xV$:</p>
<p><img src="https://drive.google.com/uc?export=view&id=1JZ5nbd-Y55QYEPMk8XQ00cE2w688KX25" alt="" />
<em>Figure: Made with <a href="https://draw.io/">draw.io</a></em></p>
<p>By tracing the paths through the layers of the neural network below, one can gain an intuition of how each transformation operation contributes to the resulting vector $V_{rot}$.</p>
<p><img src="https://drive.google.com/uc?export=view&id=1t9ccuXdm87LI7s9Tx5QdnJ0STQQv_V39" alt="" />
<em>Figure: Made with <a href="https://draw.io/">draw.io</a></em></p>
Threadripper 3970x PC BuildTue, 06 Oct 2020 00:00:00 +0000Unknown
https://www.slater.dev/threadripper/
https://www.slater.dev/threadripper/<p>In April, I built a new PC out of these parts:</p>
<ul>
<li>Threadripper 3970x</li>
<li>MSI TRX40 PRO</li>
<li>G.SKILL Trident Z Neo Series 64GB (2 x 32GB)</li>
<li>EVGA GeForce GTX 1080 8GB GDDR5X</li>
<li>Samsung 970 EVO</li>
<li>EVGA 1000 GQ</li>
<li>Noctua NH-U14S TR4-SP3</li>
<li>Fractal Design Meshify C</li>
</ul>
<p>Total cost: <strong>$3863.98</strong></p>
<p><strong>Why did I build it?</strong> I'm a software developer, and I spend all day in Visual Studio. I had just gotten hired at a new employer, and I expected to be doing a fair amount of C++ compilation. For the rest of my development tasks, I wanted to be comfortably over-provisioned. </p>
<p><strong>Do I like it?</strong> Emphatically yes. Developing on it is a dream. I am spoiled to work on any other machine. It is fun to watch all 64 logical processors max out in Task Manager during compilation. I love spinning up a virtual machine and whimsically assigning 16 cores and as many gigabytes of RAM. I feel like a teenager with a 500hp engine under his foot.</p>
<p><img src="https://drive.google.com/uc?export=view&id=1NUEqO5TG63nc1w3gjQOcWOb-4WBu8GsM" alt="" />
<em>Image: All cores maxed out compiling <a href="https://www.researchgate.net/publication/259903810_The_open-radART_ion_ORAion_Software_Suite">open-radART</a>. Vroom!</em></p>
<p><strong>It is worth it?</strong> I find the performance-per-dollar to be very high. I was pleased to see that Linus Torvalds himself <a href="http://lkml.iu.edu/hypermail/linux/kernel/2005.3/00406.html">praised his 3970x</a>. I find performance to be a good balance between the cheaper 3960x and the pricey 3990x. I literally could not find any prebuilt machines of this caliber, but if I could have, they would have easily cost over $6000.</p>
<p><img src="https://drive.google.com/uc?export=view&id=1GQNreuaFoaGbw6WqV8pW4T3tlnDIxb38" alt="" />
<em>Image: The 3970x sits equitably on the performance-price curve, but its performance is way ahead of the competition. Source: https://www.cpubenchmark.net/</em></p>
Christian Pull RequestsSat, 03 Oct 2020 00:00:00 +0000Unknown
https://www.slater.dev/christian-pull-requests/
https://www.slater.dev/christian-pull-requests/<h3 id="christian-pull-requests">Christian Pull Requests</h3>
<p>I'm a software developer, so the terms <a href="https://opensource.stackexchange.com/a/380">pull request</a> and <a href="https://en.wikipedia.org/wiki/Code_review">code review</a> are part of my daily vernacular.</p>
<p>I'm also a Christian and go to a weekly Bible study.</p>
<p>At Bible study, after the lesson, and sometimes before, the group shares "prayer requests". This is a chance for participants to pray for each other and for people outside the group.</p>
<p>Unless requested not to, we record these prayer requests in a group messaging app. It can be hard to take down requests on a smartphone, so I often bring my laptop to keep up.</p>
<p>At the end of Bible study one recent Sunday night, I pulled out my laptop and announced, "Ok, I am taking pull requests!"</p>
<p>Not knowing what I had just said, I looked around and saw blank eyes looking back.</p>
<p>"Guys, I mean <em>prayer</em> requests!"</p>
<p>Having my laptop open may have triggered something code-related in my brain.</p>
Cubic Spline Joint TrajectoriesSun, 20 Sep 2020 00:00:00 +0000Unknown
https://www.slater.dev/cubic-spline-joint-trajectories/
https://www.slater.dev/cubic-spline-joint-trajectories/<h3 id="introduction">Introduction</h3>
<p>In robot kinematics, a <em>joint path</em> is a sequence of positions for one or more joints. A <em>joint trajectory</em> is the time function interpolating these positions.</p>
<p>This post examines generating joint trajectories with cubic splines.</p>
<p>Say we have a robotic arm with one revolute joint, and we want to rotate its joint position \(Q\) from \(0\) to \(90\) degrees.</p>
<p>$$
\begin{aligned}
Q_{init} &= 0 \newline
Q_{final} &= \pi/2 \newline
\end{aligned}
$$</p>
<p><img src="https://drive.google.com/uc?export=view&id=1m5GK-sDcSwYTzq65qyWgDwImKPglIG3j" alt="" />
<em>Figure: The joint start and goal</em></p>
<p>We don't care how long it takes, but the joint must start from rest and and end at rest.</p>
<p>$$
\begin{aligned}
V_{init} &= 0 \newline
V_{final} &= 0 \newline
\end{aligned}
$$</p>
<hr />
<h3 id="initial-solution">Initial Solution</h3>
<p>We can satisfy these constraints by interpolating the joint position, velocity, and acceleration with a <a href="https://mathworld.wolfram.com/CubicSpline.html">cubic spline</a>,</p>
<p>$$
\begin{aligned}
Q(t) &= At^3 + Bt^2 + Ct + D &&\text{// Position} \newline
V(t) &= 3At^2 + 2Bt + C &&\text{// Velocity} \newline
A(t) &= 6At + 2B &&\text{// Acceleration} \newline
\end{aligned}
$$</p>
<p>where \(t\) is the time since the movement started.</p>
<p>We start by finding the coefficients \(A\), \(B\), \(C\), and \(D\).</p>
<p>$$
\begin{aligned}
Duration &= \textit{(To be Determined)} \newline
Displacement &= Q_{final} - Q_{init} \newline
A &= \frac{(2 \cdot -Displacement / Duration + V_{init} + V_{final})}{Duration^2} \newline
B &= \frac{(3 \cdot Displacement / Duration - 2 \cdot V_{init} - V_{final})}{Duration} \newline
C &= V_{init} \newline
D &= Q_{init} \newline
\end{aligned}
$$</p>
<p>Since we don't care how long the movement takes, let's choose arbitrarily that the movement should last \(1\) second:</p>
<p>$$
Duration = 1
$$</p>
<p>then we have the coefficients</p>
<p>$$
\begin{aligned}
A &= (2 \cdot -(\pi/2)/1 + 0 + 0)/1^2 = -\pi \newline
B &= (3 \cdot (\pi/2)/1 - 2 \cdot 0 -0)/1 = 3\pi/2 \newline
C &= 0 \newline
D &= 0 \newline
\end{aligned}
$$</p>
<p>Plugging these values back into the cubic equations, we can see in the figure that the joint at \(t = 1s\) has position \(Q = \pi/2>rad\) and velocity \(v = 0>rad/s\).</p>
<center>
<iframe src="https://www.desmos.com/calculator/elrzqx76aq?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
</center>
<p><em>Figure: Joint Position, Velocity, and Acceleration over Time</em></p>
<hr />
<h3 id="joint-constraints">Joint Constraints</h3>
<p>In reality, a joint may not physically be able to move in 1 second, so let's consider some realistic constraints.</p>
<p>Say the joint has a maximum angular velocity of \(6°/s\) and a maximum angular acceleration of \(3°/s^2\). Assume this holds true regardless of its payload or position, i.e. ignore dynamics.</p>
<p>$$
\begin{aligned}
V_{limit} &= 0.104719755 > rad/s \newline
A_{limit} &= 0.0523599 > rad/s^2
\end{aligned}
$$</p>
<p>Clearly the solution plotted above exceeds these limits:</p>
<p>$$
\begin{aligned}
V_{max} &= 3\pi/4, t = 0.5s \newline
A_{max} &= 3\pi, t = 0s \newline
&= -3\pi, t = 1s \newline
\end{aligned}
$$</p>
<p>We can reduce the velocity and acceleration by scaling the duration, i.e. making the movement take longer. The time-optimal solution is found analytically according to Melchiorri [1]:</p>
<pre data-lang="c" style="background-color:#1e1e1e;color:#dcdcdc;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#569cd6;">double </span><span>get_scale()
</span><span>{
</span><span> </span><span style="color:#569cd6;">double</span><span> v_scale = abs(Vmax) / Vlimit
</span><span> </span><span style="color:#569cd6;">double</span><span> a_scale = sqrt(abs(Amax) / Alimit)
</span><span> </span><span style="color:#569cd6;">return </span><span>max(v_scale, a_scale)
</span><span>}
</span></code></pre>
<p>If <code>a_scale</code> is larger than <code>v_scale</code>, then the acceleration limit is constraining the duration. If <code>v_scale</code> is larger than <code>a_scale</code>, then the velocity limit is constraining the duration.</p>
<pre style="background-color:#1e1e1e;color:#dcdcdc;"><code><span>v_scale = abs(3π/4) / 0.104719755 = 22.5
</span><span>a_scale = sqrt(abs(3π)/0.0523599) = 13.417
</span></code></pre>
<p>In this case, the velocity limit is the dominating constraint. <em>The time-optimal duration is 22.5 seconds.</em></p>
<p>We can verify by recalculating the polynomial coefficients with the new duration.</p>
<p>$$
\begin{aligned}
A &= (2 \cdot -(\pi/2)/22.5)/22.5^2 = -0.00027580511 \newline
B &= (3 \cdot (\pi/2)/22.5)/22.5 = 0.00930842267 \newline
C &= 0 \newline
D &= 0 \newline
\end{aligned}
$$</p>
<center>
<iframe src="https://www.desmos.com/calculator/uarkx6wols?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
</center>
<p><em>Figure: Scaled Joint Position, Velocity, and Acceleration over Time</em></p>
<p>We can see that the joint at \(t = 22.5s\) has position \(Q = \pi/2 >rad\) and velocity \(v = 0 >rad/s\). The maximum velocity is at \(t = 11.25s\) with \(v = 0.104719755 >rad/s\). The maximum acceleration is at \(t = 0\) and \(t = 22.5s\) with \(a = 0.0186 >rad/s^2\) and \(a = -0.0186 >rad/s^2\), respectively. The joint velocity and acceleration constraints are satisfied. \(\blacksquare\)</p>
<hr />
<h3 id="task-space-constraints">Task Space Constraints</h3>
<p>Let's add another constraint. Let's say the frame attached to the tip of the joint has maximum translational speed and angular velocity components.</p>
<p><img src="https://drive.google.com/uc?export=view&id=1SyYM1NDXyL0JCjo4HGCioj08uTcVwQxJ" alt="" />
<em>Figure: Diagram of a frame at the joint tip. The frame is right-handed, i.e. Z points out of the page.</em></p>
<p>$$
\begin{aligned}
\dot{X}_{max} &= 100mm/s \newline
\dot{Y}_{max} &= 100mm/s \newline
\dot{Z}_{max} &= 100mm/s \newline
\dot{R_x}_{max} &= 9°/s \newline
\dot{R_y}_{max} &= 9°/s \newline
\dot{R_z}_{max} &= 9°/s \newline
\end{aligned}
$$</p>
<p><em>Aside:</em> I say <em>components</em> because e.g. a velocity vector moving with \(\dot{X} = \dot{Y} = \dot{Z} = 100mm/s\) would actually be moving at \(\sqrt{(100²+100²+100²)} ~= 173mm/s\). One could certainly solve for a velocity vector constraint, too.</p>
<p>Similar to joint space constraints, we can meet task space constraints by scaling the duration of the trajectory, but we need to know the relation from joint space to task space.</p>
<p>The relation from joint space to task space is known as <em><a href="https://en.wikipedia.org/wiki/Forward_kinematics">forward kinematics</a></em>. Conversion from joint position to task space position is <em>forward position</em>, and conversion from joint velocity to task space velocity is <em>forward velocity</em>. This topic is widely covered elsewhere.</p>
<p>Let's say our robot joint has position \(Q\), angular velocity \(\dot{Q}\) (also known as \(V(t)\)), and radius \(r\) from its center of rotation to the tip frame. Then the following relations apply:</p>
<p>$$
\begin{aligned}
X &= r \cdot cos(Q) \newline
Y &= r \cdot sin(Q) \newline
Z &= 0 \newline
R_x &= 0 \newline
R_y &= 0 \newline
R_z &= Q \newline
\newline
\dot{X} &= -\dot{Q} \cdot r \cdot sin(Q) \newline
\dot{Y} &= \dot{Q} \cdot r \cdot cos(Q) \newline
\dot{Z} &= 0 \newline
\dot{Rx} &= 0 \newline
\dot{Ry} &= 0 \newline
\dot{Rz} &= \dot{Q} \newline
\end{aligned}
$$</p>
<p>For example, if \(r = 1 >meter\), \(Q = 0 >rad\), and \(Qdot = \pi > rad/s\), then</p>
<p>$$
\begin{aligned}
X &= 1m \newline
Y &= 0m \newline
R_z &= 0m \newline
\dot{X} &= 0 >m/s \newline
\dot{Y} &= 1 >m/s \newline
\dot{R_z} &= \pi > rad/s \newline
\end{aligned}
$$</p>
<p>For another example, if \(r = 1 >meter\), \(Q = \pi/2 >rad\), and \(Qdot = \pi > rad/s\), then</p>
<p>$$
\begin{aligned}
X &= 0m \newline
Y &= 1m \newline
R_z &= 0m \newline
\dot{X} &= -1 >m/s \newline
\dot{Y} &= 0 >m/s \newline
\dot{R_z} &= \pi > rad/s \newline
\end{aligned}
$$</p>
<p>For details, see this <a href="https://robotacademy.net.au/masterclass/robotic-arms-and-forward-kinematics/?lesson=260">video lecture</a>.</p>
<p>Going back to our 1-second trajectory, since the joint velocity is a parabola, which is symmetric, the maximum occurs at any of \(t = 0\), \(t = 0.5\), or \(t = 1\). Since \(V_{init} = V_{final} = 0\), the maximum occurs at \(t = 0.5\). This results in the following task space velocities:</p>
<p>$$
\begin{aligned}
Q(0.5) &= -\pi t³ + \frac{3\pi}{2}t² = \pi/4 >rad \newline
V(0.5) &= -3\pi(1/2)² +3\pi/2 = \frac{3\pi}{4} rad/s \newline
\dot{X}_{max} &= \frac{3\pi}{4} rad/s \cdot 1m \cdot cos(\pi/4) = 1.67m/s = 1670 >mm/s \newline
\dot{Y}_{max} &= \frac{3\pi}{4} rad/s \cdot 1m \cdot sin(\pi/4) = 1.67m/s = 1670 >mm/s \newline
\dot{Z}_{max} &= 0 >mm/s \newline
\dot{R_x} &= 0 >rad/s \newline
\dot{R_y} &= 0 >rad/s \newline
\dot{R_z} &= \frac{3\pi}{4} >rad/s \newline
\end{aligned}
$$</p>
<p>Dividing by the given task space constraints yields the following ratios:</p>
<p>$$
\begin{aligned}
X_{ratio} &= 1670/100 = 16.7 \newline
Y_{ratio} &= 1670/100 = 16.7 \newline
Z_{ratio} &= 0 \newline
R_{x_{ratio}} &= 0 \newline
R_{y_{ratio}} &= 0 \newline
R_{z_{ratio}} &= \frac{\frac{3\pi}{4} >rad/s}{9°/s} = 15 \newline
\end{aligned}
$$</p>
<p>The maximum task space ratio is \(16.7\), which is less than the previous value of \(v_{scale} = 22.5\). <em>The previous scaled trajectory duration of 22.5s also satisfies the given task space constraints.</em> \(\blacksquare\)</p>
<hr />
<h3 id="multiple-joints">Multiple Joints</h3>
<p>The same approach applies to robots with more than one joint. </p>
<p>In this case, if we have \(m\) joints, then we will have \(m\) cubic splines, and \(a_{scale}\) and \(v_{scale}\) must be calculated for each joint. </p>
<p>The scale resulting from dividing the forward velocity by the task space limit is also calculated. The maximum of the joint space ratios and the task space ratio yields the optimal trajectory duration.</p>
<h4 id="example">Example</h4>
<p>Consider adding a second joint to the previous example to create a two-joint manipulator. This joint has the same velocity and acceleration limits.</p>
<p>$$
\begin{aligned}
V_{limit} &= 0.104719755 > rad/s \newline
A_{limit} &= 0.0523599 > rad/s^2
\end{aligned}
$$</p>
<p><img src="https://drive.google.com/uc?export=view&id=1YFmbgUtKW6srb9yDHVx6TMgpF5-3maH1" alt="" />
<em>Figure: A robot with two joints.</em></p>
<p><a href="https://www.desmos.com/calculator/mnga15rnud">Simulate this robot on Desmos</a>. Use the \(q_1\) and \(q_2\) sliders.</p>
<p>The first spline is unchanged.</p>
<p>$$
\begin{array}{c}
\begin{aligned}
Q_{init_1} &= 0 & A_1 &= -\pi \newline
Q_{final_1} &= \pi/2 & B_1 &= 3\pi/2 \newline
V_{init_1} &= 0 & C_1 &= 0 \newline
V_{final_1} &= 0 & D_1 &= 0 \newline
\end{aligned}
\end{array}
$$</p>
<p>Here is the second spline. </p>
<p>$$
\begin{array}{c}
\begin{aligned}
Q_{init_2} &= \pi/2 & A_2 &=2\pi \newline
Q_{final_2} &= -\pi/2 & B_2 &= -3\pi \newline
V_{init_2} &= 0 & C_2 &= 0 \newline
V_{final_2} &= 0 & D_2 &= \pi/2 \newline
\end{aligned}
\end{array}
$$</p>
<p>Here is the relation of joint space to task space.</p>
<p>$$
\begin{aligned}
X &= r_1 \cdot cos(Q_1) + r_2 \cdot cos(Q_1 + Q_2) \newline
Y &= r_1 \cdot sin(Q_1) + r_2 \cdot sin(Q_1 + Q_2) \newline
Z &= 0 \newline
R_x &= 0 \newline
R_y &= 0 \newline
R_z &= Q_1 + Q_2 \newline
\newline
\dot{X} &= -\dot{Q_1} \cdot r_1 \cdot sin(Q_1) - \dot{Q_1} \cdot \dot{Q_2} \cdot r_2 \cdot sin(Q_1 + Q_2) \newline
\dot{Y} &= \dot{Q_1} \cdot r_1 \cdot cos(Q_1) + \dot{Q_1} \cdot \dot{Q_2} \cdot r_2 \cdot cos(Q_1 + Q_2)\newline
\dot{Z} &= 0 \newline
\dot{Rx} &= 0 \newline
\dot{Ry} &= 0 \newline
\dot{Rz} &= \dot{Q_1} + \dot{Q_2}\newline
\end{aligned}
$$</p>
<p>For details, see the derivation of position <a href="https://robotacademy.net.au/masterclass/robotic-arms-and-forward-kinematics/?lesson=262">here</a> and the derivation of velocity <a href="https://robotacademy.net.au/masterclass/velocity-kinematics-in-2d/?lesson=321">here</a>.</p>
<p>We now find the maximum velocity, acceleration, and resulting time scale for each joint.</p>
<p><em>Note:</em> The topic of finding polynomial minima or maxima is well-covered elsewhere and can be deferred to a <a href="https://en.wikipedia.org/wiki/Comparison_of_linear_algebra_libraries">good algebra library</a>. Here, we just use Desmos.</p>
<center>
<iframe src="https://www.desmos.com/calculator/lt2t9hn9jd?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
</center>
<p><em>Figure: Joint-space velocities, two-joint manipulator.</em></p>
<h5 id="first-joint">First Joint</h5>
<p>$$
\begin{aligned}
V_{1_{max}} &= 3\pi/4, t = 0.5s \newline
A_{1_{max}} &= 3\pi, t = 0s \newline
&= -3\pi, t = 1s \newline
V_{1_{scale}} &= \frac{|3π/4|}{0.10471975} = 22.5 \newline
A_{1_{scale}} &= \sqrt{\frac{|3π|}{0.0523599}} = 13.417 \newline
\end{aligned}
$$</p>
<h5 id="second-joint">Second Joint</h5>
<p>$$
\begin{aligned}
V_{2_{max}} &= 3\pi/2, t = 0.5s \newline
A_{2_{max}} &= 6\pi, t = 0s \newline
&= -6\pi, t = 1s \newline
V_{2_{scale}} &= \frac{|3π/2|}{0.10471975} = 45 \newline
A_{2_{scale}} &= \sqrt{\frac{|6π|}{0.0523599}} = 18.974 \newline
\end{aligned}
$$</p>
<p>Without considering task space velocity, the time-optimal duration is \(1s \cdot max(22.5, 13.417, 45, 18.974)=45s\). Clearly, since the second joint has twice as far to rotate as the first joint, it constrains the duration of the movement.</p>
<p>Let's now consider task space velocity.</p>
<center>
<iframe src="https://www.desmos.com/calculator/bkieqkwpew?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
</center>
<p><em>Figure: Task-space velocities, two-joint manipulator.</em></p>
<p>$$
\begin{aligned}
\dot{X}_{max} &= |3.245|m/s, t=0.3624s \newline
\dot{Y}_{max} &= |-3.245|m/s, t=0.6376s \newline
\dot{R_z}_{max} &= -3\pi/4 rad/s, t=0.5s \newline
X_{ratio} &= 3245/100 = 32.45 \newline
Y_{ratio} &= 3245/100 = 32.45 \newline
R_{z_{ratio}} &= \frac{\frac{3\pi}{4} >rad/s}{9°/s} = 15 \newline
\end{aligned}
$$</p>
<p>Since \(32.45 < 45\), the joint space constraints dominate the task space constraints. <em>The time-optimal duration is \(1s \cdot max(45, 32.45, 15)=45s\).</em> \(\blacksquare\)</p>
<hr />
<h3 id="longer-paths">Longer Paths</h3>
<p>If a path \(\pmb{Q}_j\) of length \(n | n>2\) is given for joint \(j\), i.e. intermediate points between \(Q_{init}\) and \(Q_{final}\) are given, and if the joint begins and ends with zero velocity (\(V_{init} = V_{final} = 0\)), then by enforcing the constraints of continuity on velocity and acceleration, the intermediate point velocities can be calculated with a system of linear equations following the method described by Melchiorri [1]. </p>
<h4 id="example-1">Example</h4>
<p>Consider an extension of the previous 2-joint trajectory, where each joint passes through three positions: an initial position, an intermediate position, and a final position. </p>
<p>$$
\begin{aligned}
\pmb{Q}_1 &= [Q_{1_{init}} Q_{1_{intermediate}} Q_{1_{final}}] \newline
\pmb{Q}_2 &= [Q_{2_{init}} Q_{2_{intermediate}} Q_{2_{final}}] \newline
\end{aligned}
$$</p>
<p>Let the given intermediate positions be</p>
<p>$$
\begin{aligned}
Q_{1_{intermediate}} &= \pi/4 \newline
Q_{2_{intermediate}} &= 0
\end{aligned}
$$</p>
<p>...along with the initial and final velocities.</p>
<p>\(V_{1_{init}} = V_{1_{final}} = V_{2_{init}} = V_{2_{final}}= 0\)</p>
<p>We must find the intermediate velocities \(V_{1_{intermediate}}\) and \(V_{2_{intermediate}}\). We can do this by solving the system</p>
<p>$$
A\pmb{v} = \pmb{c}
$$</p>
<p>where</p>
<p>$$
A=
\begin{bmatrix}
2(T_1+T_2) & T_1 \newline
T_3 &2(T_2+T_3) &T_2 \newline
& & \ddots \ddots \ddots \newline
& & & T_{n-2} & 2(T_{n-3}+T_{n-2}) & T_{n-3} \newline
& & & & T_{n-1} & 2(T_{n-2} + T_{n-1}) \newline
\end{bmatrix}
$$</p>
<p>$$
\pmb{v} =
\begin{bmatrix}
V_2 \newline
V_3 \newline
\vdots \newline
V_{n-2} \newline
V_{n-1} \newline
\end{bmatrix}
$$</p>
<p>$$
\pmb{c} =
\begin{bmatrix}
\frac{3}{T_1T_2}[T_1^2(Q_3-Q_2)+T_2^2(Q_2-Q_1)] \pmb{- T_2V_1} \newline
\frac{3}{T_2T_3}[T_2^2(Q_4-Q_3)+T_3^2(Q_3-Q_2)] \newline
\vdots \newline
\frac{3}{T_{n-3}T_{n-2}}[T_{n-3}^2(Q_{n-1}-Q_{n-2})+T_{n-2}^2(Q_{n-2}-Q_{n-3})] \newline
\frac{3}{T_{n-2}T_{n-1}}[T_{n-2}^2(Q_{n}-Q_{n-1})+T_{n-1}^2(Q_{n-1}-Q_{n-2})] \pmb{- T_{n-2}V_n}\newline
\end{bmatrix}
$$</p>
<p>and</p>
<p>$$
\begin{aligned}
T_i &= Duration_i & \text{// The duration of segment \(i\)} \newline
Q_i &= Q_{i_{init}} & \text{ // The initial position of segment \(i\) } \newline
V_i &= V_{i_{init}} & \text{ // The initial velocity of segment \(i\) } \newline
V_n &= V_{n_{final}} & \text{ // The final velocity of the last segment } \newline
\end{aligned}
$$</p>
<p><em>Reminder</em>: There are \(n-1\) splines interpolating \(n\) control points (also called knots), and our indexing starts at \(1\), not \(0\). Therefore, \(i==1\) refers to the first spline, and \(i == n-1\) refers to the last spline.</p>
<p><em>Aside:</em> <a href="https://github.com/roboticslibrary/rl/blob/cba76ed3e54676d430205a0dfdf03ce33ed3850c/src/rl/math/Spline.h#L108">Here</a> is an example implementation of the above algorithm. </p>
<h5 id="joint-1">Joint 1</h5>
<p>We solve for \(V_{1_{intermediate}}\).</p>
<p>$$
\begin{array}{ccc}
\begin{bmatrix}
2(T_1+T_2)
\end{bmatrix}
\begin{bmatrix}
V_{1_{intermediate}}
\end{bmatrix}
&=
\begin{bmatrix}
\frac{3}{T_1T_2}[T_1^2(Q_{1_{final}}-Q_{1_{intermediate}})+T_2^2(Q_{1_{intermediate}}-Q_{1_{init}})]-T_2 \cdot V_{1_{init}}
\end{bmatrix}
\end{array}
$$</p>
<p>If we choose again arbitrarily that each segment should have 1 second of duration, then \(T_1=1\) and \(T_2=1\). Then</p>
<p>$$
\begin{aligned}
\begin{bmatrix}
2(1+1)
\end{bmatrix}
\begin{bmatrix}
V_{1_{intermediate}}
\end{bmatrix}
&=
\begin{bmatrix}
\frac{3}{1 \cdot 1}[1^2(\pi/2-\pi/4)+1^2(\pi/4-0)]-2\cdot 0
\end{bmatrix}
\newline
4 \cdot V_{1_{intermediate}}&=
\begin{bmatrix}
3[\pi/4+\pi/4]
\end{bmatrix}
\newline
V_{1_{intermediate}} &= 3\pi/8 \ rad/s
\end{aligned}
$$</p>
<p>We can now plot the two splines.</p>
<p>$$
\begin{aligned}
Duration_{1 \rightarrow 2} &= 1 \newline
Displacement_{1 \rightarrow 2} &= \pi/4 \newline
A_{1 \rightarrow 2} &= \frac{(2 \cdot - \pi/4 + 0 + 3\pi/8)}{1^2} &&= -\pi/8\newline
B_{1 \rightarrow 2} &= \frac{(3 \cdot \pi/4 - 2 \cdot 0 - 3\pi/8)}{1} &&= 3\pi/8\newline
C_{1 \rightarrow 2} &= V_{init} &&= 0\newline
D_{1 \rightarrow 2} &= Q_{init} &&= 0\newline
\end{aligned}
$$</p>
<p>$$
\begin{aligned}
Duration_{2 \rightarrow 3} &= 1 \newline
Displacement_{2 \rightarrow 3} &= \pi/4 \newline
A_{2 \rightarrow 3} &= \frac{(2 \cdot - \pi/4 + 3\pi/8 + 0)}{1^2} &&= -\pi/8\newline
B_{2 \rightarrow 3} &= \frac{(3 \cdot \pi/4 - 2 \cdot 3\pi/8 - 0)}{1} &&= 0\newline
C_{2 \rightarrow 3} &= V_{init} &&= 3\pi/8\newline
D_{2 \rightarrow 3} &= Q_{init} &&= \pi/4\newline
\end{aligned}
$$</p>
<center>
<iframe src="https://www.desmos.com/calculator/2vstdsov0i?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
</center>
<p><em>Figure: Two cubic splines for joint 1. Can you see where they meet? Hint: Each spline has 1s of duration.</em></p>
<p>We only see one spline, but there are actually two. The first spline is valid on the interval \(t=(0,1)\), and the second spline is valid on the interval \(t=(1,2)\). The two splines are identical because they have the same duration, displacement, and absolute change in velocity. Therefore, while we chose to use two splines, this movement could have been interpolated by a single spline. </p>
<h5 id="joint-2">Joint 2</h5>
<p>We solve for \(V_{2_{intermediate}}\).</p>
<p>$$
\begin{aligned}
\begin{bmatrix}
2(1+1)
\end{bmatrix}
\begin{bmatrix}
V_{2_{intermediate}}
\end{bmatrix}
&=
\begin{bmatrix}
\frac{3}{1 \cdot 1}[1^2(-\pi/2-0)+1^2(0-\pi/2)]-2\cdot 0
\end{bmatrix}
\newline
4 \cdot V_{2_{intermediate}}&=
\begin{bmatrix}
3[-\pi/2-\pi/2]
\end{bmatrix}
\newline
V_{2_{intermediate}} &= -3\pi/4 \ rad/s
\end{aligned}
$$</p>
<p>$$
A = \frac{(2 \cdot -Displacement / Duration + V_{init} + V_{final})}{Duration^2} \newline
B = \frac{(3 \cdot Displacement / Duration - 2 \cdot V_{init} - V_{final})}{Duration} \newline
$$</p>
<p>$$
\begin{aligned}
Duration_{1 \rightarrow 2} &= 1 \newline
Displacement_{1 \rightarrow 2} &= -\pi/2 \newline
A_{1 \rightarrow 2} &= \frac{(2 \cdot - (- \pi/2) + 0 + (- 3\pi/4))}{1^2} &&= \pi/4\newline
B_{1 \rightarrow 2} &= \frac{(3 \cdot (- \pi/2) - 2 \cdot 0 - (-3\pi/4))}{1} &&= -3\pi/4\newline
C_{1 \rightarrow 2} &= V_{init} &&= 0\newline
D_{1 \rightarrow 2} &= Q_{init} &&= \pi/2\newline
\end{aligned}
$$</p>
<p>$$
\begin{aligned}
Duration_{2 \rightarrow 3} &= 1 \newline
Displacement_{2 \rightarrow 3} &= -\pi/2 \newline
A_{2 \rightarrow 3} &= \frac{(2 \cdot - (-\pi/2) + (- 3\pi/4) + 0)}{1^2} &&= \pi/4\newline
B_{2 \rightarrow 3} &= \frac{(3 \cdot (- \pi/2) - 2 \cdot (-3\pi/4) - 0)}{1} &&= 0\newline
C_{2 \rightarrow 3} &= V_{init} &&= -3\pi/4\newline
D_{2 \rightarrow 3} &= Q_{init} &&= 0\newline
\end{aligned}
$$</p>
<center>
<iframe src="https://www.desmos.com/calculator/ouilnuznpg?embed" width="500px" height="500px" style="border: 1px solid #ccc" frameborder=0></iframe>
</center>
<p><em>Figure: Two cubic splines for joint 2. They meet at \(t=1s\).</em></p>
<h4 id="scaling-longer-trajectories">Scaling Longer Trajectories</h4>
<p>In order to satisfy joint-space and task-space velocity and acceleration limits, the \(m(n-1)\) resulting cubic splines (\(m=\) number of joints, \(n\) = number of control points) must each be scaled by the method described previously. </p>
<p><em>Pseudocode:</em></p>
<pre data-lang="c" style="background-color:#1e1e1e;color:#dcdcdc;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#608b4e;">// segments: sequence of splines, lenth n - 1
</span><span style="color:#608b4e;">// (n == number of interpolated points)
</span><span style="color:#608b4e;">// task_space_limit: e.g. Xlim = 100, Ylim = 100mm/s, etc.
</span><span style="color:#569cd6;">void </span><span>scale(segments, task_space_limit)</span><span style="color:#569cd6;">:
</span><span>
</span><span> </span><span style="color:#569cd6;">for</span><span> segment in segments</span><span style="color:#569cd6;">:
</span><span>
</span><span> joints = segment.joints </span><span style="color:#608b4e;">// joints: container of size m
</span><span>
</span><span> </span><span style="color:#608b4e;">// get_scale: See Joint Constraints section
</span><span> joint_space_ratio = joints.max(joint => joint.get_scale())
</span><span> task_space_ratio = joints.forward_velocity() / task_space_limit
</span><span>
</span><span> r_max = max(joint_space_ratio, task_space_ratio)
</span><span> scale(segment, r_max)
</span></code></pre>
<p><em>Above:</em> For each segment \(i\), \(m\) ratios are calculated, one for each joint, and the task space ratio is calculated. The maximum ratio \(r_{max}\) is selected. Then all \(m\) splines at segment \(i\) are scaled by \(r_{max}\).</p>
<p><em>Pseudocode:</em></p>
<pre data-lang="c" style="background-color:#1e1e1e;color:#dcdcdc;" class="language-c "><code class="language-c" data-lang="c"><span style="color:#608b4e;">// Scale the given spline
</span><span style="color:#569cd6;">void </span><span>scale(segment, ratio)</span><span style="color:#569cd6;">:
</span><span>
</span><span> segment.duration *= ratio </span><span style="color:#608b4e;">// Update duration
</span><span>
</span><span> </span><span style="color:#608b4e;">// Update velocity
</span><span> </span><span style="color:#569cd6;">if </span><span>(segment.is_first()) spline.Vinit /= ratio
</span><span> </span><span style="color:#569cd6;">if </span><span>(segment.is_last()) spline.Vfinal /= ratio
</span><span> </span><span style="color:#569cd6;">if </span><span>(segment.is_intermediate()) spline.Vintermediate /= ratio
</span><span>
</span><span> forward_propagate(segment.next);
</span><span> segment.compute_velocities() </span><span style="color:#608b4e;">// e.g. find new Vintermediate
</span><span> segment.compute_coefficients() </span><span style="color:#608b4e;">// e.g. get A B C D
</span><span>
</span><span style="color:#608b4e;">// Update segment start and finish times
</span><span style="color:#569cd6;">void </span><span>forward_propagate(segment)</span><span style="color:#569cd6;">:
</span><span>
</span><span> </span><span style="color:#569cd6;">while </span><span>(next_segment != null)</span><span style="color:#569cd6;">:
</span><span> next_segment.forward_propagate() </span><span style="color:#608b4e;">// e.g. Tinit = Tinit_prev + Duration
</span><span> next_segment= segment.next
</span><span>
</span></code></pre>
<p><em>Above:</em> When a segment is scaled, the following changes occur </p>
<ol>
<li>the segment duration </li>
<li>the segment initial or final velocity (or both)</li>
</ol>
<p><em>Forward propagation</em>: All segments after the scaled segment are affected: the start time of each spline becomes the finishing time of the previous. These values propagate to the last spline.</p>
<p>Finally, the resulting intermediate velocities and polynomial coefficients must be recomputed. </p>
<hr />
<h3 id="references">References</h3>
<p>[1] Biagiotti, L., & Melchiorri, C. (2009). <em><a href="https://www.blogger.com/blog/post/edit/8646226552989795436/981000181203813289#">Trajectory Planning for Automatic Machines and Robots</a></em>. Berlin, Heidelberg: Springer Berlin Heidelberg.</p>
Edit TCS Treadmill Runs with tcxmillFri, 18 Oct 2019 00:00:00 +0000Unknown
https://www.slater.dev/edit-tcx-treadmill-runs-with-tcxmill/
https://www.slater.dev/edit-tcx-treadmill-runs-with-tcxmill/<p>Get tcxmill <a href="https://www.github.com/nref/tcxmill">here</a></p>
<p>Editing activity distance is one of the <a href="https://support.strava.com/hc/en-us/search?utf8=%E2%9C%93&query=edit+distance&commit=Search">most requested Strava features</a>, and I created a way to do it for treadmill runs.</p>
<p>If you, like me, track treadmill workouts with a Garmin watch which uploads to Strava, then you've likely experienced this kind of data:</p>
<p><img src="https://1.bp.blogspot.com/-Hs1nLrF0jCo/XaoMEmiFOdI/AAAAAAAAJrc/VUXjSj1I4-weLjjnnvmsb-dFK9yD0iS_wCLcBGAsYHQ/s640/summary-before.png" alt="" /></p>
<p><img src="https://1.bp.blogspot.com/-LxgF0tK2jR8/XaoMCjKfulI/AAAAAAAAJrk/HCwWRRdGDygeulmOJFUSRQ2Ri0zQ0_VpACEwYBhgL/s640/charts-before.png" alt="" /></p>
<p>Above is a recent treadmill workout of mine. I ran 30km, i.e. 18.6 miles. My <a href="https://buy.garmin.com/en-US/US/p/621922#overview">Forerunner 945</a> and <a href="https://buy.garmin.com/en-US/US/p/530376#overview">HRM-Run</a> recorded an extraordinary 42.54 miles at an absurd pace of 3:12/mi.</p>
<p>The impossibility of this metric is hyperbolized by Eliud Kipchoge subsequently <a href="https://www.nytimes.com/2019/10/12/sports/eliud-kipchoge-marathon-record.html">breaking the two-hour marathon barrier</a> at a pace of 4:34/mi. To avoid ridicule from my Strava friends, I gladly kept this activity private until I could correct it.</p>
<p>In addition to being wildly inaccurate, the pace data is also noisy.</p>
<p>Below is the corrected data after running tcxmill. You can see that the distance and pace are correct, and the workout structure is reflected accurately.</p>
<p><img src="https://1.bp.blogspot.com/-mbXDGITsPkY/XaoMEFxmcCI/AAAAAAAAJrs/Vs5GZBrnMdIHL74BsxqSTevBBEFxSB4mgCEwYBhgL/s640/summary-after.png" alt="" /></p>
<p><img src="https://1.bp.blogspot.com/-9-xtDOatTE4/XaoMCnOiMzI/AAAAAAAAJrg/-_9B6pqWb-4h-KK2Hlbn8lRVkk804BiKQCEwYBhgL/s640/charts-after.png" alt="" /></p>
<p>The only requirements are that you know and follow your workout structure. Instructions are on <a href="https://www.github.com/nref/tcxmill">the GitHub page</a>. Go ahead and try it out!</p>
Renato Canova in ValenciaMon, 26 Aug 2019 00:00:00 +0000Unknown
https://www.slater.dev/renato-canova-in-valencia/
https://www.slater.dev/renato-canova-in-valencia/<p>Several sites link to Renato Canova's 2017 talk in Valencia. The video is still on YouTube, but the original link to the slides is broken.</p>
<p>I contacted the Serrano athletics club on Facebook, which happily procured the file within a day. Thanks, guys!</p>
<p><a href="https://drive.google.com/file/d/1g__fXGd8wsMsBsH02fzDwSvTDXsw-I2w/view">Here is the PDF</a>.</p>