Jekyll2021-07-05T18:37:49+00:00https://www.mathematastic.com/feed.xmlMathematasticA range of ramblings regarding math, computers, and the rule of three.Henry ConklinFractal Trees: Part 2, Boundedness2020-09-06T00:00:00+00:002020-09-06T00:00:00+00:00https://www.mathematastic.com/projects/2020/09/06/trees-part-2<p>When does a fractal tree stay bounded, and when does it explode off to infinity? In <a href="/projects/2020/08/05/trees-part-1.html">the last post</a> we looked at a few fractal trees and set up an interactive demo so we could get some intuition on what fractal trees and how their parameters influence how they look. Now let’s dive into some of the math behind them.</p>
<p>If you play around with the <a href="/projects/2020/08/05/trees-part-1.html#tree-demo">demo</a> you’ll see that as you increase the scale parameters the tree quickly expands to fill the entire screen (though the demo automatically rescales things to counteract that somewhat). The demo only goes out to 100 layers from the root which is usually enough to be visually indistinguishable from an arbitrary number of iterations. However, in certain cases it may seem like the tree stops growing when really it has reached the limits of the demo. So the question is: when does the tree grow endlessly and when does it stay bounded? In order to answer this we’ll need to set up some formal definitions so that we have a firm base to build from.</p>
<p class="definition" id="definition-tree">A <em>tree</em> is a structure on the complex plane defined by four parameters: two rotation angles \(\theta_1, \theta_2 \in [-\pi, \pi]\) and two scaling factors \(s_1, s_2 \in \mathbb{R}^+\). You could alternatively use two arbitrary complex numbers, but keeping the rotation and scale separate is useful later. A tree is composed of <em>branches</em>, a branch is a line segment on the complex plane and will be written as \((x,y)\). The branches that make up a given tree are defined as follows:</p>
<ol>
<li>\((0, i)\) is a branch in the tree</li>
<li>If \((x,y)\) is a branch in the tree, then \((y, y + s_1 e^{i\theta_1} (y-x))\) and \((y, y + s_2 e^{i\theta_2} (y-x))\) are also branches in the tree</li>
</ol>
<p>In other words, at the end of each branch we can add two more branches by rotating and scaling the previous branch according to the tree’s parameters. We could instead choose \((0,1)\) as the initial branch which might feel a bit more natural, but I like \((0, i)\) because it rotates the tree upright on a standard plot of the complex plane. This definition works well to showcase the recursive structure baked into these trees and is nicely compact and concise. However, it can be a bit difficult to work with when we want to start proving things. To that end, we’ll also set up an alternative definition based on infinite series which will let us take advantage of a lot of powerful pre-existing math.</p>
<p class="definition" id="definition-path">A <em>path</em> is a sequence \((d_k)_{k=1}^{\infty}\) over the set \(\{1,2\}\). It indicates a sequence of turns taken starting at the root of a tree, moving higher in the tree with each step.</p>
<p>Given a path \((d_k)_{k=1}^{\infty}\), let</p>
\[b_k =
\begin{cases}
i, & k = 0 \\
i \prod_{j=1}^{k} s_{d_j}e^{i\theta_{d_j}}, & k \geq 1 \\
\end{cases}\]
\[p_k = \sum_{j=0}^k b_j\]
<p class="lemma" id="lemma-path-implies-branch">For any path and its corresponding sequence \((p_k)_{k=0}^\infty\), \((p_k, p_{k+1})\) is a branch in the tree for all \(k \in \mathbb{Z}_{\geq 0}\)</p>
<details>
<summary>Show proof</summary>
<p>Proceed by induction.</p>
<p>We have that \(p_0 = i\) and \(p_1 = i + is_{d_1}e^{i\theta_{d_1}}\). Since \((0, i)\) is a branch in the tree, then \((p_0, p_1) = (i, i + s_{d_1}e^{i\theta_{d_1}}(i-0))\) is also a branch in the tree by the second rule in the definition of a tree.</p>
<p>Now, assume that \((p_{k-1}, p_{k})\) is a branch in the tree. We have that</p>
\[\begin{align}
&p_{k+1} - p_{k}\\
= &b_{k+1} \\
= &s_{d_{k+1}}e^{i\theta_{d_{k+1}}} b_{k} \\
= &s_{d_{k+1}}e^{i\theta_{d_{k+1}}} (p_k - p_{k-1})
\end{align}\]
<p>Therefore</p>
\[\begin{align}
&(p_k, p_{k+1}) \\
= &(p_k, p_k + p_{k+1} - p_k) \\
= &(p_k, p_k + s_{d_{k+1}}e^{i\theta_{d_{k+1}}} (p_k - p_{k-1}))
\end{align}\]
<p>And \((p_k, p_{k+1})\) is a branch in the tree.</p>
<p>Therefore, by induction \((p_k, p_{k+1})\) is a branch in the tree for all \(k\geq 0\). \(\blacksquare\)</p>
</details>
<p class="lemma" id="lemma-branch-implies-path">For every branch \((x,y)\) in the tree except \((0, i)\), there is at least one path and corresponding sequence \((p_k)_{k=0}^\infty\) such that \(x = p_k\) and \(y = p_{k+1}\) for some \(k \in \mathbb{Z}_{\geq 0}\).</p>
<details>
<summary>Show proof</summary>
<p>This is a fairly simple induction proof, but relies on <a href="https://en.wikipedia.org/wiki/Structural_induction">structural induction</a> rather than regular induction.</p>
<p>For the base case, we start with the children of the root: \((i, i + i s_1 e^{i\theta_1})\) and \((i, i + i s_2 e^{i\theta_2})\). Each of these can be reached with any path that starts with \(d_1=1\) or \(d_1=2\) respectively. In that case you have for \(k=0\), \(p_k = i\) and \(p_{k+1} = i + is_{d_1}e^{i\theta_{d_1}}\).</p>
<p>Now we assume that \((x,y)\) is a branch in the tree which can be reached by the path \((d_j)_{j=1}^\infty\) such that \(x=p_{k}\) and \(y=p_{k+1}\) for some \(k\). Another branch can be constructed using the rule involving \(s_1\) and \(\theta_1\) (the proof is identical for \(s_2\) and \(\theta_2\)). This new branch is then:</p>
\[\begin{align}
&(p_{k+1}, p_{k+1} + s_1e^{i\theta_1}(p_{k+1} - p_{k}))\\
=&(p_{k+1}, p_{k+1} + s_1e^{i\theta_1}(b_{k+1}))\\
\end{align}\]
<p>If we take any path \((f_j)_{j=1}^\infty\) such that \(f_j=d_j\) for \(j \leq k+1\) and \(f_{k+2} = 1\). Then we have:</p>
\[\begin{align}
&(p_{k+1}, p_{k+1} + s_1e^{i\theta_1}(b_{k+1}))\\
=&(p_{k+1}, p_{k+1} + b_{k+2}) \\
=&(p_{k+1}, p_{k+2})
\end{align}\]
<p>Therefore if a branch can be reached with a path then both of its children can also be reached by a path.</p>
<p>And so by structural induction, we have that all branches in the tree can be reached by at least one path. \(\blacksquare\)</p>
</details>
<p class="theorem" id="theorem-path-equiv">A branch can be reached by a path as defined above if and only if it is part of the tree.</p>
<p class="proof">Previous two lemmas</p>
<p>So what does all this mean? Essentially, we pick some path up the tree and specify it by the series of left and right turns you make each time a branch splits into two more branches. \((b_k)_{k=0}^{\infty}\) is then the branch you visit at each step in the path with one end translated down to the origin, and \((p_k)_{k=0}^{\infty}\) is the sequence of junction points you reach as you follow the path. Finally, you can reconstruct the path by connecting consecutive elements of the \((p_k)_{k=0}^{\infty}\) sequence. Most importantly, this view of picking a path up the tree is equivalent to the recursive definition.</p>
<p>Now that we have this all set up, we can put some bounds on our trees.</p>
<p class="definition" id="definition-tree-bounded">A tree is <em>bounded</em> if all branches \((x,y)\) in the tree are within some radius \(R\) of the origin. That is, \(\vert x \vert, \vert y \vert \leq R\) for all branches in the tree for some \(R \in \mathbb{R}^+\)</p>
<p class="lemma" id="lemma-sg1-unbounded">A tree with \(s_1 > 1\) or \(s_2 > 1\) is unbounded.</p>
<details>
<summary>Show proof</summary>
<p>We can prove this by contradiction. Suppose without loss of generality that \(s_1 > 1\) and that the tree is bounded with radius \(R\). Take the path \(d_k = 1\) for all \(k\), and corresponding sequence \((p_k)_{k=0}^\infty\). Let \(k > \log_{s_1} 2R\) then</p>
\[\begin{align}
b_k &= i\prod_{j=1}^k s_1 e^{i\theta_1} = i s_1^k e^{ik\theta_1} \\
\vert b_k \vert &= s_1^k > 2R
\end{align}\]
<p>That is, we can find a individual branch in the tree whose length is greater than \(2R\). Now, since we assumed the tree is bounded and \((p_{k-1}, p_k)\) is a branch in the tree, \(\vert p_{k-1} \vert, \vert p_k \vert \leq R\)</p>
\[\begin{align}
p_{k} &= b_{k} + p_{k-1} \\
\vert p_k \vert &= \vert b_k + p_{k-1} \vert \\
&\geq \vert b_k \vert - \vert p_{k-1} \vert \\
&> 2R - R \\
&=R \\
p_k &> R
\end{align}\]
<p>Which is a contradiction. Therefore, the tree cannot be bounded with \(s_1 > 1\) or \(s_2 > 1\). \(\blacksquare\)</p>
</details>
<p class="lemma" id="lemma-scale-lt-1-bounded">A tree with \(s_1 < 1\) and \(s_2 < 1\) is bounded</p>
<details>
<summary>Show proof</summary>
<p>Let \(\hat{s} = max(s_1, s_2)\). Note \(\vert b_0 \vert = \vert i \vert = 1\), and for \(k \geq 1\)</p>
\[\begin{align}
\vert b_k \vert =& \vert i \vert \prod_{j=1}^k \vert s_{d_j} e^{i\theta_{d_j}} \vert\\
&=\prod_{j=1}^k s_{d_j} \\
&=s_1^n s_2^m \text{ for some } n + m = k \\
&\leq \hat{s}^k \\
\end{align}\]
<p>Therefore \(\vert b_k \vert \leq \hat{s}^k\).</p>
\[\begin{align}
\vert p_k \vert &= \left\vert \sum_{j=0}^k b_j \right\vert \\
&\leq \sum_{j=0}^k \vert b_j \vert \\
&\leq \sum_{j=0}^k \hat{s}^j \\
&\leq \frac{1}{1-\hat{s}}
\end{align}\]
<p>Therefore, for any branch \((x, y)\) we have</p>
\[x, y \leq \frac{1}{1-\hat{s}}\]
<p>And the tree is bounded. \(\blacksquare\)</p>
</details>
<p>The remaining case is when either scale factor is exactly one. Within that case are a few interesting subcases. For one, if we have \(s_1 = 1\) and \(\theta_1 = 0\) we get a tree with straight vertical line and the tree is unbounded. With \(s_1 = 1\), \(\theta_1 \neq 0\), and \(s_2 < 1\), the branches that don’t shrink stay contained in circular loops and so you end up with lots of loops branching off of each other but the tree stays bounded. If \(s_1, s_2 = 1\) and \(\theta_1 \neq \theta_2\), then the tree is again unbounded. If \(s_1, s_2 = 1\) and \(\theta_1 = \theta_2\) we end up with a single circular loop which is bounded.</p>
<p class="lemma" id="lemma-scale-1-loop-radius">A tree with a scale factor \(s=1\) and rotation angle \(\theta \neq 0\) forms loops of radius \(\frac{1}{2} \sec \left( \frac{\pi - \theta}{2} \right)\)</p>
<details>
<summary>Show proof</summary>
<p>This can be shown geometrically.</p>
<p><img src="/images/fractal-tree/fractal-tree-loop-radius.png.webp" alt="Geometric diagram showing a proof of the formula for the loop radius" /></p>
<p><a href="https://www.geogebra.org/geometry/dgzrad7t" target="_blank">You can also play with this interactive version on GeoGebra.</a></p>
<p>Here we have two adjacent branches with the second scaled by 1 and rotated by \(\theta\). The dashed circle indicates that the two branches have equal length. We can draw the solid circle with the three endpoints of the two segments on the border because <a href="https://proofwiki.org/wiki/Three_Points_Describe_a_Circle" target="_blank">any three points describe a circle</a>.</p>
<p>In order to derive the formula we need to first show that the two angles labeled \(\phi\) are in fact equal. That can be done by connecting all three endpoints of the segments to the center of the solid circle and showing that the two triangles formed are congruent isosceles triangles which implies that the two angles are in fact equal.</p>
<p>Second, we use the fact that <a href="https://proofwiki.org/wiki/Perpendicular_Bisector_of_Chord_Passes_Through_Center" target="_blank">the perpendicular bisector of a chord passes through the center of the circle</a> to draw the right triangle shown in the diagram which connects the shared endpoint of the two segments, the center of the solid circle, and the midpoint of the second segment.</p>
<p>We can then use some basic trigonometry to derive the relationship:</p>
\[\frac{0.5}{r} = \cos(\phi)\]
<p>We can then use \(2 \phi + \theta = \pi \Rightarrow \phi = \frac{\pi - \theta}{2}\) and rearrange the equation to get:</p>
\[r = \left\vert \frac{1}{2} \sec \left( \frac{\pi - \theta}{2} \right) \right\vert\]
<p><img src="/images/fractal-tree/fractal-tree-loop-radius-graph.png.webp" alt="Plot of the loop radius formula" /></p>
<p>You can show that all the branches that come from these parameters lie on the same circle by rotating the entire diagram by \(\theta\) and repeating the same argument. \(\blacksquare\)</p>
<p>Note that as \(\theta\) approaches zero the formula gives us a radius that approaches infinity. A circle with a radius of infinity can be thought of as equivalent to a straight line which is what we get with \(\theta=0\).</p>
</details>
<p class="lemma" id="lemma-scale-lt-1-bounded">A tree with \(s_1 = 1, s_2 < 1\) and \(\theta_1 \neq 0\) is bounded.</p>
<details>
<summary>Show proof</summary>
<p>Take any branch \((x,y)\) and its corresponding path \((d_k)_{k=1}^{n}\).</p>
<p>The path can be broken into a stretches of ones and twos. That is, a pattern like \(1,...,2,...,1,...,2,...\) potentially with zero ones in the first stretch.</p>
<p>Every stretch of repeated ones is bounded since all such branches lie on a circle of radius \(r = \left\vert \frac{1}{2} \sec \left( \frac{\pi - \theta_1}{2} \right) \right\vert\). Therefore, any sum</p>
\[\left\vert \sum_{k=1}^{m} s_1^k e^{i k \theta_1} \right\vert \leq 2r = \left\vert \sec \left( \frac{\pi - \theta_1}{2} \right) \right\vert\]
<p>Similarly, every stretch of repeated twos is bounded due to <a href="#lemma-scale-lt-1-bounded">the lemma above showing trees with scale factor < 1 are bounded</a>.</p>
\[\left\vert \sum_{k=1}^{m} s_2^k e^{i k \theta_2} \right\vert \leq \frac{1}{1-s_2}\]
<p>Let \(x_k\) be sequence of run lengths for each stretch of ones and \(y_k\) be the sequence of run lengths for each stretch of twos. Then we can break apart the repeated pattern of a stretch of ones followed by a stretch of twos to put a bound on the whole series. Note that each subsequent sequence of twos adds some number of \(s_2\) factors, but we can put an upper bound on this by reducing to just one \(s_2\) per sequence of twos since \(s_2 < 1\). That lets us bound the sequence as follows:</p>
\[\begin{align}
\vert p_k \vert &= \left\vert i \sum_{j=0}^k b_j \right\vert = \left\vert \sum_{j=0}^k b_j \right\vert\\
&\leq \left\vert \sum_{j=0}^k s_2^{j} \left( \sum_{l=1}^{x_j} e^{i l \theta_1} + e^{i x_j \theta_1} \sum_{l=1}^{y_j} s_2^l e^{i l \theta_2} \right) \right| \\
&\leq \sum_{j=0}^k s_2^j \left( \left\vert \sum_{l=1}^{x_j} e^{i l \theta_1} \right\vert + \left\vert \sum_{l=1}^{y_j} s_2^l e^{i l \theta_2} \right\vert \right) \\
&\leq \sum_{j=0}^k s_2^j \left( \left\vert \sec \left( \frac{\pi - \theta_1}{2} \right) \right\vert + \frac{1}{1-s_2} \right) \\
&= \left( \sum_{j=0}^k s_2^j \right) \left( \left\vert \sec \left( \frac{\pi - \theta_1}{2} \right) \right\vert + \frac{1}{1-s_2} \right) \\
&\leq \frac{1}{1-s_2} \left( \left\vert \sec \left( \frac{\pi - \theta_1}{2} \right) \right\vert + \frac{1}{1-s_2} \right)
\end{align}\]
<p>Which is constant with respect to \(k\), therefore every branch in this tree is bounded. \(\blacksquare\)</p>
</details>
<p class="lemma" id="lemma-scale-1-unbounded">A tree \(s_1, s_2 = 1\) and \(\theta_1 \neq \theta_2\) is unbounded.</p>
<details>
<summary>Show proof</summary>
<p>The goal here is to construct a finite-length pattern which ends with a branch facing straight up (i.e. with an angle of 0) which doesn’t double back to the root of the tree. Repeating such a pattern of branches leads to a path which continues out in a way that avoids falling into a closed circle. To do this we’ll use a pattern from group theory called the <a href="https://en.wikipedia.org/wiki/Commutator">commutator</a>, i.e. \(a b a^{-1} b^{-1}\).</p>
<p>In particular we’re going to make use of the fact that the total rotation over a sequence of branches is independent of their order (i.e. commutative), while the final position is not. We’ll show that we can construct \(-\theta\) (or an approximation) as an integer multiple of \(\theta\) which will be the inverse in this case. Then we’ll show that the commutator pattern is a way to sequence these turns which satisfies the conditions above.</p>
<p>First, let’s investigate which angles we can construct with a given branching angle. Let \(\theta\) be a branching angle, it’s obvious that one application of this angle from the root gives us a rotation of \(\theta\). A second application gives us an angle of \(2 \theta\). Note that this isn’t the angle from the origin to the end of the branch, it’s the orientation of the branch itself. So obviously we can construct the angle \(k \theta\) for any \(k\), but eventually that wraps around past \(2 \pi\) so what unique angles does that actually give us? Well, we’ve constructed the <a href="https://en.wikipedia.org/wiki/Circle_group">Circle Group</a> so we can take some results from there. At this point there is a divide between angles which are a rational multiple of \(2\pi\) and angles which are an irrational multiple of \(2 \pi\). We’ll call an angle which is a rational multiple of \(2\pi\) a “rational angle” and an angle which is an irrational multiple of \(2\pi\) an “irrational angle.”</p>
<p>Multiples of rational angles will go to finitely many unique angles. In fact, if the angle is \(\frac{p}{q} 2\pi\) with \(\frac{p}{q}\) in reduced form, there will be \(q\) unique angles at \(\frac{k}{q} 2\pi\) for \(0 \leq k < q\). Notably, this set of unique angles is a subgroup of the circle group which means that for every angle that you can construct, you can also construct its negation (this is part of the definition of a group).</p>
<p>Multiples of irrational angles will go to infinitely many unique angles, and in fact any angle can be approximated arbitrarily well by an integer multiple of an irrational angle.</p>
<p>So, for a rational angle we can take \(\theta\) as \(x\) and \(-\theta\) as \(x^{-1}\). We know that \(-\theta\) can be constructed by repeated rotations by \(\theta\) by the argument above. For an irrational angle, we can take \(\theta\) and an arbitrarily close approximation of \(-\theta\) which we can also construct by repeated applications of \(\theta\).</p>
<p>We then can take the sequence:</p>
\[e^{i\theta_1} + e^{i\theta_1}e^{i\theta_2} + e^{i\theta_1}e^{i\theta_2} \sum_{j=1}^k e^{ij\theta_1} + e^{i\theta_1}e^{i\theta_2}e^{ik\theta_1} \sum_{j=1}^l e^{ij\theta_2}\]
<p>Where \(k, l\) are selected so that \(e^{ik\theta_1} = e^{-i\theta_1}\) and \(e^{il\theta_2} = e^{-i\theta_2}\) or at least approximately equal in the case of irrational angles. We can then reduce \(\sum_{j=1}^k e^{ij\theta_1}\) to \(-1\) as follows:</p>
\[\begin{align}
&\sum_{j=1}^k e^{ij\theta_1} \\
=&\frac{e^{i\theta_1}(1 - e^{ik\theta_1})}{1 - e^{i\theta_1}} \\
=&\frac{e^{i\theta_1}(1 - e^{-i\theta_1})}{1 - e^{i\theta_1}} \frac{1-e^{-i\theta_1}}{1-e^{-i\theta_1}} \\
=&\frac{-(2 - e^{i\theta_1} - e^{-i\theta_1} )}{2 - e^{i\theta_1} - e^{-i\theta_1}} = -1
\end{align}\]
<p>Which is undefined when \(e^{i\theta_1} = 1 \Rightarrow \theta_1 = 0\), but in that case the tree is unbounded anyway because we have a straight vertical line. Similar holds for \(\theta_2\). Now the sequence above becomes:</p>
\[\begin{align}
&e^{i\theta_1} + e^{i\theta_1}e^{i\theta_2} - e^{i\theta_1}e^{i\theta_2} - e^{i\theta_1}e^{i\theta_2}e^{-i\theta_1} \\
=&e^{i\theta_1} - e^{i\theta_2}
\end{align}\]
<p>This is non-zero whenever \(\theta_1 \neq \theta_2\). Note that the last term in this series, before all the simplification, is \(e^{i\theta_1}e^{i\theta_2}e^{-i\theta_1}e^{-i\theta_2} = 1\). Therefore, by repeating this sequence of branches we can travel arbitrarily far from the origin and the tree is unbounded. \(\blacksquare\)</p>
<p>This proof does gloss over the irrational angle case somewhat. To be more thorough you would need to show that a close approximation of \(e^{-i\theta}\) leads to a close approximation of the final term. However, the core argument doesn’t change so this case is mostly omitted for the sake of brevity in an already long proof.</p>
</details>
<p class="theorem" id="theorem-bounded">A tree is bounded if and only if one of the following is true (for some ordering of \(s_1, s_2 \text{ and } \theta_1, \theta_2\)):</p>
<ol>
<li>
\[s_1, s_2 < 1\]
</li>
<li>
\[s_1 = 1, s_2 < 1, \text{ and } \theta_1 \neq 0\]
</li>
<li>
\[s_1, s_2 = 1 \text{ and } \theta_1 = \theta_2 \neq 0\]
</li>
</ol>
<p class="proof">Previous lemmas</p>Henry ConklinWhen does a fractal tree stay bounded, and when does it explode off to infinity? In the last post we looked at a few fractal trees and set up an interactive demo so we could get some intuition on what fractal trees and how their parameters influence how they look. Now let’s dive into some of the math behind them.Fractal Trees: Part 1, Introduction2020-08-05T00:00:00+00:002020-08-05T00:00:00+00:00https://www.mathematastic.com/projects/2020/08/05/trees-part-1<p>This was originally going to be a small section in a short series building up to a simulation of evolving trees, but it has turned out to be extremely interesting on its own. I have a few parts worth of interesting math to go through related to fractal trees, but in this part we’ll just go through what they are, some cool examples, and an interactive demo where you can mess with the parameters and see what comes out.</p>
<h2 id="construction">Construction</h2>
<p>A fractal tree is part of a class of fractals that are composed of several copies of themselves. To build one start with a trunk of any length, then draw two branches coming out of the top of the trunk each slightly smaller than the trunk and rotated to point in a different direction. Then draw two more branches coming out each of the first two branches rotated and scaled in the same way and repeat forever. You can change four parameters: the scaling factor and angle for each branch every time a branch splits into two more branches. Messing with these parameters can get you some really interesting shapes.</p>
<h2 id="examples">Examples</h2>
<p>Some of these examples look a fair bit like actual trees like this half of a christmas tree:</p>
<p><img src="/images/fractal-tree/fractal-tree-half-christmas.png.webp" alt="Half a christmas tree" /></p>
<p>Or this tree with some oddly level leaves:</p>
<p><img src="/images/fractal-tree/fractal-tree-oddly-level-leaves.png.webp" alt="Oddly level leaves" /></p>
<p>You can also get some more fractally shapes like this infinite series of self-similar loops on loops:</p>
<p><img src="/images/fractal-tree/fractal-tree-loops-on-loops.png.webp" alt="Loops on loops" /></p>
<p>Or this angry-looking shark fin:</p>
<p><img src="/images/fractal-tree/fractal-tree-angry-shark-fin.png.webp" alt="Angry shark fin" /></p>
<p>You can also get some more well-known shapes like the <a href="https://en.wikipedia.org/wiki/Dragon_curve">Dragon Curve</a> (plus some scaffolding)</p>
<p><img src="/images/fractal-tree/fractal-tree-dragon-curve.png.webp" alt="The Dragon Curve" /></p>
<p>And the related Twin Dragon Curve</p>
<p><img src="/images/fractal-tree/fractal-tree-twin-dragon.png.webp" alt="The Twin Dragon Curve" /></p>
<p>You can also get this space-filling fractal that looks like a sheet of A4 paper with a ratio \(\sqrt{2}\) of between the side lengths.</p>
<p><img src="/images/fractal-tree/fractal-tree-a4-paper.png.webp" alt="A sheet of A4 paper" /></p>
<p>There are also some pretty normal but unexpected shapes like this octagon.</p>
<p><img src="/images/fractal-tree/fractal-tree-octagon.png.webp" alt="An octagon" /></p>
<h2 id="interactive-demo">Interactive Demo</h2>
<link rel="stylesheet" href="/demos/tree1/build/bundle.css" />
<script src="/demos/tree1/build/bundle.js" defer=""></script>
<div id="tree-demo" style="position:relative; width: 100%; height: 720px;">
</div>
<p><a href="/demos/tree1/">Full Screen</a></p>
<h2 id="how-does-it-work">How does it work?</h2>
<p>This demo draws the trees up to 100 iterations of branching. Given that the number of branches doubles each time, that’s about \(2^{100} \approx 10^{30}\) things to draw which is an impossible amount if you draw them individually. Instead of drawing individual branches, you can take advantage of the fact that the tree is made up of the trunk plus two copies of itself rotated and scaled by different amounts. Therefore you can draw two copies of the image itself from the previous iteration in order to increase the total height of the tree by one. Rather than the process taking an exponentially longer time with increased height, the process now scales linearly with height. The image is also persistent (rather than being redrawn every frame) so that I can progressively draw more iterations over time and take advantage of a few seconds of computation time rather than trying to cram everything into a 60th of a second.</p>
<p><a href="https://github.com/HenryWConklin/trees">Source code</a></p>Henry ConklinThis was originally going to be a small section in a short series building up to a simulation of evolving trees, but it has turned out to be extremely interesting on its own. I have a few parts worth of interesting math to go through related to fractal trees, but in this part we’ll just go through what they are, some cool examples, and an interactive demo where you can mess with the parameters and see what comes out.Harmonic Waves2019-10-27T00:00:00+00:002020-07-27T20:07:00+00:00https://www.mathematastic.com/projects/2019/10/27/harmonic-waves<p>I have been occasionally obsessed with this video for years and I finally sat down to deconstruct it. The video shows a row of 15 pendulums, with each pendulum being slightly shorter than the last. All of the pendulums are started swinging at once and they follow a kind of snaking pattern that devolves into chaos. However, they occasionally line up into two or three groups that swing together before falling out of phase again. After 60 seconds they all line up in one row and the pattern repeats. Have a watch:</p>
<p align="center"><iframe width="560" height="315" src="https://www.youtube-nocookie.com/embed/yVkdfJ9PkRQ" frameborder="0" allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture" allowfullscreen="" loading="lazy"></iframe></p>
<h2 id="how-does-it-work">How does it work?</h2>
<p>So how does this demo work? How can a set of independent pendulums alternate between complete chaos and perfectly aligned rows? There’s a hint on <a href="https://sciencedemonstrations.fas.harvard.edu/presentations/pendulum-waves">this page</a>:</p>
<blockquote>
<p>The period of one complete cycle of the dance is 60 seconds. The length
of the longest pendulum has been adjusted so that it executes 51
oscillations in this 60 second period. The length of each successive
shorter pendulum is carefully adjusted so that it executes one
additional oscillation in this period. Thus, the 15th pendulum
(shortest) undergoes 65 oscillations. When all 15 pendulums are started
together, they quickly fall out of sync—their relative phases
continuously change because of their different periods of oscillation.
However, after 60 seconds they will all have executed an integral number
of oscillations and be back in sync again at that instant, ready to
repeat the dance.</p>
</blockquote>
<p>This quote gives us a decent picture of <em>how</em> to build one of these for ourselves if we wanted to but it misses <em>why</em> this system works in the first place. It does tell us that the arrangement of the frequencies is important so let’s start there.</p>
<p>Let’s start by writing out an equation for where each pendulum is at a given point in time. The standard way to describe the motion of a pendulum is to start with all the forces acting on it, then use the “small angle approximation” and end up with this equation (which I’ve simplified a bit)<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup></p>
\[p(t) = \cos(2\pi f t)\]
<p>Where \(f\) is the frequency of the pendulum in cycles per second and \(t\) is the time since the pendulums started swinging in seconds.</p>
<p>An important property of \(\cos\) is its periodicity. That is, it follows a repeating cycle. We will be using this property a lot, so I will go ahead and state it here:</p>
\[\cos(x) = \cos(x + 2\pi) = \cos(x + c(2\pi))\]
<p>where \(c\) is any integer.</p>
<h3 id="why-does-the-cycle-repeat-after-60-seconds">Why does the cycle repeat after 60 seconds?</h3>
<p>From the quoted description above, each pendulum has a frequency one cycle per minute higher than the previous ranging from 51 to 65 cycles per minute. The equation for the \(i\)th pendulum is then</p>
\[p_i(t) = \cos\left(2\pi t\frac{50 + i}{60}\right)\]
<p>The description states that the cycle repeats after 60 seconds, and we can verify that by plugging in 60 for \(t\).</p>
\[p_i(60) = \cos\left(2\pi\frac{60(50+i)}{60}\right) = \cos(2\pi(50+i)) = \cos(0)=1\]
<p>Note that the final term is independent of \(i\), so all the pendulums must have the same position at the peak of their swing (also meaning their velocity is 0) at 60 seconds after they started swinging. Furthermore since a pendulum’s state is fully determined by its position and velocity, the pendulums have all returned to their initial state at the same time and the system must repeat its behavior. You can also verify that this holds for any multiple of 60 seconds, meaning that this whole system must have a period of 60 seconds.</p>
<h3 id="what-about-the-wavy-rows-of-pendulums">What about the wavy rows of pendulums?</h3>
<p>Let’s focus on the clearest pattern which is when the pendulums line up in two rows made up of every other pendulum. It seems to happen at roughly halfway through the cycle and we can check exactly when it happens by figuring out when a pendulum lines up with another two steps further down the line. We can accomplish that by setting \(p_i(t) = p_{i+2}(t)\) and solving for \(t\).</p>
\[\begin{aligned}
p_i(t) &= p_{i+2}(t)\\
\cos\left(2\pi t\frac{50+i}{60}\right)&=\cos\left(2\pi t \frac{50+i+2}{60}\right)\\
\cos\left(2\pi t\frac{50+i}{60}\right)&=\cos\left(2\pi t \frac{50+i}{60} +\frac{2\pi t}{30}\right)
\end{aligned}\]
<p>This holds true when \(\frac{2 \pi t}{30}\) is a multiple of \(2 \pi\) or equivalently when \(\frac{t}{30}\) is an integer. That happens when \(t\) is a multiple of 30 meaning our solutions are \(t=0, 30, 60, 90\) and so on. We already knew about the multiples of 60 – that’s when all of the pendulums line up in one row – so the two rows of pendulums must show up at exactly 30 seconds into the cycle. More precisely, that is exact moment of time when they should be in perfect straight rows. The pattern will be visible for a range of time before and after that point, and due to random variations in starting conditions the rows won’t be perfect.</p>
<p>To see why there are two different rows instead of a single row at 30 seconds, let’s plug in some numbers for specific pendulums.</p>
\[\begin{aligned}
p_1(30) &= \cos\left(2 \pi \frac{30 * 51}{60}\right) = \cos(2 \pi (25.5))=\cos(\pi) = -1\\
p_2(30) &= \cos\left(2 \pi \frac{30 * 52}{60}\right) = \cos(2 \pi (26)) = \cos(0) = 1\\
p_3(30) &= \cos\left(2 \pi \frac{30 * 53}{60}\right) = \cos(2 \pi (26.5))=\cos(\pi) = -1\\
p_4(30) &= \cos\left(2 \pi \frac{30 * 54}{60}\right) = \cos(2 \pi (27)=\cos(0) =1\\
...
\end{aligned}\]
<p>So the pendulums with odd indices all line up on one side and all the pendulums with even indices line up on the other.</p>
<p>What about the other patterns? You can see three rows fairly clearly and even four somewhat. In fact, there is a broader pattern here the explains all of these patterns. Let’s look at when two pendulums with an arbitrary gap of \(k\) pendulums line up.</p>
\[\begin{aligned}
p_i(t)&=p_{i+k}(t)\\
\cos\left(2 \pi t \frac{50+i}{60}\right)&=\cos\left(2\pi t \frac{50+i+k}{60}\right)\\
\cos\left(2 \pi t \frac{50+i}{60}\right)&=\cos\left(2\pi t \frac{50+i}{60}+2\pi t \frac{k}{60}\right)\\
\end{aligned}\]
<p>So two pendulums which are \(k\) pendulums apart line up when \(\frac{tk}{60}\) is an integer. Equivalently, that is when \(t\) is a multiple of \(\frac{60}{k}\). For example, the first and fourth pendulum (a gap of three) will line up at \(t=0, 20, 40, 60, 80\) and so on every 20 seconds. Similarly, the fourth and seventh pendulums line up at the same times, as well as the second and fifth. If you were to plug in \(t=20\) to each of the equations as we did above, you would see three different groups of pendulums made up of every third pendulum lined up at exactly 20 seconds. In general, when \(t\) is a multiple of \(\frac{60}{k}\) you will see \(k\) rows of pendulums made up of every \(k\)th pendulum. However, since there are only 15 pendulums in the video \(k=5\) only gives us three pendulums in a group and anything above \(k=7\) puts only one pendulum in most of the groups. Because of that it is fairly difficult to see anything more than four groups of pendulums lining up at once.</p>
<h3 id="why-do-we-sometimes-see-fewer-rows-than-expected">Why do we sometimes see fewer rows than expected?</h3>
<p>If we keep investigating, there is something interesting that happens when you plug in \(k=4\). If you look at the times where you should see four rows of pendulums you get \(t=0,15,30,45,60\) and so on. So we should see four rows at \(t=30\) but we already know that there are only two rows at that point. Something similar happens at \(t=60\) for all of these values where we might expect there to be \(k\) rows, but we already know there is only one. What’s happening here?</p>
<p>Let’s look at the statement we proved above a bit more carefully. We showed that a pendulum with index \(i\) will line up with the pendulum with index \(i+k\) whenever \(t\) is a multiple of \(\frac{60}{k}\). When \(t\) is 30, it is a multiple of \(\frac{60}{4} = 15\) so pendulum \(i\) lines up with pendulum \(i+4\). However, 30 is also a multiple of \(\frac{60}{2} = 30\) (i.e. \(1\cdot 30\)) so pendulum \(i\) lines up with pendulum \(i+2\). Both of these statements are true simultaneously, in particular because the second statement implies the first. <sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">2</a></sup> So rather than having two distinct rows at \((i, i+4, i+8, ...)\) and at \((i+2, i+6, i+10, ...)\) as you would have at \(t=15\) or \(45\), the two rows actually appear merged together as one at \(t=30\).</p>
<p>In general this will happen whenever we have a time \(60 \cdot \frac{c}{k}\) where \(c\) is an integer and \(c\) and \(k\) share a common divisor. For example, with \(k=6\) you will see six rows at \(t=60\cdot\frac{1}{6}=10\), then three at \(t=60\cdot\frac{2}{6}=60\cdot\frac{1}{3}=20\), then two at \(t=60\cdot\frac{3}{6}=60\cdot\frac{1}{2}=30\). In summary, for any rational multiple of the period, \(60 \cdot \frac{p}{q}\), where \(p\) and \(q\) are integers with no common factors (i.e. the fraction has been reduced to its lowest terms) you will see \(q\) distinct rows of pendulums. However, you are limited by the number of pendulums in the system since any \(k\) above 15 will have rows with zero pendulums.</p>
<p>An interesting consequence of this is that if \(k\) is a prime number you will see \(k\) rows of pendulums at \(k-1\) distinct times. <sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">3</a></sup> Also, if you look at where each of the rows goes at each solution for \(t\) for a particular \(k\) you’ll end up with something that looks like the subgroups of the <a href="https://en.wikipedia.org/wiki/Cyclic_group">cyclic group</a> of order \(k\).<sup id="fnref:6" role="doc-noteref"><a href="#fn:6" class="footnote" rel="footnote">4</a></sup></p>
<h2 id="what-can-we-change">What can we change?</h2>
<p>So is there something special about those frequencies that makes this work? Intuitively it seems like you should be able to slow down the video or equivalently slow down all of the pendulums by making them longer and still see the same system. In fact the frequencies and number of pendulums does not matter as long as there is a fixed frequency gap between them. In this case the gap is one swing per 60 seconds but it could be anything you like, and there could be as many pendulums as you like.</p>
<p>Additionally, the only property we used to prove all the statements above was periodicity. Because of that we can construct a system that has similar behavior using any periodic function. One I particularly like is the movement of clock hands around a clock. <sup id="fnref:5" role="doc-noteref"><a href="#fn:5" class="footnote" rel="footnote">5</a></sup> With this visualization it is much easier to see the patterns for higher values of \(k\). You can also turn the number of hands up to something like 120 for a particularly stunning experience.</p>
<div style="height: 100%; width=100%;">
<iframe src="/demos/clock/clock.html" style="width: 100%; height: 40em;" frameborder="1" scrolling="no"></iframe>
</div>
<p><a href="/demos/clock/clock.html">Full screen</a>.</p>
<h2 id="how-far-can-we-take-it">How far can we take it?</h2>
<p>This system has a limit as it stands. In principle you should be able to see \(k\) groups at some time for any given \(k\). However with finitely many oscillators (e.g. pendulums or clock hands) you cannot see more groups than you have oscillators. What I want is a system where for any rational number \(\frac{p}{q}\) in lowest terms you will see \(q\) evenly spaced groups of oscillators at \(t=\frac{p}{q}\).</p>
<p>The closest thing I’ve seen before is <a href="https://en.wikipedia.org/wiki/Thomae%27s_function">Thomae’s function</a> which is commonly used as an example of a function with bizarre continuity properties. However, I want something that operates over both time and space so that we can see where the oscillators are at any given point in time. Thomae’s function is one dimensional and can’t quite accomplish what I want, so we’ll have to extend it a bit.</p>
<p><img src="https://wikimedia.org/api/rest_v1/media/math/render/svg/fb0891208faccc94e30501184ff7b5e08a44ad44" alt="Thomae's function equation" style="min-width:100%;" /></p>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/1/15/Thomae_function_%280%2C1%29.svg/1920px-Thomae_function_%280%2C1%29.svg.png.webp" alt="Thomae's function" /></p>
<p>The best I can come up with is the following:</p>
\[f:\mathbb{R}\times [0,1) \rightarrow \mathbb{Q}\]
\[f(t, x) = \begin{cases}
1, &t=0 \text{ and } x = 0\\
\frac{1}{q},& \text{if } x=\frac{p}{q}\text{, with } p \in \mathbb{Z} \text{ and } q \in \mathbb{N} \text{ coprime, and } t=\frac{r}{q}, \text{ with } r\in\mathbb{Z} \text{ and } r,q \text{ coprime} \\
0,& \text{otherwise (i.e. } x,t \text{ irrational or don't share a common denominator in reduced form)}
\end{cases}\]
<p>Essentially this says that if \(t\) and \(x\) share a common denominator in reduced form (with 0 being interpreted as \(\frac{0}{1}\)), then the value is one over that denominator. Otherwise the value is zero. I’m not particularly sure how to make a proper visualization of this since all the patterns become actually instantaneous and the function is technically zero for essentially all values of \(t\). Therefore I’m going to leave this as an open challenge.</p>
<h2 id="challenges">Challenges</h2>
<p>Send me whatever you come up with for these open-ended challenges.</p>
<ol>
<li>Where else can you find this kind of system?</li>
<li>Implement/build a visualization of this kind of system. Try using another kind of periodic function (e.g. turn signals, planetary systems, something with audio), be creative!</li>
<li>Implement the above definition for an infinite analog in an appealing way.</li>
<li>Formulate some alternative definition for an infinite analog to this system.</li>
<li>Send me an interesting challenge to put here.</li>
</ol>
<h2 id="footnotes">Footnotes</h2>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="https://en.wikipedia.org/wiki/Pendulum_(mathematics)">https://en.wikipedia.org/wiki/Pendulum_(mathematics)</a> for the standard derivation, if you’re interested in a more thorough look <a href="https://www.youtube.com/watch?v=p_di4Zn4wz4">this video</a> by 3Blue1Brown is fantastic <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p>\(p_i(30) = p_{i+2}(30)\) implies \(p_{i+2}(30) = p_{i+4}(30)\) and by the transitive property of equality \(p_i(30) = p_{i+4}(30)\) <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:4" role="doc-endnote">
<p>Not \(k\) since it still must line up in one row at multiples of 60 <a href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:6" role="doc-endnote">
<p>Actually, the multiplication table for \(\mathbb{Z}/\mathbb{Z}_k\) (integers mod \(k\)) is a much better description of what I meant. Thanks, Stephen. <a href="#fnref:6" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:5" role="doc-endnote">
<p>You can also think of this as the <a href="https://en.wikipedia.org/wiki/Euler%27s_formula">complex exponential</a> if you want to <a href="#fnref:5" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Henry ConklinI have been occasionally obsessed with this video for years and I finally sat down to deconstruct it. The video shows a row of 15 pendulums, with each pendulum being slightly shorter than the last. All of the pendulums are started swinging at once and they follow a kind of snaking pattern that devolves into chaos. However, they occasionally line up into two or three groups that swing together before falling out of phase again. After 60 seconds they all line up in one row and the pattern repeats. Have a watch:Bee++2019-02-01T00:00:00+00:002019-02-01T00:00:00+00:00https://www.mathematastic.com/projects/2019/02/01/bee-plus-plus<p>There is currently an epidemic of dying bee hives.
Colony Collapse Disorder, where the majority of a
hive’s worker drones disappear suddenly, has doubled
in prevalence since 2007. These bees are vital for a vast
portion of our agriculture. Without them our crops would
remain unpollinated and not produce the food and other
products we need to survive.</p>
<p>In order to combat this issue, I developed a sensor
to monitor the traffic through a beehive’s entrance. This
sensor measures capacitance to detect when bees travel
through the hive entrance, and also provides information about their size and speed. The sensor sends its
detections over WiFi to a dashboard server in the cloud
for easy analysis by the hive’s keeper. If the bee keeper
notices an anomaly in the hive’s activity, such as lower
traffic volume or the bees moving slower than usual,
they can intervene before further damage is done to the
hive. In the future, this information could be analyzed
automatically to send alerts for anomalous behavior.</p>
<h2 id="the-sensor">The Sensor</h2>
<h1 id="why-capacitance">Why Capacitance?</h1>
<p>There are a variety of methods that could be used
for this task. Others have used infrared LEDs and light
sensors to detect when a bee blocks the light’s path<sup id="fnref:1" role="doc-noteref"><a href="#fn:1" class="footnote" rel="footnote">1</a></sup>,
and there have been several other projects that used
capacitance<sup id="fnref:3" role="doc-noteref"><a href="#fn:3" class="footnote" rel="footnote">2</a></sup> <sup id="fnref:2" role="doc-noteref"><a href="#fn:2" class="footnote" rel="footnote">3</a></sup>. Both strategies can measure traffic volumes and speed (by using a pair of sensors and measuring the time it takes the bee to travel between them). Capacitance offers a few advantages: it is
very minimally invasive as it does not require direct
contact, the sensor can be hidden outside the existing hive entrance, and it
additionally allows the sensor to measure the size of the
bees based on the magnitude of their capacitance as they
pass by the sensor.</p>
<h1 id="capacitors-how-do-they-work">Capacitors, how do they work?</h1>
<p><img src="https://upload.wikimedia.org/wikipedia/commons/thumb/3/35/Parallel_plate_capacitor.svg/1280px-Parallel_plate_capacitor.svg.png.webp" alt="Capacitor diagram from Wikipedia article "Capacitor"" />
<em>Capacitor diagram from Wikipedia article “Capacitor”</em></p>
<p>Capacitors are an electrical component that functions
as a kind of power storage in a circuit. Capacitors are composed
of two parallel plates of metal, with some kind of
non-conductive (dielectric) material between them. The
overall capacitance of such a circuit is affected by the
area of the plates, the distance between them, and the
properties of the dielectric material. For the purposes of
this sensor, the size and distance between the plates will
be held constant. We can then detect a change in the
material between the plates by measuring a change in
the capacitance. Importantly, there can be a gap of air
or other material between the plates and the material we
wish to analyze, so the effect can be measured without
physical contact between the bees and the metal plates.</p>
<h1 id="the-sensor-circuit">The Sensor Circuit</h1>
<p>As a proxy for a hive entrance, I used a plastic tube of
sprinkles with the ends cut off and the sprinkles removed.
I wrapped three rings of aluminum foil around the tube
at the ends and the center to create two physically
large (but small in capacitance) capacitors with a shared
ground on the center loop. The dielectric material is
then the plastic tube itself, as well as the empty space
inside the tube. When a bee or other object passes through the
tube, the capacitance will change. One reference I used
for this project measured the change in capacitance at
about 0.5pF which is smaller than any widely available
capacitor component, so this effect is extremely small.
Rather than using an actual bee, I elected to use
a yellow gummy bear on a stick due to the similar size
and hopefully similar capacitance values due to water
content.</p>
<p>Capacitance is generally measured through the time it takes for
the capacitor to reach its maximum charge and match
the voltage fed in to it. When a capacitor is connected
to a voltage, its charge and the voltage across its terminals will follow an exponential decay curve toward
the voltage on its positive terminal. The rate at which it
charges is proportional to its capacitance. For extremely
small capacitors, this is too fast to measure directly so
other methods based on measuring the effects on waves
passed through the capacitor (essentially charging and
discharging it quickly) are used.</p>
<p><img src="/images/beecounter/beecounter-circuit.svg" alt="Bee counter sensor circuit diagram" /></p>
<p>I used a circuit based around an NE555P chip which
generates a square wave with a frequency that depends
on the capacitance on one of the chip’s pins <sup id="fnref:4" role="doc-noteref"><a href="#fn:4" class="footnote" rel="footnote">4</a></sup>. The chip
generates a square wave by enabling and disabling a
signal as an internal capacitor charges and discharges.
By attaching an additional external capacitor (in this case our sensors), we can
change the rate at which the overall circuit charges and
modify the resulting frequency. This frequency can then
be measured in order to measure the capacitance across
our sensors. Because the capacitance we are measuring
is so small, the circuit required a significant amount of
trial and error to reduce noise and increase its sensitivity.
In particular I added a few capacitors across various pins until it worked.
I also tried modifying the values of the resistors in the circuit which should have lowered the output frequency and made it easier to read,
however none of the values I tried produced useful results.</p>
<p>For my application, the two capacitance sensors were
connected together so that the measured capacitance is
the sum of the capacitance over the two sensors. The area of highest sensitivity
for each of the sensors is in the centerpoint between the center loop and either outer loop.
So, when a bee passes through the tunnel the capacitance will spike as it passes through the first centerpoint, lower as it passes under the middle ring, then spike again as it passes through the second centerpoint. Since the values of the two sensors is summed, it is impossible to tell which one is triggered first. If the two sensors were read separately with separate circuits you could additionally detect whether a bee was entering or leaving the hive.
However running two of the sensor circuits in parallel
caused too much noise in the readings to be useful.</p>
<h2 id="signal-processing">Signal Processing</h2>
<p><img src="/images/beecounter/beecounter-signal.jpg.webp" alt="Sample capacitance signal as a "bee" passes through the tube" /></p>
<p><em>Sample capacitance signal as a “bee” passes through the tube</em></p>
<p>In order to measure the frequency coming from the
sensor circuit, I needed to be able to sample an analog
value very quickly. The waves coming out of the circuit
need to be between about 10kHz and 100kHz for optimal
accuracy, so the microcontroller needs to sample at about
200kHz (<a href="https://en.wikipedia.org/wiki/Nyquist_frequency">Nyquist frequency</a>). The microcontroller I used
(an ESP8266, on a NodeMCU development board) has
a clock rate of 80MHz, and has an internal Analog-to-Digital converter (ADC) which can sample at about
1MHz using some low-level libraries, although I am not
sure of the exact sampling rate. Getting clean and fast
samples from the ADC was a significant problem for
the whole duration of this project. It took a significant
amount of optimization in both the sensor circuit and
the code on the microprocessor in order to get a clean
signal.</p>
<p>If I sample the incoming square wave over a fixed
window of time and at a high enough sampling rate, I
can count the pulses and get a measure of the frequency
and therefore the capacitance of my sensors.
I then sample the measured capacitance value at approximately 100Hz. When a bee crosses the centerpoint
between two of the loops of foil, there will be a spike in
the capacitance value. I can detect a spike by setting a
threshold above the typical noisy fluctuations in the signal. I also compensate for varying ambient capacitance
from the environment in the form of humidity and larger
nearby objects by subtracting off a long-term exponential moving
average of the capacitance signal. When I detect a pair
of spikes as a bee pass through both of the capacitance
sensors, I can derive the speed of the bee from the time
difference between the spikes and the distance between
the sensors’ centerpoints, and I can derive the size of
the bee from the magnitude of the spikes in capacitance.
After each detection I send the size and speed values
over WiFi to a server in the cloud which additionally
records the time the message arrived at the server and
saves the detection to a database.</p>
<p>In summary, there are two levels of signal processing
between the output of the sensor circuit and the final
detections. First, the square wave from the sensor circuit
is mapped to its frequency. Then that frequency is plotted
over time and analyzed to produced the bee detections.
Finally these detections are sent over WiFi to a central
server in the cloud.</p>
<h2 id="dashboard">Dashboard</h2>
<p><img src="/images/beecounter/beecounter-dash.jpg.webp" alt="Dashboard screenshot" /></p>
<p>For my dashboard I used the <a href="https://plot.ly/products/dash/">Dash</a> framework by
Plotly. The server reads from the detection database and
generates a table of the entries and several histograms of
traffic volume, bee size, and bee speed. The charts and
table update automatically to give a live feed of data
from the sensor. The entire dashboard is implemented in
about 50 lines of python code and can be hosted in the AWS
cloud on a t2.micro instance.</p>
<p>All of the code is available at: <a href="https://github.com/HenryWConklin/beecounter">https://github.com/HenryWConklin/beecounter</a></p>
<div class="footnotes" role="doc-endnotes">
<ol>
<li id="fn:1" role="doc-endnote">
<p><a href="https://www.instructables.com/id/Honey-Bee-Counter/">https://www.instructables.com/id/Honey-Bee-Counter/</a> <a href="#fnref:1" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:3" role="doc-endnote">
<p><a href="https://www.researchgate.net/publication/231061606_Capacitance-based_sensor_for_monitoring_bees_passing_through_a_tunnel">https://www.researchgate.net/publication/231061606_Capacitance-based_sensor_for_monitoring_bees_passing_through_a_tunnel</a> <a href="#fnref:3" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:2" role="doc-endnote">
<p><a href="https://spectrum.ieee.org/geek-life/hands-on/how-to-build-an-electronic-bee-counter">https://spectrum.ieee.org/geek-life/hands-on/how-to-build-an-electronic-bee-counter</a> <a href="#fnref:2" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
<li id="fn:4" role="doc-endnote">
<p><a href="https://www.electronicdesign.com/analog/use-analog-techniques-measure-capacitance-capacitive-sensors">https://www.electronicdesign.com/analog/use-analog-techniques-measure-capacitance-capacitive-sensors</a> <a href="#fnref:4" class="reversefootnote" role="doc-backlink">↩</a></p>
</li>
</ol>
</div>Henry ConklinThere is currently an epidemic of dying bee hives. Colony Collapse Disorder, where the majority of a hive’s worker drones disappear suddenly, has doubled in prevalence since 2007. These bees are vital for a vast portion of our agriculture. Without them our crops would remain unpollinated and not produce the food and other products we need to survive.Bark to Text2015-08-28T18:47:00+00:002015-08-28T18:47:00+00:00https://www.mathematastic.com/projects/2015/08/28/bark-to-text<p>As I promised in my last post, <a href="/projects/2015/08/17/oliver-twitter.html">A Twitter account for Oliver</a>, I have been working on a bark-to-text translator for Oliver’s twitter account (<a href="http://www.twitter.com/OliverBarkBark">@OliverBarkBark</a>). The overall process ended up being rather simple. Since the original recordings often capture multiple bark sounds, I needed to split the audio files into distinct barks. With that done, all that was left classifying the different bark noises that Oliver makes. For that I reused the <a href="http://www.github.com/tyiannak/pyAudioAnalysis">pyAudioAnalysis</a> library from the bark vs. junk classification step in detecting Oliver’s barks.</p>
<p>However, due to some issues that I’ll get into shortly, Oliver’s current, random tweets are going to remain the way they are. This bark-to-text project will go unused, but will still be available on the <a href="http://www.github.com/HenryWConklin/barkdetect">Github page for this project</a>.</p>
<p>Anyway, onto the details. Here is a typical clip of Oliver barking as captured by the raspberry pi setup:</p>
<audio controls="controls">
<source src="/sounds/oliver-barking.mp3" type="audio/mpeg" />
<source src="/sounds/oliver-barking.wav" type="audio/wav" />
<source src="/sounds/oliver-barking.ogg" type="audio/ogg" />
Your browser does not support HTML5 audio. You can download the audio file [here](/sounds/oliver-barking.mp3).
</audio>
<p>As you can hear, multiple bark sounds are often captured in the same file. In order to classify each distinct bark, I needed to seperate each of the sounds out. I used a thresholding system similar to what I used to capture the clips originally, but this time I had access to the whole clip and could therefore look at the level of the background noise which made things a bit easier.</p>
<p>My final solution took the maximum amplitude of the audio over some period to get an approximate volume. After throwing arbitrary constants at the problem for about half an hour, I settled on a period of 2500 samples (about 0.05 seconds) and assumed that a bark started when the volume between two periods at least doubled. I then split the audio file along start of each bark and end up with rather well separated bark sounds.</p>
<p>Here are examples of a few of the different sounds that Oliver makes:</p>
<p>A “bark”:</p>
<audio controls="controls">
<source src="/sounds/oliver-bark.wav" type="audio/wav" />
<source src="/sounds/oliver-bark.mp3" type="audio/mpeg" />
<source src="/sounds/oliver-bark.ogg" type="audio/ogg" />
Your browser does not support HTML5 audio. You can download the audio file [here](/sounds/oliver-bark.mp3).
</audio>
<p>A “woof”:</p>
<audio controls="controls">
<source src="/sounds/oliver-woof.wav" type="audio/wav" />
<source src="/sounds/oliver-woof.mp3" type="audio/mpeg" />
<source src="/sounds/oliver-woof.ogg" type="audio/ogg" />
Your browser does not support HTML5 audio. You can download the audio file [here](/sounds/oliver-woof.mp3).
</audio>
<p>With the splitting accomplished, it was fairly simple to reuse the classification process from the original project on each separate bark sound and produce a string accurate to the original audio file. Here are some examples of the end result:</p>
<ul>
<li>Woof bark bark bark bark</li>
<li>Bark bark bark</li>
<li>Bark</li>
<li>Bark bark</li>
<li>Bark bark bark bark</li>
<li>Bark</li>
<li>Bark</li>
<li>Woof</li>
<li>Bark bark</li>
<li>Bark</li>
<li>Woof</li>
<li>Woof</li>
<li>Bark bark</li>
</ul>
<p>Now you might start to see why I chose not to implement this fully. Oliver just does not produce enough distinct kinds of barks. Most of his barks can only be transcribed as “bark,” and so it all starts to look very similar and boring. However, it is more than that. Twitter’s API, as part of a spam filtering system, will ignore any tweets that match any of your “recent” tweets. So, not only does this provide less of an entertaining experience than the random tweets, most of them will just be blocked and we can’t have that.</p>Henry ConklinAs I promised in my last post, A Twitter account for Oliver, I have been working on a bark-to-text translator for Oliver’s twitter account (@OliverBarkBark). The overall process ended up being rather simple. Since the original recordings often capture multiple bark sounds, I needed to split the audio files into distinct barks. With that done, all that was left classifying the different bark noises that Oliver makes. For that I reused the pyAudioAnalysis library from the bark vs. junk classification step in detecting Oliver’s barks.A Twitter account for Oliver2015-08-17T15:32:00+00:002015-08-17T15:32:00+00:00https://www.mathematastic.com/projects/2015/08/17/oliver-twitter<p>My dog Oliver has always been quite vocal, and recently I decided that his thoughts and comments needed to be shared with the world. Thus the <a href="http://www.twitter.com/OliverBarkBark">@OliverBarkBark</a> project was born. By connecting a Rasberry Pi, a wifi dongle, and a microphone, I was able to make a system that automatically detected, filtered, and published each and every one of Oliver’s deafening vocalizations.</p>
<p><img src="/images/oliver-twitter/oliver-twitter-setup.jpg.webp" alt="The setup" /></p>
<h6>The setup</h6>
<p>The full process from bark to tweet takes three steps. First is recording. I have the raspberry pi listening continuously and triggering a recording once it hears a sound over a preset volume. Oliver barking is by far the loudest thing within several miles, so the volume threshold should be sufficient. However, the recordings are still triggered occaisonally by unwanted junk. To gaurd against this, I needed to perform a second step to filter the barks from the junk.</p>
<p>I took a machine learning approach to filter out the barks. I built a model using the <a href="https://github.com/tyiannak/pyAudioAnalysis">pyAudioAnalysis</a> library and around a day’s worth of barks (about 20). I then set up a bash script to run every ten minutes, classify each recorded sound, and forward the barks on to the next step.</p>
<p>Finally, the barks are forwarded to the <a href="https://dev.twitter.com/overview/documentation">twitter api</a> (using <a href="https://github.com/bear/python-twitter">python-twitter</a>) and posted under the handle <a href="http://www.twitter.com/OliverBarkBark">@OliverBarkBark</a> (be sure to follow!). Currently the tweets are random strings composed of “bark,” “ruff,” and “woof.” I plan to replace that with a bark-to-text translator that will likely produce similar results but be more accurate to Oliver’s actual voice.</p>
<p>Check out all the code: <a href="https://github.com/HenryWConklin/barkdetect">https://github.com/HenryWConklin/barkdetect</a></p>
<p><img src="/images/oliver-twitter/oliver-twitter-on-guard.jpg.webp" alt="Oliver on guard" /></p>
<h6>Oliver on guard</h6>
<p>Next step: weekly podcasts.</p>Henry ConklinMy dog Oliver has always been quite vocal, and recently I decided that his thoughts and comments needed to be shared with the world. Thus the @OliverBarkBark project was born. By connecting a Rasberry Pi, a wifi dongle, and a microphone, I was able to make a system that automatically detected, filtered, and published each and every one of Oliver’s deafening vocalizations.Enhanced 2D Visibility Project2015-02-07T15:32:00+00:002015-02-07T15:32:00+00:00https://www.mathematastic.com/projects/2015/02/07/enhanced-2d-vision<p>For the last year and a half or so, I’ve been working on a project I like to call <em>Enhanced 2D Visibility</em>. Visibility mechanics in 2D games have been around
since the original <em>Rogue</em> at least, and more recently in games like <em>Monaco</em>. The basic idea is that if you don’t have direct line of sight to an
area of the screen, then it shouldn’t be visible.</p>
<p><img src="http://www.monacoismine.com/images/screenshots/the_lookout_in_action.jpg" alt="Monaco screenshot" /></p>
<h6>A screenshot from <em>Monaco</em></h6>
<p>This adds a nice level of immersion by restricting the information the player has to what a <em>Flatland</em>-esque 2D entity might have. The problem with this
technique is that it has a distinct lack of GPU-melting recursive structures. This is why I wanted to add a level of complexity on top the standard 2D visibility
to support reflection and refraction. Refraction has proven to be difficult, and might not be entirely possible in a real-time context. Reflection, however, is at
least a bit simpler and I’m coming close to having a functional tech-demo for it. Before I go into the details of reflection and refraction, let’s go over how basic
2D visibility is usually implemented.</p>
<p>#Basic Visibility#
I implemented the basic visibility using a ray-casting approach like the one described <a href="http://www.redblobgames.com/articles/visibility/">here</a> by Red Blob Games.
There’s a very nice list of other resources on that same site <a href="http://simblob.blogspot.com/2012/07/2d-visibility.html">here</a> if you want to implement this yourself.
I’ll give a brief overview of how I implemented this, as the resources above explain it far better than I can.</p>
<p>The major steps are as follows:</p>
<ol>
<li>Get a list of all the solid objects in the scene, represented as line segments.</li>
<li>Store the endpoints of all the segments in a list, sorted by angle from some axis (I used the positive x axis)</li>
<li>Cast a ray from the player to each endpoint in the scene, getting the intersection closest to the player</li>
<li>Cast two more rays slightly clockwise and counter-clockwise from the endpoint to get points behind the segment</li>
<li>Store all these intersection points in a list</li>
<li>Draw the points as a triangle fan around the player to the OpenGL
<a href="http://lazyfoo.net/tutorials/OpenGL/26_the_stencil_buffer/index.php">stencil buffer</a>
(or use some other masking method)</li>
<li>Draw the rest of the scene</li>
</ol>
<p>That’s a very rough overview, but it gives something that looks like this:</p>
<p><img src="/images/2d-visibility/2dVis1.png.webp" alt="Basic Visibility Screenshot" />
<img src="/images/2d-visibility/2dVis2.png.webp" alt="Basic Visibility Screenshot" /></p>
<h2 id="reflection">Reflection</h2>
<p><img src="/images/2d-visibility/2dMirror1.png.webp" alt="Reflection screenshot" /></p>
<p>Now, let’s get back to what I meant by GPU-melting recursive structures. What happens when you have a mirror facing another mirror? In real life, it produces
a seemingly-infinite halway of reflections of reflections of reflections.</p>
<p>This screams recursion. Whenever you draw a mirror, you then have to draw any mirrors visible in the reflection of that mirror, which may or may not include
the original mirror.</p>
<p>Let’s find our entry point. You start at the position of the player, with no reflections applied. Great, let’s push that state onto a queue. Why a queue?
The obvious way to do it is to call the render function recursively with the new state, but that can crash too easily. We use an explicit collection so that we can
limit the number of recursive iterations to a managable amount. Why a queue and not a stack? With a stack, the rendering process would follow the reflections down to
furthest depths of reflections of reflections of one mirror before drawing anything for any other mirros. The queue guaruntees that the first-level mirrors get
rendered first, second-level second, etc.</p>
<p>Next, build the visibility mask as described in the last section. However, now we need to do some extra stuff. When we store our points that we get from
raycasting, we also want to store which segment they are on. Then, after we’ve gotten all the points, we can go through the list and pick out the visible parts
of the reflective segments. That looks something like this:</p>
<figure class="highlight"><pre><code class="language-c--" data-lang="c++"><span class="n">vector</span><span class="o"><</span><span class="n">Segment</span><span class="o">></span> <span class="n">mirrors</span><span class="p">;</span>
<span class="k">for</span> <span class="p">(</span><span class="kt">int</span> <span class="n">i</span> <span class="o">=</span> <span class="mi">0</span><span class="p">;</span> <span class="n">i</span> <span class="o"><</span> <span class="n">visiblePoints</span><span class="p">.</span><span class="n">size</span><span class="p">()</span><span class="o">-</span><span class="mi">1</span><span class="p">;</span> <span class="n">i</span><span class="o">++</span><span class="p">)</span> <span class="p">{</span>
<span class="n">VisPoint</span> <span class="n">p</span> <span class="o">=</span> <span class="n">visiblePoints</span><span class="p">[</span><span class="n">i</span><span class="p">];</span>
<span class="n">VisPoint</span> <span class="n">nextP</span> <span class="o">=</span> <span class="n">visiblePoints</span><span class="p">[</span><span class="n">i</span><span class="o">+</span><span class="mi">1</span><span class="p">];</span>
<span class="c1">//If the current point is on a mirror and the next point</span>
<span class="c1">//is on the same segment, add a mirror segment to the list</span>
<span class="k">if</span> <span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">segment</span><span class="p">.</span><span class="n">isMirror</span><span class="p">()</span> <span class="o">&&</span> <span class="n">nextP</span><span class="p">.</span><span class="n">segment</span> <span class="o">==</span> <span class="n">p</span><span class="p">.</span><span class="n">segment</span><span class="p">)</span> <span class="p">{</span>
<span class="n">mirrors</span><span class="p">.</span><span class="n">push_back</span><span class="p">(</span><span class="n">Segment</span><span class="p">(</span><span class="n">p</span><span class="p">.</span><span class="n">point</span><span class="p">,</span> <span class="n">nextP</span><span class="p">.</span><span class="n">point</span><span class="p">));</span>
<span class="p">}</span>
<span class="p">}</span></code></pre></figure>
<p>Then, for each visible mirror segment we need to find the transform matrix for it’s reflection (detailed
<a href="http://planetmath.org/derivationof2dreflectionmatrix">here</a>). Once we have that, push each matrix to the queue I mentioned earlier, pop the next one off the top
and repeat.</p>
<p>There’s one detail we’re missing. We need to know what area of the scene to draw behind the mirror. This is simple enough if we keep a boundary polygon along
the reflection matrix in the queue. We just need to find the region behind the mirror and reflect it across the mirror and there we have our boundary polygon.
The issue is, although the first layer of mirrors only needs to find the region behind clipped to the screen (a rectangle), every consecutive instance needs
to clip to some arbitrary polygon that was the bounds of the previous reflection. This is where I am now, I’ll update when I figure it out.</p>
<p>If we put all this togther, we get something that looks like this:</p>
<p><img src="/images/2d-visibility/2dMirror2.png.webp" alt="Mirror on mirror action" /></p>
<p>Not exactly like that, however. That is from a previous attempt at mirrors reflecting mirrors that involved rendering to offscreen textures. The program ran
exacly long enough to take a screenshot before it claimed all the memory available and crashed. Hopefully this iteration will go a bit better.</p>
<h2 id="refraction">Refraction</h2>
<p>I’m really not sure where to go with refraction. All I know is that it would look cool, and let me make lenses. It doesn’t seem to be a linear transformation.
So if I do end up writing it in, it will probably be rather slow and take some craziness to get it to work with OpenGL. If you have any ideas on how to
to go about implementing refraction, shoot me an email at the address below.</p>Henry ConklinFor the last year and a half or so, I’ve been working on a project I like to call Enhanced 2D Visibility. Visibility mechanics in 2D games have been around since the original Rogue at least, and more recently in games like Monaco. The basic idea is that if you don’t have direct line of sight to an area of the screen, then it shouldn’t be visible.