Alden Bradfordhttps://aldenbradford.com/2023-04-09T00:00:00-04:00Solution to a puzzle on the incircle of a triangle2023-04-09T00:00:00-04:002023-04-09T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2023-04-09:/solution-to-a-puzzle-on-the-incircle-of-a-triangle.html<p>Solution to a geometry puzzle</p><p>Some times I hang around the <a href="https://www.reddit.com/r/MathHelp/">math help subreddit</a>. Most of the people posting there are doing some standard homework problem from algebra or calculus, and they get help right away. Every so often there will be a probability question, and those people get help within a day or so. My favorite to see are the geometry questions. Those questions don't always get solved, since they tend to be trickier. That's <a href="https://www.reddit.com/r/MathHelp/comments/12cgipu/geometry_question/">where I found</a> the following puzzle.</p> <h1>The puzzle</h1> <p>Begin with a general triangle <span class="math">$$\triangle ABC$$</span>. Construct the incenter <span class="math">$$I$$</span>, altitude <span class="math">$$AH$$</span>, and <span class="math">$$M$$</span> the midpoint of <span class="math">$$BC$$</span>. Define <span class="math">$$K$$</span> to be the intersection of the lines <span class="math">$$AH$$</span> and <span class="math">$$MI$$</span>. Show that <span class="math">$$\overline{AK}$$</span> is equal to the inradius <span class="math">$$r$$</span> of the triangle.</p> <iframe src="https://www.geogebra.org/calculator/sfdk3mqv?embed" width="600" height="400" allowfullscreen style="border: 1px solid #e4e4e4;border-radius: 4px;" frameborder="0"></iframe> <p>Just for convenience, let's also define <span class="math">$$D$$</span> to be the point on <span class="math">$$BC$$</span> which touches the incircle, so that <span class="math">$$\overline{ID}=r$$</span>.</p> <p>This puzzle had me stumped for a few days. I did eventually get a solution which is not too messy, detailed below, thought I suspect there is a nicer solution using more geometry. I enjoyed solving this, and I hope you will to. If you would like to solve it yourself, stop here. Otherwise, read on.</p> <h1>The solution strategy</h1> <p>The main idea is to consider the area of the triangle. We are given an altitude, after all, and altitudes are good for finding areas. We will compute the area of <span class="math">$$\triangle ABC$$</span> in two different ways. First, using the given altitude, the area must be <span class="math">$$\frac{1}{2}\overline{AH}\overline{BC}$$</span>. Second, by partitioning <span class="math">$$\triangle ABC$$</span> along its angle bisectors (into <span class="math">$$\triangle ABI$$</span>, <span class="math">$$\triangle BCI$$</span>, and <span class="math">$$\triangle CAI$$</span>) we find another formula for the area, <span class="math">$$\frac{1}{2}r(\overline{AB}+\overline{BC}+\overline{AC})$$</span>. That is,</p> <div class="math">\begin{equation} r(\overline{AB}+\overline{BC}+\overline{AC})=\overline{AH}\overline{BC}.\tag{I} \end{equation}</div> <p>We will use one other fact about the incenter, which may not be immediately obvious.</p> <div class="math">\begin{equation} \overline{AB}+\overline{CD} = \overline{AC}+\overline{DB}.\tag{II} \end{equation}</div> <p>Here is a picture-proof of that theorem. Connecting the incenter to each side along a radius, we split the trianlge into three kites. Dividing each in half, we get three pairs of congruent right triangles, all with the same altitude <span class="math">$$r$$</span>. Equation <span class="math">$$\text{(II)}$$</span> simply follows from grouping those triangles and considering their bases.</p> <iframe src="https://www.geogebra.org/calculator/axrqzdyb?embed" width="600" height="400" allowfullscreen style="border: 1px solid #e4e4e4;border-radius: 4px;" frameborder="0"></iframe> <p>Besides that, we will only use Pythagoras, </p> <div class="math">\begin{equation} \overline{AB}^2=\overline{AH}^2+\overline{HB}^2,\qquad \overline{AC}^2=\overline{AH}^2+\overline{CH}^2,\tag{III} \end{equation}</div> <p> the definition of midpoint, </p> <div class="math">\begin{equation} \overline{CM}=\overline{BM},\tag{IV} \end{equation}</div> <p> and similar triangles, </p> <div class="math">\begin{equation} r\overline{MH}=\overline{KH}\overline{MD}.\tag{V} \end{equation}</div> <p>If you would like to try it for yourself, have a go at manipulating the above formulas to get the answer. Otherwise read on.</p> <h1>The arithmetic</h1> <p>Depending on the relative positions of <span class="math">$$A$$</span>, <span class="math">$$B$$</span>, and <span class="math">$$C$$</span>, the lengths will add up differently. To make it simpler to follow, we will assume that (as in the diagram above) <span class="math">$$H$$</span> lies between <span class="math">$$M$$</span> and <span class="math">$$B$$</span>. The strategy will work in any case, but this will make a world of difference for readability.</p> <p>Let's begin at <span class="math">$$\text{(III)}$$</span>, subtracting the two equations to cancel the <span class="math">$$\overline{AH}$$</span> term. </p> <div class="math">\begin{align*} \overline{AC}^2-\overline{AB}^2 &amp;=\overline{CH}^2-\overline{HB}^2\\ &amp;=(\overline{CH}+\overline{HB})(\overline{CH}-\overline{HB})\\ &amp;=\overline{BC}(\overline{CM}+\overline{MH}-\overline{HB})\\ &amp;=\overline{BC}(\overline{BM}+\overline{MH}-\overline{HB})\\ &amp;=2\overline{BC}\overline{MH}.\tag{VI} \end{align*}</div> <p>Inspired by the difference-of-squares formula, let's manipulate <span class="math">$$\text{(II)}$$</span> similarly. </p> <div class="math">\begin{align*} \overline{AC}-\overline{AB} &amp;= \overline{CD}-\overline{DB}\\ &amp;= \overline{CM}+\overline{MD} -\overline{DB}\\ &amp;= \overline{BM}+\overline{MD} -\overline{DB}\\ &amp;= 2\overline{MD}.\tag{VII} \end{align*}</div> <p>Do the same with <span class="math">$$\text(I)$$</span>, </p> <div class="math">\begin{equation} r(\overline{AC}+\overline{AB})=\overline{BC}(\overline{AH}-r).\tag{VIII} \end{equation}</div> <p>Difference of squares takes us home, and <span class="math">$$\text{(V)}$$</span> lays the problem to rest. </p> <div class="math">\begin{align*} r(\overline{AC}^2-\overline{AB}^2) &amp;= r(\overline{AC}+\overline{AB})(\overline{AC}-\overline{AB})\\ 2r\overline{BC}\overline{MH} &amp;= \overline{BC}(\overline{AH}-r)2\overline{MD}\\ r\overline{MH} &amp;= (\overline{AH}-r)\overline{MD}\\ \overline{KH}\overline{MD} &amp;= (\overline{AH}-r)\overline{MD}\\ r&amp;=\overline{AH}-\overline{KH}\\ &amp;=\overline{AK}. \end{align*}</div> <h1>Conclusion</h1> <p>As mentioned above, this solution is based on areas, because we are given altitudes. It still feels quite algebra-heavy for a geometry proof. In general, <span class="math">$$H$$</span> could lie to the left of <span class="math">$$C$$</span>, between <span class="math">$$C$$</span> and <span class="math">$$M$$</span>, between <span class="math">$$M$$</span> and <span class="math">$$B$$</span>, or to the right of <span class="math">$$B$$</span>. Giving all the details of each case would be tiresome, but would make this proof fully rigorous. I can't quite shake the feeling that there must be a tidyer way about this problem, using less algebra and more geometry. For now, I leave it here. I hope you enjoyed this puzzle as much as I did.</p> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>Orrell's Quick Wall Paper Calculator2022-12-05T00:00:00-05:002022-12-05T00:00:00-05:00Alden Bradfordtag:aldenbradford.com,2022-12-05:/oqwpc.html<p>A trade show freebie which is almost a slide rule</p><p>My wife got me an early holiday gift this year. It's an ancient trade show promotional item, which must be nearly 100 years old by now. I believe it was made in 1923 or 1924, for reasons which will soon become obvious. In addition to working as an advertisement for L.C. Orrell &amp; Company, it serves as a rough area estimation tool for wall paper.</p> <p>It consists of a cardboard envelope with two holes cut into it, along with a card which slides inside the envolope. The whole thing measures 5.5 by 2.75 inches. To use it, you slide the card in and out, and different figures show through the holes.</p> <script type="text/javascript"> function makeDraggable(evt) { var svg = evt.target; svg.addEventListener('mousedown', startDrag); svg.addEventListener('mousemove', drag); svg.addEventListener('mouseup', endDrag); svg.addEventListener('mouseleave', endDrag); svg.addEventListener('touchstart', startDrag); svg.addEventListener('touchmove', drag); svg.addEventListener('touchend', endDrag); svg.addEventListener('touchleave', endDrag); svg.addEventListener('touchcancel', endDrag); var selectedElement, offset, transform; function getMousePosition(evt) { var CTM = svg.getScreenCTM(); if (evt.touches) { evt = evt.touches; } return { x: (evt.clientX - CTM.e) / CTM.a, y: (evt.clientY - CTM.f) / CTM.d }; } function startDrag(evt) { selectedElement = document.getElementById("slide"); offset = getMousePosition(evt); // Make sure the first transform on the element is a translate transform var transforms = selectedElement.transform.baseVal; if (transforms.length === 0 || transforms.getItem(0).type !== SVGTransform.SVG_TRANSFORM_TRANSLATE) { // Create an transform that translates by (0, 0) var translate = svg.createSVGTransform(); translate.setTranslate(0, 0); selectedElement.transform.baseVal.insertItemBefore(translate, 0); } // Get initial translation transform = transforms.getItem(0); offset.x -= transform.matrix.e; offset.y -= transform.matrix.f; } function drag(evt) { evt.preventDefault(); if (selectedElement) { var coord = getMousePosition(evt); var dx = coord.x - offset.x; dx = Math.max(dx, 0) transform.setTranslate(dx, 0); } } function endDrag(evt) { selectedElement = false; } } </script> <p><svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 4000 1732" onload="makeDraggable(evt)" cursor="grab"> <image x="40" y="10" id="slide" height="1715" width="3325" xlink:href="/front_slide.png" /> <image xlink:href="/front.png" /> </svg></p> <p>I have put the image above into an SVG to let you drag the card in and out on your screen by clicking and dragging. Try it out! Thanks to <a href="https://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/">Peter Collingridge</a> for the dragging code. If you would like the bare images, here is the <a href="https://aldenbradford.com/front.png">envolope</a> and the <a href="https://aldenbradford.com/front_slide.png">card</a>.</p> <p>On the reverse is a simple table which shows the area of a rectangular ceiling in various dimensions, in case you want to wallpaper your ceiling for some reason.</p> <p><img alt="a table of celing areas" src="https://aldenbradford.com/back.jpeg"></p> <p>On the back of the inserted card is a calendar, hence my claim that this was made in 1923 or 1924.</p> <p><img alt="a 1924 calendar" src="https://aldenbradford.com/back_slide.jpeg"></p> <p>I have not found out much about the company that made this, except for a digitized version of <a href="https://commons.wikimedia.org/wiki/File:Sample_Book,_L.C._Orrell_and_Co.,_Book_No._2,_1906_(CH_18802803-71).jpg">one of their sample catalogs</a>.</p> <h1>How it works</h1> <p>Mostly the mechanism is fairly straightforward. The area of the sides of a room is simply</p> <div class="math">$$2(\text{length}+\text{width})\times\text{height}.$$</div> <p>Each position of the slide corresponds to one sum, <span class="math">$$\text{length}+\text{width}$$</span>. The table of heights just shows the corresponding sum, multiplied by the corresponding height. In fact, the sum itself shows up as well (albeit rounded up) as the "yards of border" entry in the table.</p> <h1>The mystery</h1> <p>Most of the card is quite self-explanatory. There is one thing which still perplexes me though. The calculator claims to give "exact quantities required for over 800 different sized rooms". Where did this number come from? The width is marked out from 7 to 20 giving us 14 widths, and the length is marked from 8 to 20 giving us 13 lengths. Put together with the six given heights, we get <span class="math">$$14\times 13\times 6 = 1092$$</span> dimensions. Wouldn't "over 1000 different sized rooms" have sounded much better? Perhaps they are being more careful than that. After all, an 8-by-10 room is no different from a 10-by-8 room, so maybe they don't want to count those twice. In that case, we would find <span class="math">$$\binom{13}{2}+13=91$$</span> different floor dimensions (choosing two numbers from 8 to 20, or choosing a 7 and a number from 8 to 20). Multiplying by the six heights leaves us 546, woefully short of the promised 800. </p> <p>Perhaps they initially intended to make the whole thing a bit bigger, giving all the dimensions listed on the table on the back? No, this gives us the same problem. Treating length and width as separate things, we get 1122 room sizes, much larger than 800. Treating length and width as interchangeable gets us 726 room sizes, still falling short of 800.</p> <p>If you have any idea where 800 comes from, let me know. I am stumped!</p> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>My proof of Descartes's theorem was just published2022-11-30T00:00:00-05:002022-11-30T00:00:00-05:00Alden Bradfordtag:aldenbradford.com,2022-11-30:/my-proof-of-descartess-theorem-was-just-published.html<p>Where to find my latest publication</p><p>Three months ago, I shared an original proof of Descartes's theorem <a href="https://aldenbradford.com/an-intuitive-proof-of-the-descartes-circle-theorem.html">here on my blog</a>. Today it was finally published! It will be part of the december issue of The Mathematical Intelligencer, but <a href="https://doi.org/10.1007/s00283-022-10234-6">you can already find it online</a>. I made several improvements for clarity, so I consider that version to be definitive. If you don't have access though your library, you can read <a href="https://arxiv.org/abs/2211.05539">a preprint on ArXiV</a> which is almost the same, just with a few more typos.</p>Another little puzzle2022-11-29T00:00:00-05:002022-11-29T00:00:00-05:00Alden Bradfordtag:aldenbradford.com,2022-11-29:/another-little-puzzle.html<p>A little puzzle involving nesteed circles</p><p>Here is another little puzzle. Should be short. Have fun!</p> <p><img alt="the puzzle" src="https://aldenbradford.com/circles.svg"></p>A simple geometry puzzle2022-11-15T00:00:00-05:002022-11-15T00:00:00-05:00Alden Bradfordtag:aldenbradford.com,2022-11-15:/a-simple-geometry-puzzle.html<p>A little puzzle involving a circle with inscribed squares</p><p>I found a fun puzzle last week, which I would like to share with you. It concerns the following figure.</p> <p><img alt="the puzzle" src="https://aldenbradford.com/baseball.svg"></p> <p>You are given a right-angled circle sector, with a square inscribed. Beside the square another square is inscribed. The question for you is: how big are the squares? To make things easier, let's say the circle has a radius of <span class="math">$$\sqrt{250}$$</span>. What, then, are the side lengths of the inscribed squares?</p> <h1>Hints</h1> <p>In case your geometry skills are a bit rusty, I have prepared a series of hints you may find helpful. To avoid giving it all away, I will hide them until you click the button to reveal them.</p> <h2>Hint 1</h2> <p><button onclick="document.getElementById('hint1').toggleAttribute('hidden')">Show</button></p> <div id="hint1" hidden="true"> <p>When you are given a point on the arc of a circle, it is often helpful to draw a line connecting that point to the circle center.</p> </div> <h2>Hint 2</h2> <p><button onclick="document.getElementById('hint2').toggleAttribute('hidden')">Show</button></p> <div id="hint2" hidden="true"> <p>The Pythagorean theorem is your friend. Look for ways to make right triangles.</p> </div> <h2>Hint 3</h2> <p><button onclick="document.getElementById('hint3').toggleAttribute('hidden')">Show</button></p> <div id="hint3" hidden="true"> <p><img alt="the first graphical hint" src="/baseball_hint1.svg"></p> </div> <h2>Hint 4</h2> <p><button onclick="document.getElementById('hint4').toggleAttribute('hidden')">Show</button></p> <div id="hint4" hidden="true"> <p><img alt="the second graphical hint" src="/baseball_hint2.svg"></p> </div> <p><a href="https://aldenbradford.com/baseball_hint1.svg"></a> <a href="https://aldenbradford.com/baseball_hint2.svg"></a></p> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>A Cubic Spline for Animation2022-09-09T00:00:00-04:002022-09-09T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-09-09:/a-cubic-spline-for-animation.html<p>Developing a simple linear program for animating an LED string</p><p>I am designing <a href="https://github.com/AldenMB/addressable_led_patterns">animations for a string of addressable LEDs</a>. Deterministic patterns generally have a nice regularity to them, but their visual interest diminishes after a while. Random patterns can have a bit more visual interest, since they are different every time. If we just assign a random value to each pixel, the result is very busy, loud, disjointed. It would be better to have something changing in a random way, but with a smooth, regular structure so that the eye can actually take it all in.</p> <p>One nice way to get a smooth, organic-feeling function is to use a polynomial spline. We can specify values to achieve at certain times (in animation these are called "key frames") and use a polynomial to interpolate between them. By choosing the spline carefully, we can get a motion which is not only continuous, but smooth in both time and space. It will have no jumps, and no times or places where the color suddenly starts changing in a different way. In this post we will walk through a procedure for generating those splines using linear algebra.</p> <h1>The setting</h1> <p>We have a one-dimensional string of LEDs. We can specify the location of each LED by a number on an interval. To make the numbers work out nice, let's put them on the interval <span class="math">$$[0, 3]$$</span>. For symmetry, let's describe time in the same way, as an interval <span class="math">$$[0, 3]$$</span>. For reasons which will become clear, we will focus on the time subinterval <span class="math">$$[1, 2]$$</span>. We will put the key frames at <span class="math">$$t=0$$</span>, <span class="math">$$t=1$$</span>, <span class="math">$$t=2$$</span>, and <span class="math">$$t=3$$</span>. We will specify each key frame by the values at <span class="math">$$s=0$$</span>, <span class="math">$$s=1$$</span>, <span class="math">$$s=2$$</span>, and <span class="math">$$s=3$$</span>. That is, we begin with a function defined on <span class="math">$$\{0, 1, 2, 3\}\times\{0, 1, 2, 3\}$$</span>, and we wish to extend it to a function on <span class="math">$$[1, 2]\times[0, 3]$$</span>.</p> <p>Why use this specific number of key values? I want to make a cubic polynomial, which we can write as <span class="math">$$f(t, s) = \sum_{i, j=0}^3 A_{ij}t^i s^j$$</span>. This has sixteen degrees of freedom. By specifying the function at sixteen points, we can make a system which is neither over-constrained nor under-constrained.</p> <p>Why go for a cubic function? I want to get a curve which is smooth in time and space. On a given interval, I want it to hit the key frames, but I also want it to be smooth at both ends. That makes four constraints, so a third degree polynomial is just right.</p> <p>Why look at times between 1 and 2? I want to think of time as starting at 1, and proceeding to 2. When the time hits 2, we can extend <span class="math">$$f$$</span> by defining <span class="math">$$f(t, s) = g(t-1, s)$$</span> for <span class="math">$$t\in\{1\}\cup[2,3]$$</span>. In this way, <span class="math">$$g$$</span> has the same form as <span class="math">$$f$$</span>, with values of <span class="math">$$g(t)$$</span> specified for <span class="math">$$t\in \{0, 1, 2\}$$</span>. By dropping the values of <span class="math">$$f$$</span> at <span class="math">$$t=0$$</span> and making new values for <span class="math">$$g$$</span> at <span class="math">$$t=3$$</span>, the program repeats with <span class="math">$$g$$</span> taking the role of <span class="math">$$f$$</span>.</p> <h1>The clever bit</h1> <p>When we restrict <span class="math">$$f$$</span> to <span class="math">$$t=2$$</span>, we get a polynomial in <span class="math">$$s$$</span>. Since it interpolates four values at <span class="math">$$s=0, 1, 2, 3$$</span>, it is completely specified. Since <span class="math">$$g$$</span> restricted to <span class="math">$$t=1$$</span> is also a cubic interpolant, we can see that <span class="math">$$f$$</span> is continuous not only at <span class="math">$$\{2\}\times \{0, 1, 2, 3\}$$</span> but on all of <span class="math">$$\{2\}\times [0, 3]$$</span>. If we are clever about how we specify <span class="math">$$f$$</span>, we can do even better. Notice that <span class="math">$$f_t$$</span> is also a cubic polynomial in <span class="math">$$s$$</span> when restricted to <span class="math">$$t=2$$</span>. If we can specify <span class="math">$$f_t(2, s)=g_t(1, s)$$</span> then we can make <span class="math">$$f'$$</span> continuous as well!</p> <p>How to do this? Since <span class="math">$$f$$</span> is never directly evaluated at <span class="math">$$t=3$$</span>, we will use the funciton values there to instead specify <span class="math">$$f_t$$</span> at <span class="math">$$t=2$$</span>. That is, we will enforce <span class="math">$$f_t(2, s) = (1-c) (f(3, s)-f(1, s))/2$$</span>, where <span class="math">$$c$$</span> is a "tension" term allowing us to tweak the behavior of the interpolant. By assigning the same rule for <span class="math">$$g$$</span>, that is, <span class="math">$$g_t(1, s) = (1-c)(f(3, s)-f(1, s))/2$$</span>, we get a function which is continuous and smooth.</p> <h1>Computing it</h1> <p>We began by writing <span class="math">$$f(t, s)=\sum_{i, j=0}^3 A_{ij}t^i s^j$$</span>. We can write this as a matrix product, </p> <div class="math">$$f(t, s) = \begin{bmatrix} 1 \\ t \\ t^2 \\ t^3 \end{bmatrix}^T A \begin{bmatrix} 1 \\ s \\ s^2 \\ s^3 \end{bmatrix}.$$</div> <p> to keep things tidy, let's define <span class="math">$$P_x = [1, x, x^2, x^3]^T$$</span>. Then we can write this as <span class="math">$$f(t, s)=P_t^T A P_s$$</span>. The goal then is to specify the matrix <span class="math">$$A$$</span>, in terms of the data, the key-frame values of <span class="math">$$f$$</span> on <span class="math">$$\{0, 1, 2, 3\}^2$$</span>. Write <span class="math">$$F=[f(i, j)]_{i, j=0}^3$$</span>, so we are looking for how to write <span class="math">$$A$$</span> in terms of <span class="math">$$F$$</span>.</p> <h2>Continuity</h2> <p>We use the values at <span class="math">$$t=1, 2$$</span> to enforce the continuity of the spline. This is straightforward: we want <span class="math">$$P_1^TAP_s = F_{1s}$$</span> for <span class="math">$$s=0, 1, 2, 3$$</span>. Writing <span class="math">$$P_S = [P_0P_1P_2P_3]$$</span> we can write this more compactly as <span class="math">$$P_1^TAP_S = F_1$$</span>. Similarly, we want <span class="math">$$P_2^TAP_S = F_2$$</span>. Together, these give </p> <div class="math">$$[P_1, P_2]^T A P_S = \begin{bmatrix} 0 &amp; 1 &amp; 0 &amp; 0 \\ 0 &amp; 0 &amp; 1 &amp; 0 \end{bmatrix} F.$$</div> <p>Not enough to solve for <span class="math">$$A$$</span> yet, but we have not used smoothness yet.</p> <h2>Smoothness</h2> <p>Differentiation yields</p> <div class="math">$$f_t(t, s) = \begin{bmatrix} 0 \\ 1 \\ 2t \\ 3t^2 \end{bmatrix}^T A \begin{bmatrix} 1 \\ s \\ s^2 \\ s^3 \end{bmatrix}.$$</div> <p>Write <span class="math">$$D_x=\frac{d}{dx} P_x$$</span>, so we can write this as <span class="math">$$f_t = D_t^T A P_s$$</span>. We want <span class="math">$$f(1, s) = \frac{1-c}{2}(f(2, s) - f(0, s))$$</span>. As a matrix equation, this becomes</p> <div class="math">$$D_1^T A P_S = \frac{1-c}{2} \begin{bmatrix}-1 &amp; 0 &amp; 1 &amp; 0\end{bmatrix}F.$$</div> <p>Repeating for the <span class="math">$$t=2$$</span> case yields</p> <div class="math">$$[D_1, D_2]^T A P_S = \frac{1-c}{2}\begin{bmatrix}-1&amp;0&amp;1&amp;0\\0&amp;-1&amp;0&amp;1\end{bmatrix}F.$$</div> <p>Writing it this way shows how to combine with the continuity equation,</p> <div class="math">$$[P_1, P_2, D_1, D_2]^T A P_S = \frac{1}{2}\left( \begin{bmatrix} 0 &amp; 2 &amp; 0 &amp; 0\\ 0 &amp; 0 &amp; 2 &amp; 0\\ -1&amp; 0 &amp; 1 &amp; 0\\ 0 &amp;-1 &amp; 0 &amp; 1 \end{bmatrix} +c\begin{bmatrix} 0 &amp; 0 &amp; 0 &amp; 0\\ 0 &amp; 0 &amp; 0 &amp; 0\\ 1 &amp; 0 &amp;-1 &amp; 0\\ 0 &amp; 1 &amp; 0 &amp;-1 \end{bmatrix} \right)F.$$</div> <h2>Solving for <span class="math">$$A$$</span></h2> <p>Now all the matrices are square, so we are in business. We have enough to solve for <span class="math">$$A$$</span>. Writing <span class="math">$$U=[P_1,P_2,D_1,D_2]^T$$</span> we find </p> <div class="math">$$U^{-1} = \begin{bmatrix}-4 &amp; 5 &amp; -4 &amp; -2\\12 &amp; -12 &amp; 8 &amp; 5\\-9 &amp; 9 &amp; -5 &amp; -4\\2 &amp; -2 &amp; 1 &amp; 1\end{bmatrix}.$$</div> <p> We can also compute </p> <div class="math">$$P_S^{-1} = \begin{bmatrix}1 &amp; 1 &amp; 1 &amp; 1\\0 &amp; 1 &amp; 2 &amp; 3\\0 &amp; 1 &amp; 4 &amp; 9\\0 &amp; 1 &amp; 8 &amp; 27\end{bmatrix}^{-1} =\frac{1}{6} \begin{bmatrix}6 &amp; -11 &amp; 6 &amp; -1\\0 &amp; 18 &amp; -15 &amp; 3\\0 &amp; -9 &amp; 12 &amp; -3\\0 &amp; 2 &amp; -3 &amp; 1\end{bmatrix}.$$</div> <p> Multiplying on both sides yields</p> <div class="math">$$A = \frac{1}{12} \left(\begin{bmatrix}4 &amp; -6 &amp; 6 &amp; -2\\-8 &amp; 19 &amp; -16 &amp; 5\\5 &amp; -14 &amp; 13 &amp; -4\\-1 &amp; 3 &amp; -3 &amp; 1\end{bmatrix} + c \begin{bmatrix}-4 &amp; -2 &amp; 4 &amp; 2\\8 &amp; 5 &amp; -8 &amp; -5\\-5 &amp; -4 &amp; 5 &amp; 4\\1 &amp; 1 &amp; -1 &amp; -1\end{bmatrix} \right) F \begin{bmatrix}-4 &amp; 5 &amp; -4 &amp; -2\\12 &amp; -12 &amp; 8 &amp; 5\\-9 &amp; 9 &amp; -5 &amp; -4\\2 &amp; -2 &amp; 1 &amp; 1\end{bmatrix}.$$</div> <h2>Applying the formula</h2> <p>Now that we have a formula for <span class="math">$$f(t, s)=P_t^TAP_s$$</span>, we will want to apply it to our light string, which is discrete. Choose some discrete times <span class="math">$$T = [1, 1+\Delta t, 1+2\Delta t, \dots, 2]$$</span> and string positions <span class="math">$$S=[0,\Delta s, 2\Delta s, ..., 3]$$</span>. Form them into polynomials <span class="math">$$P_T$$</span> and <span class="math">$$P_S$$</span> as before, then multiply them on the outside in advance; the coefficient matrices we derived above will not change, only F will change. This can save us a lot of multiplication, since much of it can be done in advance.</p> <h1>Conclusion</h1> <p>I won't belabor this any further here. You can see how this algorithm is implemented <a href="https://github.com/AldenMB/addressable_led_patterns">in the repository I have shared</a>. I think this shows how linear algebra allows us to express some pretty complicated formulas quite compactly, which lets us think about more-complicated things than would otherwise be possible.</p> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>A Problem in Optimal Transport2022-09-02T00:00:00-04:002022-09-02T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-09-02:/a-problem-in-optimal-transport.html<p>We show why convex cost functions lead to certain types of solutions to optimal transport problems, using a simple example.</p><p>I like math classes. I don't like turning in math homework. Lucky for me, I am far enough along in my math education that people mostly don't assign me homework. On the other hand, solving problems is the best way anyone has found so far of learning math. I am taking a course in optimal transport theory, and so I have made some homework for myself.</p> <p>In one dimension, the optimal transport problem is sometimes quite easy to solve. So long as the cost function <span class="math">$$c(x,y)$$</span> can be written as <span class="math">$$\phi(|x-y|)$$</span> where <span class="math">$$\phi$$</span> is convex and increasing, then there is an optimal transport plan <span class="math">$$T$$</span> and it is monotone, in the sense that if <span class="math">$$x_1&lt;x_2$$</span> then <span class="math">$$T(x_1)\leq T(x_2)$$</span>. Why is this? I feel we can get to the heart of it by discarding concerns about complicated distributions and sigma algebras. Let's focus on the case where we just have two point masses to move to two targets. That is, our source distribution is <span class="math">$$\delta(x_1)+\delta(x_2)$$</span> and the target is <span class="math">$$\delta(y_1)+\delta(y_2)$$</span>. We will take <span class="math">$$x_1&lt;x_2$$</span> and <span class="math">$$y_1&lt;y_2$$</span>. There are two transport plans to consider. </p> <div class="math">\begin{align*} T_\parallel&amp;\begin{cases} x_1 \mapsto y_1 \\ x_2\mapsto y_2 \end{cases} \\ T_\perp&amp;\begin{cases} x_1 \mapsto y_2 \\ x_2\mapsto y_1 \end{cases} \end{align*}</div> <p> In a sense the plan <span class="math">$$T_\perp$$</span> crosses itself, while <span class="math">$$T_\parallel$$</span> does not. Our claim is that, given <span class="math">$$\phi$$</span> is convex and nondecreasing, <span class="math">$$c(T_\parallel)\leq c(T_\perp)$$</span>. We will prove this together, paying special attention to where we use each of the conditions on <span class="math">$$\phi$$</span>. That is, we will show that </p> <div class="math">$$\phi(|y_1-x_1|)+\phi(|y_2-x_2|) \leq \phi(|y_1-x_2|)+\phi(|y_2-x_1|).\tag{*}$$</div> <h1>The proof</h1> <p>Inequality <span class="math">$$(*)$$</span> is symmetric in terms of <span class="math">$$x$$</span> and <span class="math">$$y$$</span>, so we can take <span class="math">$$x_1&lt;y_1$$</span> without loss of generality. Let's treat two cases, either <span class="math">$$x_2&gt;y_2$$</span> or <span class="math">$$x_2&lt;y_2$$</span>.</p> <h2>When <span class="math">$$x_2&gt;y_2$$</span></h2> <p>Our assumptions together tell us <span class="math">$$x_1\leq y_1\leq y_2 \leq x_2$$</span>. Then our goal <span class="math">$$(*)$$</span> can be written as </p> <div class="math">$$\phi(y_1-x_1)+\phi(x_2-y_2) \leq \phi(y_2-x_1)+\phi(x_2-y_1).$$</div> <p> This case is solved by the nondecreasing assumption on <span class="math">$$\phi$$</span>, since <span class="math">$$\phi(y_1-x_1)\leq \phi(y_2-x_1)$$</span> and <span class="math">$$\phi(x_2-y_2)\leq \phi(x_2-y_1)$$</span>. We do not require convexity at all in this case.</p> <h2>When <span class="math">$$x_2&lt;y_2$$</span></h2> <p>We can rewrite our goal <span class="math">$$(*)$$</span> as </p> <div class="math">$$\phi(y_1-x_1)+\phi(y_2-x_2) \leq \phi(y_2-x_1) + \phi(|y_1-x_2|).$$</div> <p> By repeatedly applying <span class="math">$$x_1&lt;x_2$$</span> and <span class="math">$$y_1&lt;y_2$$</span> we find <span class="math">$$y_1-x_2\leq y_1-x_1\leq y_2-x_1$$</span> and <span class="math">$$y_1-x_2\leq y_2-x_2 \leq y_2-x_1$$</span>. By convexity, we have </p> <div class="math">\begin{align*} \phi(y_1-x_1)&amp;\leq \frac{y_2-y_1}{y_2-y_1+x_2-x_1}\phi(y_2-x_1)+\frac{x_2-x_1}{y_2-y_1+x_2-x_1}\phi(y_1-x_2),\\ \phi(y_2-x_2)&amp;\leq \frac{x_2-x_1}{y_2-y_1+x_2-x_1}\phi(y_2-x_1)+\frac{y_2-y_1}{y_2-y_1+x_2-x_1}\phi(y_1-x_2). \end{align*}</div> <p> Adding these yields </p> <div class="math">$$\phi(y_1-x_1)+\phi(y_2-x_2) \leq \phi(y_2-x_1) + \phi(y_1-x_2).$$</div> <p> Finally, the nondecreasing assumption tells us <span class="math">$$\phi(y_1-x_2)\leq \phi(|y_1-x_2|)$$</span>.</p> <h1>Conclusion</h1> <p>The <span class="math">$$x_2&gt;y_2$$</span> case above relied only on the nondecreasing assumption. Indeed, it seems to me that it is both necessary and sufficient in that case. That's the case where both distances are reduced by changing from <span class="math">$$T_\perp$$</span> to <span class="math">$$T_\parallel$$</span>. In the other case, <span class="math">$$x_2&lt;y_2$$</span>, we used convexity because <span class="math">$$|x_1-T(x_1)|$$</span> decreased while <span class="math">$$|x_2-T(x_2)|$$</span> increased. Convexity allows us to express the "diminishing return" property of a cost, where it's at least twice as hard to move a mass twice as far. In this case the trade was worth it, because <span class="math">$$x_1$$</span> had farther to go.</p> <p>None of this is new stuff. Looking ahead, proposition 4.5 of Luigi Ambrosio's book treats a very similar question, from a slightly less elementary perspective. However, I do think this example is illustrative. It shows exactly what aspect of the cost function we are taking advantage of, when we use the convexity property.</p> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>An intuitive proof of the Descartes circle theorem2022-09-01T00:00:00-04:002022-09-01T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-09-01:/an-intuitive-proof-of-the-descartes-circle-theorem.html<p>An original, intuitive proof of the Descartes circle theorem.</p><p>I have been a little obsessed with the Descartes circle theorem for a bit over a year now, ever since I came across it when looking for fun fractals to draw. The theorem is essential for quickly computing the circles in an Apollonian gasket. As a reminder, it says that when you have four circles which are pairwise tangent, their radii satisfy </p> <div class="math">$$\left(\frac{1}{r_1}+\frac{1}{r_2}+\frac{1}{r_3}+\frac{1}{r_4}\right)^2 = 2\left(\frac{1}{r_1^2}+\frac{1}{r_2^2}+\frac{1}{r_3^2}+\frac{1}{r_4^2}\right),$$</div> <p> where the radius is taken to be negative if the corresponding circle encloses the others. This convention allows us to say <span class="math">$$d_{12} = r_1 + r_2$$</span> where <span class="math">$$d_{12}$$</span> is the distance between the centers of circles 1 and 2.</p> <p>I have come up with a proof for this theorem which I like. People have been coming up with proofs of this theorem for centuries, some of them quite elaborate. The most elegant one I have found is due to Jerzy Kocik. It treats the generalized version of the theorem which was <a href="https://arxiv.org/abs/math/0101066">discovered in 2001</a>, which gives a similar relation between the centers of the circles treating them as complex numbers. Kocik's proof is <a href="https://arxiv.org/abs/1910.09174">available on arXiv</a>.</p> <p>The following proof does not treat the generalized version of the theorem, but it does a better job of giving the intuitive reason for the theorem than any other proof I have seen. It is certainly shorter than any proof I have seen elsewhere.</p> <h1>Heron's formula and the Cayley-Menger determinant</h1> <p>Most people who have studied any geometry have at least heard of Heron's formula. There are many ways to state it. Here is one of them. It states that for any triangle with area <span class="math">$$A$$</span> and side lengths <span class="math">$$a$$</span>, <span class="math">$$b$$</span>, and <span class="math">$$c$$</span>, we have the equation</p> <div class="math">$$4A^2 = (a^2+b^2+c^2)^2 - 2(a^4+b^4+c^4).$$</div> <p>Perhaps this way of writing it already has stirred some inspiration in you; it certainly bears a passing resemblance to the form in the Descartes theorem. As I say, there are many ways to write Heron's formula. Here is another.</p> <div class="math">$$-16A^2 = \begin{vmatrix} 0 &amp; 1 &amp; 1 &amp; 1\\ 1 &amp; 0 &amp; c^2 &amp; b^2\\ 1 &amp; c^2 &amp; 0 &amp; a^2\\ 1 &amp; b^2 &amp; a^2 &amp; 0 \end{vmatrix}.$$</div> <p>For this proof I prefer this form because it generalizes nicely to higher dimensions. This is the <a href="https://en.wikipedia.org/wiki/Cayley%E2%80%93Menger_determinant">Cayley-Menger determinant</a> which gives a formula for the volume of an <span class="math">$$n$$</span>-dimensional simplex in terms of its edge lengths. For our proof we will use the formula for the volume of the tetrahedron,</p> <div class="math">$$288V^2= \begin{vmatrix} 0&amp;1&amp;1&amp;1&amp;1\\ 1&amp;0&amp;d_{12}^{2}&amp;d_{13}^{2}&amp;d_{14}^{2}\\ 1&amp;d_{21}^{2}&amp;0&amp;d_{23}^{2}&amp;d_{24}^{2}\\ 1&amp;d_{31}^{2}&amp;d_{32}^{2}&amp;0&amp;d_{34}^{2}\\ 1&amp;d_{42}^{2}&amp;d_{42}^{2}&amp;d_{43}^{2}&amp;0\\ \end{vmatrix}.$$</div> <h1>The proof</h1> <p>Begin with four pairwise-tangent spheres with radii <span class="math">$$r_1$$</span>, <span class="math">$$r_2$$</span>, <span class="math">$$r_3$$</span>, and <span class="math">$$r_4$$</span>. The centers of these spheres define a tetrahedron, and the Cayley-Menger determinant formula gives us the volume of that tetrahedron in terms of <span class="math">$$d_{ij}=r_i+r_j$$</span>. After simplification, we are left with the tidy formula</p> <div class="math">$$V^2 = \left(\frac{r_1r_2r_3r_4}{3}\right)^2\left[\left(\frac{1}{r_1}+\frac{1}{r_2}+\frac{1}{r_3}+\frac{1}{r_4}\right)^2 -2\left(\frac{1}{r_1^2}+\frac{1}{r_2^2}+\frac{1}{r_3^2}+\frac{1}{r_4^2}\right)\right].$$</div> <p>Now we just observe that if the four centers lie in a plane, their tetrahedron must have volume zero. If all the radii are nonzero, the term <span class="math">$$r_1r_2r_3r_4$$</span> cannot be zero, so that leaves</p> <div class="math">$$\left(\frac{1}{r_1}+\frac{1}{r_2}+\frac{1}{r_3}+\frac{1}{r_4}\right)^2 -2\left(\frac{1}{r_1^2}+\frac{1}{r_2^2}+\frac{1}{r_3^2}+\frac{1}{r_4^2}\right)=0$$</div> <p>and the proof is complete.</p> <h1>The details</h1> <p>I should really justify the volume formula I gave above, since it is not obvious and it is essential to the proof. It really is a "follow your nose" kind of derivation, starting with the Cayley-Menger determinant and performing straightforward simplifications. We use these common determinant simplification rules.</p> <ul> <li>Adding (or subtracting) one row or column to another does not change the determinant.</li> <li>Multiplying a row or column by a constant scales the determinant by that multiplicative factor.</li> <li>When <span class="math">$$D$$</span> is invertible, the block determinant <div class="math">$$\begin{vmatrix}A&amp;B\\ C&amp;D\end{vmatrix}$$</div> can be computed as <span class="math">$$\left| D\right | \left| A - B D^{-1} C\right|$$</span>.</li> </ul> <p>Since I write out the entire matrix on each line this fills a lot of space, but there are really very few steps involved. Without further ado, the algebra.</p> <div class="math">\begin{align*} 288V^2 &amp;= \begin{vmatrix} 0&amp;1&amp;1&amp;1&amp;1\\ 1&amp;0&amp;d_{12}^{2}&amp;d_{13}^{2}&amp;d_{14}^{2}\\ 1&amp;d_{21}^{2}&amp;0&amp;d_{23}^{2}&amp;d_{24}^{2}\\ 1&amp;d_{31}^{2}&amp;d_{32}^{2}&amp;0&amp;d_{34}^{2}\\ 1&amp;d_{42}^{2}&amp;d_{42}^{2}&amp;d_{43}^{2}&amp;0\\ \end{vmatrix}\\ &amp;= \begin{vmatrix} 0&amp;1&amp;1&amp;1&amp;1\\ 1&amp;0&amp;(r_1+r_2)^2&amp;(r_1+r_3)^2&amp;(r_1+r_4)^2\\ 1&amp;(r_2+r_1)^2&amp;0&amp;(r_2+r_3)^2&amp;(r_2+r_4)^2\\ 1&amp;(r_3+r_1)^2&amp;(r_3+r_2)^2&amp;0&amp;(r_3+r_4)^2\\ 1&amp;(r_4+r_1)^2&amp;(r_4+r_2)^2&amp;(r_4+r_3)^2&amp;0\\ \end{vmatrix}\\ \frac{288V^2}{(r_1r_2r_3r_4)^4}&amp;= \begin{vmatrix} 0&amp;\frac{1}{r_1^2}&amp;\frac{1}{r_2^2}&amp;\frac{1}{r_3^2}&amp;\frac{1}{r_4^2}\\ \frac{1}{r_1^2}&amp;0&amp;(\frac{1}{r_1}+\frac{1}{r_2})^2&amp;(\frac{1}{r_1}+\frac{1}{r_3})^2&amp;(\frac{1}{r_1}+\frac{1}{r_4})^2\\ \frac{1}{r_2^2}&amp;(\frac{1}{r_2}+\frac{1}{r_1})^2&amp;0&amp;(\frac{1}{r_2}+\frac{1}{r_3})^2&amp;(\frac{1}{r_2}+\frac{1}{r_4})^2\\ \frac{1}{r_3^2}&amp;(\frac{1}{r_3}+\frac{1}{r_1})^2&amp;(\frac{1}{r_3}+\frac{1}{r_2})^2&amp;0&amp;(\frac{1}{r_3}+\frac{1}{r_4})^2\\ \frac{1}{r_4^2}&amp;(\frac{1}{r_4}+\frac{1}{r_1})^2&amp;(\frac{1}{r_4}+\frac{1}{r_2})^2&amp;(\frac{1}{r_4}+\frac{1}{r_3})^2&amp;0\\ \end{vmatrix}\\ &amp;= \begin{vmatrix} 0&amp;\frac{1}{r_1^2}&amp;\frac{1}{r_2^2}&amp;\frac{1}{r_3^2}&amp;\frac{1}{r_4^2}\\ \frac{1}{r_1^2}&amp;0&amp;\frac{1}{r_1^2}+\frac{2}{r_1r_2}+\frac{1}{r_2^2}&amp;\frac{1}{r_1^2}+\frac{2}{r_1r_3}+\frac{1}{r_3^2}&amp;\frac{1}{r_1^2}+\frac{2}{r_1r_4}+\frac{1}{r_4^2}\\ \frac{1}{r_2^2}&amp;\frac{1}{r_2^2}+\frac{2}{r_2r_1}+\frac{1}{r_1^2}&amp;0&amp;\frac{1}{r_2^2}+\frac{2}{r_2r_3}+\frac{1}{r_3^2}&amp;\frac{1}{r_2^2}+\frac{2}{r_2r_4}+\frac{1}{r_4^2}\\ \frac{1}{r_3^2}&amp;\frac{1}{r_3^2}+\frac{2}{r_3r_1}+\frac{1}{r_1^2}&amp;\frac{1}{r_3^2}+\frac{2}{r_3r_2}+\frac{1}{r_2^2}&amp;0&amp;\frac{1}{r_3^2}+\frac{2}{r_3r_4}+\frac{1}{r_4^2}\\ \frac{1}{r_4^2}&amp;\frac{1}{r_4^2}+\frac{2}{r_4r_1}+\frac{1}{r_1^2}&amp;\frac{1}{r_4^2}+\frac{2}{r_4r_2}+\frac{1}{r_2^2}&amp;\frac{1}{r_4^2}+\frac{2}{r_4r_3}+\frac{1}{r_3^2}&amp;0\\ \end{vmatrix}\\ &amp;= \begin{vmatrix} 0&amp;\frac{1}{r_1^2}&amp;\frac{1}{r_2^2}&amp;\frac{1}{r_3^2}&amp;\frac{1}{r_4^2}\\ \frac{1}{r_1^2}&amp;\frac{-2}{r_1^2}&amp;\frac{2}{r_1r_2}&amp;\frac{2}{r_1r_3}&amp;\frac{2}{r_1r_4}\\ \frac{1}{r_2^2}&amp;\frac{2}{r_2r_1}&amp;\frac{-2}{r_2^2}&amp;\frac{2}{r_2r_3}&amp;\frac{2}{r_2r_4}\\ \frac{1}{r_3^2}&amp;\frac{2}{r_3r_1}&amp;\frac{2}{r_3r_2}&amp;\frac{-2}{r_3^2}&amp;\frac{2}{r_3r_4}\\ \frac{1}{r_4^2}&amp;\frac{2}{r_4r_1}&amp;\frac{2}{r_4r_2}&amp;\frac{2}{r_4r_3}&amp;\frac{-2}{r_4^2}\\ \end{vmatrix}\\ \frac{288V^2}{(r_1r_2r_3r_4)^2}&amp;= \begin{vmatrix} 0&amp;\frac{1}{r_1}&amp;\frac{1}{r_2}&amp;\frac{1}{r_3}&amp;\frac{1}{r_4}\\ \frac{1}{r_1}&amp;-2&amp;2&amp;2&amp;2\\ \frac{1}{r_2}&amp;2&amp;-2&amp;2&amp;2\\ \frac{1}{r_3}&amp;2&amp;2&amp;-2&amp;2\\ \frac{1}{r_4}&amp;2&amp;2&amp;2&amp;-2 \end{vmatrix}\\ \frac{288V^2}{8(r_1r_2r_3r_4)^2}&amp;= \begin{vmatrix} 0&amp;\frac{1}{r_1}&amp;\frac{1}{r_2}&amp;\frac{1}{r_3}&amp;\frac{1}{r_4}\\ \frac{1}{r_1}&amp;-1&amp;1&amp;1&amp;1\\ \frac{1}{r_2}&amp;1&amp;-1&amp;1&amp;1\\ \frac{1}{r_3}&amp;1&amp;1&amp;-1&amp;1\\ \frac{1}{r_4}&amp;1&amp;1&amp;1&amp;-1 \end{vmatrix}\\ &amp;=-\begin{vmatrix} -1&amp;1&amp;1&amp;1\\ 1&amp;-1&amp;1&amp;1\\ 1&amp;1&amp;-1&amp;1\\ 1&amp;1&amp;1&amp;-1 \end{vmatrix} \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}^T \begin{bmatrix} -1&amp;1&amp;1&amp;1\\ 1&amp;-1&amp;1&amp;1\\ 1&amp;1&amp;-1&amp;1\\ 1&amp;1&amp;1&amp;-1 \end{bmatrix}^{-1} \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix} \end{align*}</div> <p>Notice the matrix </p> <div class="math">$$D=\begin{bmatrix} -1&amp;1&amp;1&amp;1\\ 1&amp;-1&amp;1&amp;1\\ 1&amp;1&amp;-1&amp;1\\ 1&amp;1&amp;1&amp;-1 \end{bmatrix}$$</div> <p> satisfies <span class="math">$$D^2=4I$$</span>. Thus, <span class="math">$$D^{-1} = \frac{1}{4}D$$</span>. We can also compute <span class="math">$$|D| = -16$$</span>. Just a couple more lines and we are done with the algebra.</p> <div class="math">\begin{align*} \frac{288V^2}{32(r_1r_2r_3r_4)^2} &amp;= \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}^T \begin{bmatrix} -1&amp;1&amp;1&amp;1\\ 1&amp;-1&amp;1&amp;1\\ 1&amp;1&amp;-1&amp;1\\ 1&amp;1&amp;1&amp;-1 \end{bmatrix} \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}\\ \frac{9V^2}{(r_1r_2r_3r_4)^2} &amp;= \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}^T \begin{bmatrix} 1&amp;1&amp;1&amp;1\\ 1&amp;1&amp;1&amp;1\\ 1&amp;1&amp;1&amp;1\\ 1&amp;1&amp;1&amp;1 \end{bmatrix} \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix} -2\begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}^T \begin{bmatrix} 1&amp;0&amp;0&amp;0\\ 0&amp;1&amp;0&amp;0\\ 0&amp;0&amp;1&amp;0\\ 0&amp;0&amp;0&amp;1 \end{bmatrix} \begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}\\ &amp;=\left(\frac{1}{r_1}+\frac{1}{r_2}+\frac{1}{r_3}+\frac{1}{r_4}\right)^2 -2\left(\frac{1}{r_1^2}+\frac{1}{r_2^2}+\frac{1}{r_3^2}+\frac{1}{r_4^2}\right) \end{align*}</div> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>Return to Battleship2022-08-28T00:00:00-04:002022-08-28T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-08-28:/battleship.html<p>Reflecting on an old coding project, and what I would do differently now.</p><p>Particularly in grad school it can feel like your life is standing still. Because you are always learning new things, pushing yourself outside of your comfort zone, it can seem like you have not mastered anything. This is an illusion. To prove this, all you need to do is look at something you made before and feel the cringe. If you cringe at the way you solved a problem before, that is a sure sign that you know better now than you did then.</p> <p>I am in the midst of refreshing my math department webpage, removing what is out-of-date and preserving what deserves to be preserved. Four years ago, I was teaching myself Javascript. I was also frequently playing Battleship over the phone with my then-girlfriend, now-wife. I made a quick tool to generate ship placements for me. Last year I heard from an old friend that they had actually found the tool on a search, and that it had been useful. For that reason, I think it's worth preserving. I have copied it below.</p> <p>I know better than to jump back in to a completed project. The tool I made works, and it works well. To redo it would be a wasted effort. However, the power of cringe is strong. I feel compelled to at least comment on the bad parts of the code, if only to reassure myself that I have indeed grown.</p> <h1>The tool</h1> <p>This is a placement generator for Battleship. It will give you a randomly selected arrangement of your ships on the board. Here, "randomly selected" means that every possible arrangement is equally likely.</p> <form id="input_form"> Rows:<br> <input type="number" name="rows" max="99" min="1" value="10"><br> Columns:<br> <input type="number" name="columns" max="52" min="1" value="10"><br> Ships:<br> <textarea name="ships" rows="3" cols="17"> (C,5),(B,4),(c,3),(S,3),(D,2) </textarea><br> <input type="reset"> <button type="button" id="generate" onclick="generateBoard()">Generate!</button> </form> <pre id="the_board"> </pre> <p>Written by Alden Bradford on November 1-2, 2018. Generated placements are guaranteed to be (pseudo) random, but not guaranteed to be good. Use at your own risk.</p> <script> "use strict"; var theForm = document.getElementById("input_form"); function generateBoard(){ let board = new Board(theForm); let container = document.getElementById("the_board"); board.placeShips(); if (!board.failed){ container.innerHTML = board.appearance(); } else { container.innerHTML = "I tried 1,000,000 times, but \nI could not make the board work."; } } function parseShips(string){ let inputList = string.match(/\w,\d+/g); let ships = []; for(let i = 0; i < inputList.length; i++){ let pair = []; pair = inputList[i].match(/\w/); pair = parseInt(inputList[i].match(/\d+/),10); ships[i] = pair; } return ships; } class Board { constructor(form){ this.rows = form.elements.rows.valueAsNumber; this.cols = form.elements.columns.valueAsNumber; this.cells = []; this.initializeCells(); this.shipPairs = parseShips(form.elements.ships.value); this.failed = false; } initializeCells(){ this.cells = []; for(let i=0;i<this.cols;i++){ this.cells[i]=[]; for(let j=0;j<this.rows;j++){ this.cells[i][j]= new Cell(); } } } appearance(){ let string = " A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z "; string = string.slice(0,this.cols*2+2); string = string.concat('\n'); for(let j = 0; j < this.rows; j++){ if( j < 9) string = string.concat(' '); string = string.concat(${j+1}); for(let i = 0; i < this.cols; i++){ string = string.concat(" "+this.cells[i][j].appearance()); } string = string.concat('\n'); } string = string.slice(0,-1); return string; } placeShip(charLenPair){ let direction = ["v","h"][Math.floor(Math.random()*2)]; let max_col = this.cols; let max_row = this.rows; if(direction === "v") max_row -= (charLenPair-1); if(direction === "h") max_col -= (charLenPair-1); if(max_row <1 || max_col<1) return false; let corner=[Math.floor(Math.random()*max_col), Math.floor(Math.random()*max_row)]; let newShip = new Ship(charLenPair,direction,corner,this); if(newShip.canFit()){ newShip.insert(); return true; } else { return false; } } placeShips(){ for(let attempt = 0; attempt < 1000000; attempt++){ this.initializeCells(); let attemptSuccessful = true; for(let i = 0; i<this.shipPairs.length; i++){ if(!this.placeShip(this.shipPairs[i])){ attemptSuccessful = false; break; } } if(attemptSuccessful){ return; } } this.failed = true; } } class Ship{ constructor(charLenPair,orientation,corner,board){ this.appearance = charLenPair; this.length = charLenPair; this.orientation = orientation; this.corner = corner; this.board = board; } cells(){ let cellList = []; if(this.orientation === "h"){ for(let i = 0; i < this.length; i++){ cellList[i] = this.board.cells[this.corner+i][this.corner ]; } } if(this.orientation === "v"){ for(let i = 0; i < this.length; i++){ cellList[i] = this.board.cells[this.corner ][this.corner+i]; } } return cellList; } canFit(){ let cellList = this.cells(); for(let i = 0; i<this.length; i++){ if(cellList[i].contains) return false; } return true; } insert(){ let cellList=this.cells(); for(let i = 0; i<this.length; i++){ cellList[i].contains = this; } } } class Cell { constructor(){ this.contains = null; } appearance(){ return this.contains ? this.contains.appearance : '~'; } } window.addEventListener('load', (event) => generateBoard()); </script> <h1>The good</h1> <p>One of the things which makes this particular tool so useful is that it is written in pure Javascript. Everyone with a modern web browser has a Javascript interpreter already installed and ready to go. The code is so portable that I was able to copy it directly here with no changes, and it still works perfectly. This is why I think Javascript is a good first language for many people to learn. There are exceptions, of course -- if you want to do data science you should learn Python, if you want to program Arduino you should learn C, et cetera. For most people, the benefit of having your code run anywhere outweighs the cost of having to deal with Javascript's peculiarities. Perhaps this will change soon, as there are several attempts in the works to make Python available in the browser. For now, Javascript has a strong advantage.</p> <p>Let's have a look at my old code and see what nice qualities it has, lest we become overcritical.</p> <div class="highlight"><pre><span></span><code><span class="s2">&quot;use strict&quot;</span><span class="p">;</span> <span class="kd">var</span><span class="w"> </span><span class="nx">theForm</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&quot;input_form&quot;</span><span class="p">);</span> <span class="kd">function</span><span class="w"> </span><span class="nx">generateBoard</span><span class="p">(){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">board</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">Board</span><span class="p">(</span><span class="nx">theForm</span><span class="p">);</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">container</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&quot;the_board&quot;</span><span class="p">);</span> <span class="w"> </span><span class="nx">board</span><span class="p">.</span><span class="nx">placeShips</span><span class="p">();</span> <span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="nx">board</span><span class="p">.</span><span class="nx">failed</span><span class="p">){</span> <span class="w"> </span><span class="nx">container</span><span class="p">.</span><span class="nx">innerHTML</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">board</span><span class="p">.</span><span class="nx">appearance</span><span class="p">();</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="nx">container</span><span class="p">.</span><span class="nx">innerHTML</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="s2">&quot;I tried 1,000,000 times, but \nI could not make the board work.&quot;</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span> <span class="p">}</span> <span class="kd">function</span><span class="w"> </span><span class="nx">parseShips</span><span class="p">(</span><span class="nx">string</span><span class="p">){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">inputList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">match</span><span class="p">(</span><span class="sr">/\w,\d+/g</span><span class="p">);</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">ships</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="nx">inputList</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">pair</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span> <span class="w"> </span><span class="nx">pair</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">inputList</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">match</span><span class="p">(</span><span class="sr">/\w/</span><span class="p">)[</span><span class="mf">0</span><span class="p">];</span> <span class="w"> </span><span class="nx">pair</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">parseInt</span><span class="p">(</span><span class="nx">inputList</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">match</span><span class="p">(</span><span class="sr">/\d+/</span><span class="p">)[</span><span class="mf">0</span><span class="p">],</span><span class="mf">10</span><span class="p">);</span> <span class="w"> </span><span class="nx">ships</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">pair</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">ships</span><span class="p">;</span> <span class="p">}</span> <span class="kd">class</span><span class="w"> </span><span class="nx">Board</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kr">constructor</span><span class="p">(</span><span class="nx">form</span><span class="p">){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">rows</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">form</span><span class="p">.</span><span class="nx">elements</span><span class="p">.</span><span class="nx">rows</span><span class="p">.</span><span class="nx">valueAsNumber</span><span class="p">;</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cols</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">form</span><span class="p">.</span><span class="nx">elements</span><span class="p">.</span><span class="nx">columns</span><span class="p">.</span><span class="nx">valueAsNumber</span><span class="p">;</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">initializeCells</span><span class="p">();</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">shipPairs</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">parseShips</span><span class="p">(</span><span class="nx">form</span><span class="p">.</span><span class="nx">elements</span><span class="p">.</span><span class="nx">ships</span><span class="p">.</span><span class="nx">value</span><span class="p">);</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">failed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">initializeCells</span><span class="p">(){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="o">=</span><span class="mf">0</span><span class="p">;</span><span class="nx">i</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">cols</span><span class="p">;</span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="o">=</span><span class="p">[];</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">j</span><span class="o">=</span><span class="mf">0</span><span class="p">;</span><span class="nx">j</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">rows</span><span class="p">;</span><span class="nx">j</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="p">[</span><span class="nx">i</span><span class="p">][</span><span class="nx">j</span><span class="p">]</span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">Cell</span><span class="p">();</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">appearance</span><span class="p">(){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span> <span class="w"> </span><span class="s2">&quot; A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z &quot;</span><span class="p">;</span> <span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="k">this</span><span class="p">.</span><span class="nx">cols</span><span class="o">*</span><span class="mf">2</span><span class="o">+</span><span class="mf">2</span><span class="p">);</span> <span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="s1">&#39;\n&#39;</span><span class="p">);</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">rows</span><span class="p">;</span><span class="w"> </span><span class="nx">j</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="w"> </span><span class="nx">j</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mf">9</span><span class="p">)</span><span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="s1">&#39; &#39;</span><span class="p">);</span> <span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="sb"></span><span class="si">${</span><span class="nx">j</span><span class="o">+</span><span class="mf">1</span><span class="si">}</span><span class="sb"></span><span class="p">);</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cols</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="s2">&quot; &quot;</span><span class="o">+</span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="p">[</span><span class="nx">i</span><span class="p">][</span><span class="nx">j</span><span class="p">].</span><span class="nx">appearance</span><span class="p">());</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">concat</span><span class="p">(</span><span class="s1">&#39;\n&#39;</span><span class="p">);</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">string</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">string</span><span class="p">.</span><span class="nx">slice</span><span class="p">(</span><span class="mf">0</span><span class="p">,</span><span class="o">-</span><span class="mf">1</span><span class="p">);</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">string</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">placeShip</span><span class="p">(</span><span class="nx">charLenPair</span><span class="p">){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">direction</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[</span><span class="s2">&quot;v&quot;</span><span class="p">,</span><span class="s2">&quot;h&quot;</span><span class="p">][</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span><span class="o">*</span><span class="mf">2</span><span class="p">)];</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">max_col</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cols</span><span class="p">;</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">max_row</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">rows</span><span class="p">;</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="nx">direction</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s2">&quot;v&quot;</span><span class="p">)</span><span class="w"> </span><span class="nx">max_row</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="p">(</span><span class="nx">charLenPair</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="o">-</span><span class="mf">1</span><span class="p">);</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="nx">direction</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s2">&quot;h&quot;</span><span class="p">)</span><span class="w"> </span><span class="nx">max_col</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="p">(</span><span class="nx">charLenPair</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="o">-</span><span class="mf">1</span><span class="p">);</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="nx">max_row</span><span class="w"> </span><span class="o">&lt;</span><span class="mf">1</span><span class="w"> </span><span class="o">||</span><span class="w"> </span><span class="nx">max_col</span><span class="o">&lt;</span><span class="mf">1</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">corner</span><span class="o">=</span><span class="p">[</span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span><span class="o">*</span><span class="nx">max_col</span><span class="p">),</span> <span class="w"> </span><span class="nb">Math</span><span class="p">.</span><span class="nx">floor</span><span class="p">(</span><span class="nb">Math</span><span class="p">.</span><span class="nx">random</span><span class="p">()</span><span class="o">*</span><span class="nx">max_row</span><span class="p">)];</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">newShip</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="ow">new</span><span class="w"> </span><span class="nx">Ship</span><span class="p">(</span><span class="nx">charLenPair</span><span class="p">,</span><span class="nx">direction</span><span class="p">,</span><span class="nx">corner</span><span class="p">,</span><span class="k">this</span><span class="p">);</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="nx">newShip</span><span class="p">.</span><span class="nx">canFit</span><span class="p">()){</span> <span class="w"> </span><span class="nx">newShip</span><span class="p">.</span><span class="nx">insert</span><span class="p">();</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span><span class="k">else</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">placeShips</span><span class="p">(){</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">attempt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">attempt</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="mf">1000000</span><span class="p">;</span><span class="w"> </span><span class="nx">attempt</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">initializeCells</span><span class="p">();</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">attemptSuccessful</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">shipPairs</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="k">this</span><span class="p">.</span><span class="nx">placeShip</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">shipPairs</span><span class="p">[</span><span class="nx">i</span><span class="p">])){</span> <span class="w"> </span><span class="nx">attemptSuccessful</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span> <span class="w"> </span><span class="k">break</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="nx">attemptSuccessful</span><span class="p">){</span> <span class="w"> </span><span class="k">return</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">failed</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">class</span><span class="w"> </span><span class="nx">Ship</span><span class="p">{</span> <span class="w"> </span><span class="kr">constructor</span><span class="p">(</span><span class="nx">charLenPair</span><span class="p">,</span><span class="nx">orientation</span><span class="p">,</span><span class="nx">corner</span><span class="p">,</span><span class="nx">board</span><span class="p">){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">appearance</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">charLenPair</span><span class="p">[</span><span class="mf">0</span><span class="p">];</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">length</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">charLenPair</span><span class="p">[</span><span class="mf">1</span><span class="p">];</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">orientation</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">orientation</span><span class="p">;</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">corner</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">corner</span><span class="p">;</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">board</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nx">board</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">cells</span><span class="p">(){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">cellList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">[];</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">orientation</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s2">&quot;h&quot;</span><span class="p">){</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="nx">cellList</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">board</span><span class="p">.</span><span class="nx">cells</span><span class="p">[</span><span class="k">this</span><span class="p">.</span><span class="nx">corner</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span><span class="o">+</span><span class="nx">i</span><span class="p">][</span><span class="k">this</span><span class="p">.</span><span class="nx">corner</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="w"> </span><span class="p">];</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="k">this</span><span class="p">.</span><span class="nx">orientation</span><span class="w"> </span><span class="o">===</span><span class="w"> </span><span class="s2">&quot;v&quot;</span><span class="p">){</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">&lt;</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="nx">cellList</span><span class="p">[</span><span class="nx">i</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">board</span><span class="p">.</span><span class="nx">cells</span><span class="p">[</span><span class="k">this</span><span class="p">.</span><span class="nx">corner</span><span class="p">[</span><span class="mf">0</span><span class="p">]</span><span class="w"> </span><span class="p">][</span><span class="k">this</span><span class="p">.</span><span class="nx">corner</span><span class="p">[</span><span class="mf">1</span><span class="p">]</span><span class="o">+</span><span class="nx">i</span><span class="p">];</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="nx">cellList</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">canFit</span><span class="p">(){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">cellList</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="p">();</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="k">if</span><span class="p">(</span><span class="nx">cellList</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">contains</span><span class="p">)</span><span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">false</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="kc">true</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">insert</span><span class="p">(){</span> <span class="w"> </span><span class="kd">let</span><span class="w"> </span><span class="nx">cellList</span><span class="o">=</span><span class="k">this</span><span class="p">.</span><span class="nx">cells</span><span class="p">();</span> <span class="w"> </span><span class="k">for</span><span class="p">(</span><span class="kd">let</span><span class="w"> </span><span class="nx">i</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">&lt;</span><span class="k">this</span><span class="p">.</span><span class="nx">length</span><span class="p">;</span><span class="w"> </span><span class="nx">i</span><span class="o">++</span><span class="p">){</span> <span class="w"> </span><span class="nx">cellList</span><span class="p">[</span><span class="nx">i</span><span class="p">].</span><span class="nx">contains</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="k">this</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span><span class="w"> </span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> <span class="kd">class</span><span class="w"> </span><span class="nx">Cell</span><span class="w"> </span><span class="p">{</span> <span class="w"> </span><span class="kr">constructor</span><span class="p">(){</span> <span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">contains</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">null</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="w"> </span><span class="nx">appearance</span><span class="p">(){</span> <span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">contains</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="k">this</span><span class="p">.</span><span class="nx">contains</span><span class="p">.</span><span class="nx">appearance</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="s1">&#39;~&#39;</span><span class="p">;</span> <span class="w"> </span><span class="p">}</span> <span class="p">}</span> </code></pre></div> <p>I like the functional and object-oriented qualities of this program. It makes sense to have a class for the game board and the ships, since there is a natural flow of these units going back and forth, asking each other questions. The game board is only generated in one spot, and a new object is made every time a board is generated. That makes it simple to follow the flow of the program and predict its state.</p> <h1>The bad</h1> <p>This is all done as an inline script. That's not too unwieldy for a script of this size, but it is still not the way I would do it now. For one thing, it could lead to problems down the line if the program were to grow. For another, we are filling up the global namespace. This one is not too bad due to its size, only creating 6 global objects. Still, we could get it down to none by making this its own separate module.</p> <p>While I like the object-oriented style, I do not like the use of Javascript classes here. For new projects I have adopted the "class free" style I learned from <a href="https://howjavascriptworks.com/">Douglas Crockford's book</a>. Essentially, we can avoid ever having to deal with <code>this</code> and all its oddities by avoiding the <code>class</code> syntax all together. There is some memory overhead in going class-free, since objects no longer share references to their methods, instead each storing their own methods. In practice this is not really a problem since we seldom have many instances of a class to begin with.</p> <p>This brings me to a more fundamental problem with the code above. What's that class <code>Cell</code> supposed to be doing? All it does is store a reference to an object which is either <code>null</code> or a character. I suspect that in a previous version of the code I had the cell doing more work, keeping track of its neighbors or some such. With the wisdom of more years, I can see that the class should never have been made at all. I should have used the built-in strings instead.</p> <h1>The janky</h1> <p>I don't wish to pick nits over strange formatting or missing semicolons. I won't harp on the liberal use of <code>let</code> where <code>const</code> would be more appropriate. Instead I will conclude by pointing out a few "What was I thinking?" absurdities.</p> <p>Why oh why is there something called a <code>charLenPair</code> floating around? That should plainly just be an object, so that you can refer to the character or length as you see fit.</p> <p>Why is there a function called <code>generateBoard</code> that lives outside the class <code>Board</code>? Surely this is the constructor for the class, and it should live within the class. I think I can tell what I though all those years ago, that I should separate those bits of code which interact with the DOM from those which keep to themselves. That's a good instinct, but it seems to have led to an absurd result in this case.</p> <p>Why does the ship parser ignore parentheses? Why does it process each string three times, with three separate regular expressions? All of these can be explained by the fact that I was not experienced with regular expressions back then. That whole function feels wrong now, as a better-made regular expression would be more explicit and fail in more obvious ways.</p> <p>Why do I left pad the digits in <code>Board.appearance</code> by checking if they are less than 9? That's much harder to read than <code>String.padStart</code>, for example. Of course, <code>String.padStart</code> was only introduced in 2017 after the famous <code>left-pad</code> scandal, so I may well not have heard of it yet at the time.</p> <h1>Conclusion</h1> <p>Even though I can see many ways to improve it, this is ultimately a good piece of code. It does what it set out to do, and works just about anywhere.</p> <p>It would be easy for me to hide my previous blunders, hastily rewrite the code here to be up to my current standards. I believe that would be a mistake. We should wear our early attempts with pride and learn from them where we can. This keeps us humble, and I hope it is instructive to those who are just beginning. It's okay to make mistakes, it's okay to do things in a poorly-thought-out way. It's better to make the mistakes and learn from them than to be paralyzed in unending revisions.</p>Decoding a multiplexed LCD2022-08-12T00:00:00-04:002022-08-12T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-08-12:/decoding-a-multiplexed-lcd.html<p>Figuring out which segments are wired where on the TI-30Xa</p><p>The button-pressing side of Xanthippe is currently working intermittently. I have ordered some more-appropriate parts which will hopefully improve matters. In the mean time, the LCD decoding functionality is working fine. Now is an ideal time to figure out the correspondence between the signals heading to the display and the segments which are activated.</p> <p>Previously, when designing the display for my calculator emulator, I painstakingly traced every segment. I found that there are precisely 112 segments. There are 32 wires going into the LCD, making up 4 ground planes and 28 data lines, for a total of 4x28=112 segments. It's clear there is an alignment between the two, it's just a matter of figuring it out.</p> <h1>Naming conventions</h1> <p>In order to describe the correspondence, it will be necessary to have names for the signals and names for the segments. I already have names I like for the segments, which I made up when creating this SVG file.</p> <p><img alt="a hand-made SVG of the TI-30Xa display" src="https://aldenbradford.com/display.svg" width="600"></p> <p>I have edited the file here to overlay some of the segment labels. Each digit is numbered starting from the right at 0. Each segment within follows a traditional lettering scheme for 7 segment displays. For the orphan "-" sign at the left, it is numbered as though there were an entire digit around it. The exponent digits are numbered similarly, prefixed with E. The indicators above are text, so their labels are simply their text content.</p> <p>When naming the signals I wish to refer back to the physical device as much as possible. I have numbered the wires leading to the display, starting with 1 closest to the battery. The first four wires carry the ground planes. I have also assigned each ground plane a letter, according to the order the segments cycle through when displaying. They repeat a pattern of 4-3-2-1, so I have assigned the backplane on pin 4 the letter A, the plane on pin 3 the letter B, and the plane on pin 2 the letter C.</p> <p>I have put together a simple script which constantly polls the screen and produces the following output.</p> <pre> 33322222222221111111111000000000 21098765432109876543210987654321 A: ......##....................#... B: #.....##.....................#.. C: #.....#.......................#. D: ......##.......................# </pre> <p>A "." indicates that the corresponding segment is not active, and "#" indicates it is active. In this way we end up with a table. All that remains is to fill it in with labels.</p> <h1>The table</h1> <p>To avoid filling the whole screen with tables, I will include the full table here only once. Below I will describe how it was filled in.</p> <table> <thead> <tr> <th></th> <th>32</th> <th>31</th> <th>30</th> <th>29</th> <th>28</th> <th>27</th> <th>26</th> <th>25</th> <th>24</th> <th>23</th> <th>22</th> <th>21</th> <th>20</th> <th>19</th> <th>18</th> <th>17</th> </tr> </thead> <tbody> <tr> <td>A</td> <td>STAT</td> <td>R</td> <td>K</td> <td>E0D</td> <td>()</td> <td>E1D</td> <td>0DP</td> <td>0D</td> <td>1DP</td> <td>1D</td> <td>2DP</td> <td>2D</td> <td>3DP</td> <td>3D</td> <td>4DP</td> <td>4D</td> </tr> <tr> <td>B</td> <td>DE</td> <td>X</td> <td>E0C</td> <td>E0E</td> <td>E1C</td> <td>E1E</td> <td>0C</td> <td>0E</td> <td>1C</td> <td>1E</td> <td>2C</td> <td>2E</td> <td>3C</td> <td>3E</td> <td>4C</td> <td>4E</td> </tr> <tr> <td>C</td> <td>G</td> <td>RAD</td> <td>E0B</td> <td>E0G</td> <td>E1B</td> <td>E1G</td> <td>0B</td> <td>0G</td> <td>1B</td> <td>1G</td> <td>2B</td> <td>2G</td> <td>3B</td> <td>3G</td> <td>4B</td> <td>4G</td> </tr> <tr> <td>D</td> <td>FIX</td> <td>E2G</td> <td>E0A</td> <td>E0F</td> <td>E1A</td> <td>E1F</td> <td>0A</td> <td>0F</td> <td>1A</td> <td>1F</td> <td>2A</td> <td>2F</td> <td>3A</td> <td>3F</td> <td>4A</td> <td>4F</td> </tr> </tbody> </table> <table> <thead> <tr> <th></th> <th>16</th> <th>15</th> <th>14</th> <th>13</th> <th>12</th> <th>11</th> <th>10</th> <th>9</th> <th>8</th> <th>7</th> <th>6</th> <th>5</th> <th>4</th> <th>3</th> <th>2</th> <th>1</th> </tr> </thead> <tbody> <tr> <td>A</td> <td>5DP</td> <td>5D</td> <td>6DP</td> <td>6D</td> <td>7DP</td> <td>7D</td> <td>8DP</td> <td>8D</td> <td>9DP</td> <td>9D</td> <td>M3</td> <td>2nd</td> <td>A</td> <td>.</td> <td>.</td> <td>.</td> </tr> <tr> <td>B</td> <td>5C</td> <td>5E</td> <td>6C</td> <td>6E</td> <td>7C</td> <td>7E</td> <td>8C</td> <td>8E</td> <td>9C</td> <td>9E</td> <td>10G</td> <td>HYP</td> <td>.</td> <td>B</td> <td>.</td> <td>.</td> </tr> <tr> <td>C</td> <td>5B</td> <td>5G</td> <td>6B</td> <td>6G</td> <td>7B</td> <td>7G</td> <td>8B</td> <td>8G</td> <td>9B</td> <td>9G</td> <td>M2</td> <td>ENG</td> <td>.</td> <td>.</td> <td>C</td> <td>.</td> </tr> <tr> <td>D</td> <td>5A</td> <td>5F</td> <td>6A</td> <td>6F</td> <td>7A</td> <td>7F</td> <td>8A</td> <td>8F</td> <td>9A</td> <td>9F</td> <td>M1</td> <td>SCI</td> <td>.</td> <td>.</td> <td>.</td> <td>D</td> </tr> </tbody> </table> <h2>The backplanes</h2> <p>These are free spaces. Bingo!</p> <h2>DEGRAD</h2> <p>The angle indication is actually composed of three segments, "DE", "G", and "RAD". By cycling through the angle modes, we can infer the values from each combination.</p> <h2>Indicators</h2> <p>Most of the indicators can be toggled on or off individually. That saves us some effort, we can just inspect them one at a time.</p> <h2>Digits</h2> <p>What about the digits? The calculator powers on showing "0.", which indicates that most of the first digit lies in columns 25 and 26. If we enter "8." this confirms it, and tells us segment G for free.</p> <p>Pushing the digit to the left by hitting zero, we find columns for the other digits just the same. Hitting "-" at the end tells us 10G.</p> <p>Now we must discern which segment is which. I don't like to assume, so let's test all of them. Starting with "G", since we can just push a negative sign through to see it. We can get DP by sending and 8 along without its decimal point. Next grab E by sending a 9 along. 6 gives us B in a similar way. Take away F from 9 and you get 3, so 3 will tell us F. 1 consists of B and C, and we know B, so 1 gives us C. Finally, 7 is the only digit which distinguishes between A and D, so we need it to get us those. Now we are done with the mantissa</p> <h2>Exponent</h2> <p>A bit trickier since you can't have a blank digit in the exponent. We can have 8, the complement of blank. By comparing everything against 8, we get all our segments in the same way.</p> <h1>Conclusions</h1> <p>We can see a definite pattern, with the digits all using the same configuration of segments on backplanes, just placed on different pins.We can see that the "K" and "()" indicators are filling the spots of the exponent's decimal points.</p> <p>We can also see that a natural way to encode this signal would be to group adjacent pairs of wires into bytes, so that one digit with associated decimal point ends up occupying exactly one byte.</p> <p>Now that I have confidence in the wiring of the LCD reader, all that remains is to get a stable solution to the button pressing problem. I have some ideas for how this will get done, but this will make up a future post. Stay tuned!</p>Lessons from the Xanthippe circuit board2022-08-11T00:00:00-04:002022-08-11T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-08-11:/lessons-from-the-xanthippe-circuit-board.html<p>Regrets and insights brought from the Xanthippe circuit board</p><blockquote> <p>It is too bad! Always the old story! When a man has finished building his house, he finds that he has learnt unawares something which he OUGHT absolutely to have known before he began to build. The eternal, fatal "Too late!" The melancholia of everything COMPLETED—!</p> <p>-- Friedrich Nietsche, <cite>Beyond Good and Evil</cite>, section 277, translated by Helen Zimmern</p> </blockquote> <p>It's here! The board I designed has arrived!</p> <p>This is always an exciting time. Marvel over all the little details which went into a circuit board, which seemed so much larger on the computer screen. Patiently solder all the connections. Curse the clever choices you made only a week prior. Hold your breath to see if it works as you hoped.</p> <p>We are always our own harshest critics. I have been often told not to point out the flaws of my own artistic works -- most people won't notice the flaws unless pointed out, and many wouldn't consider them flaws unless they are told. There is wisdom in that. We should embrace the imperfect, recognizing that we are also imperfect.</p> <p>We should also be frank with ourselves. We can embrace the mistakes we made while simultaneously taking measures to prevent those mistakes in the future. This blog is mostly just for my own reference. With that in mind, this blog post lists some lessons which I hope will stick with me.</p> <h1>Board size</h1> <p>The board manufacturer I used, JLCPCB, provides a significant discount for boards which fit within a 100X100mm square. I think I could have fit my design into this space if I had set out to do so from the beginning. Once I realized this, though, I had already done most of the fiddly layout and didn't want to change it.</p> <p>Also, I had initially neglected to include mounting holes in my design. I put some in by expanding the outline, which led to large mostly-unused margins at the top and bottom. If I had included mounting holes from the beginning, I would have saved some space over all.</p> <h1>Default behavior</h1> <p>Some of the chips in my circuit, notably the 74HC595 chips, have undefined behavior if their inputs are not driven. This is no good if the board receives power before the microcontroller is driving it. This could have been avoided by using some pulldown resistors on all the inputs.</p> <h1>Resistor arrays</h1> <p>I got pretty bored soldering the 32 pullup resistors individually. It was much more pleasant than doing the same on prototype board, but still not great. I think I would happily accept the additional expense of using resistor arrays for pullups, since this almost halves the number of solder points and means dealing with larger (i.e. less fiddly) parts.</p> <h1>TO-92 footprints</h1> <p>These gave me a lot of trouble during soldering. I used a footprint for standard, unbent leads. That made it easy to insert the parts, sure, but it meant the pads were too close for easy soldering. This led to a few solder bridges I had to fix. Next time I will definitely go for a bent-lead footprint, just to make the hand soldering easier.</p> <h1>Breakouts</h1> <p>Now that I am powering up and debugging the circuit, it would be quite useful to have a header for each of the signals for testing. Even something as simple as duplicating the input/output headers, so I can access them while they are plugged. That would have been a cheap thing to add, which would make the current task easier.</p> <h1>Disconnects</h1> <p>Since there is more than one part to the circuit, it would be a help to the debugging if I could isolate parts of the circuit from one another. I'm thinking something like a double row of headers, which I could join with jumpers. That would also help if I decide to keep half the circuit but replace the other half -- I could just leave some of the jumpers off to disconnect the portion I don't need, and even use the headers to move the signal to another board.</p> <h1>Conclusion</h1> <p>I am in the midst of hunting down bugs and errors, finding out why the circuit does not do exactly what I want it to. It is easy then to forget all the things it is doing well. Aside from the trouble with the TO-92 footprints, the board was quite easy to assemble. The layout is fairly sensible, and by not packing parts too tight I was able to get the soldering iron everywhere it needed to go. It is a good design, even if it is not working right just yet. The goal of pointing out the errors that were made is not to diminish the usefulness of the board I have, or the work that went into it. Rather, I hope to make the next board I make even better. </p>Revisiting Apollonian gaskets2022-08-06T00:00:00-04:002022-08-06T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-08-06:/revisiting-apollonian-gaskets.html<p>A better way of generating Apollonian gaskets.</p><p>Last summer I designed <a href="/Apollo">Apollo</a>, a tool for making Apollonian gaskets. Now I am a year older and a year wiser, so I think it's worth revisiting it. There are a few great improvements we can make, particularly when it comes to making integral packings, i.e. packings where the bend of every circle is an integer.</p> <h1>Computing bends to start from</h1> <h2>No primitive bends</h2> <p>There are basically two places where the code can be improved. First, the algorithm I use to find the primitive bends for a packing could be better. In particular, there are cases where it gets into an infinite loop, due to the gasket being in fact unbounded. The algorithm I use starts with three bends and finds a fourth using the quadratic formula. Then, by placing the bends in increasing order and using the mapping </p> <div class="math">$$(w, x, y, z) \mapsto (w, x, y, 2(w+x+y)-z)$$</div> <p> we make the largest bend smaller. If this makes the largest bend bigger instead, we are done, we found the primitive bends. Otherwise we sort the bends to find the new largest bend and repeat.</p> <p>The problem with this strategy is that for some gaskets, the bend can decrease indefinitely toward zero without going negative. In those cases this strategy will not terminate. Here is an example. To make things simple, let's take <span class="math">$$w=0$$</span>, and take the other bends to be in a geometric progression <span class="math">$$(x, y, z) = (x, \alpha x, \alpha^2 x)$$</span>. By Descartes's theorem, <span class="math">$$\alpha$$</span> must satisfy the equation </p> <div class="math">$$2(1+\alpha^2+\alpha^4) = (1+\alpha+\alpha^2)^2.$$</div> <p> At first this may look nasty, but it gets nicer if we do a backwards sort of completing the square, I suppose you could say completing the difference-of-squares. </p> <div class="math">\begin{align*} \alpha^4+\alpha^2+1 &amp;= (\alpha^4+2\alpha^2+1)-\alpha^2\\ &amp;= (\alpha^2+1)^2-\alpha^2\\ &amp;= (\alpha^2+\alpha+1)(\alpha^2-\alpha+1) \end{align*}</div> <p> So, we see a common factor in both sides of the equation. We can factor it out as </p> <div class="math">$$(\alpha^2+\alpha+1)(\alpha^2-3\alpha+1)=0.$$</div> <p> This has two real roots, the solutions to <span class="math">$$\alpha^2-3\alpha+1=0$$</span>. For our purposes I prefer to write this as <span class="math">$$\alpha+\frac{1}{\alpha} = 3$$</span>, which emphasizes that the two solutions are reciprocal to one another. Taking <span class="math">$$\alpha$$</span> to be the larger of these two roots (<span class="math">$$\alpha=(3+\sqrt{5})/2$$</span> if you are curious) puts our original bends in increasing order.</p> <p>If we apply our mirroring strategy to <span class="math">$$(0, x, \alpha x, \alpha^2 x)$$</span> we get rid of <span class="math">$$\alpha^2 x$$</span> and replace it with </p> <div class="math">\begin{align*} 2(x+\alpha x)-\alpha^2 x &amp;= 2x+2\alpha x -(3\alpha-1) x\\ &amp;= (3-\alpha) x\\ &amp;= \frac{1}{\alpha} x. \end{align*}</div> <p> Sorting the bends gives us <span class="math">$$(0, \frac{1}{\alpha} x, x, \alpha x)$$</span>. We see that for this configuration of bends the mirroring lets the bends decrease indefinitely toward zero, dividing their magnitudes by <span class="math">$$\alpha$$</span> at each stage.</p> <p>This can never happen if the first four bends are integers since no sequence of positive integers can decrease forever. The method specified above fails precisely because some of the time we don't get integer bends. A better solution would be to treat the cases separately, i.e. figure out a plan to deal with large regions and just plot any three bends specified, without first reducing the bends.</p> <h2>A list of good bends</h2> <p>The reason I previously included the bend-reducing algorithm was as a way to generate integral gaskets. Usually, if I care about the exact bend of a gasket, it's because it's an integral packing. Why not simply provide a list of all the integral packings?</p> <p>In <a href="https://arxiv.org/abs/math/0009113">this paper from 2003</a> the authors provide a way of describing all the primitive integral packings in their theorem 4.1. They give a one-to-one correspondence between primitive packings of the form <span class="math">$$(a, b, c, d)$$</span> and integer solutions to the equation <span class="math">$$x^2+m^2=d_1d_2$$</span> satisfying <span class="math">$$x&lt;0\leq 2m\leq d_1 \leq d_2$$</span> and <span class="math">$$\gcd(x, d_1, d_2)=1$$</span>.</p> <div class="math">\begin{align*} a&amp;=x\\ b&amp;=d_1-x\\ c&amp;=d_2-x\\ d&amp;=-2m+d_1+d_2-x \end{align*}</div> <p>I want these grouped by their outer bend, so I will fix <span class="math">$$a=x=-n$$</span>. As it turns out, for each fixed <span class="math">$$n$$</span> there are not too many candidates for <span class="math">$$m$$</span>, and we can check them all provided <span class="math">$$n$$</span> isn't too big.</p> <p>Since we want <span class="math">$$2m\leq d_1$$</span> and <span class="math">$$2m\leq d_2$$</span> it follows that <span class="math">$$4m^2\leq d_1d_2$$</span> and so <span class="math">$$3m^2\leq d_1d_2-m^2=n^2$$</span>. Thus, we only need to check <span class="math">$$m&lt;n/\sqrt{3}$$</span>. For each <span class="math">$$m$$</span>, we can just try all the numbers <span class="math">$$d_1$$</span> between <span class="math">$$2m$$</span> and <span class="math">$$\sqrt{n^2+m^2}$$</span> to see if they divide <span class="math">$$n^2+m^2$$</span>. If so that gives us <span class="math">$$d_2$$</span>, so we can just check if <span class="math">$$\gcd(n, d1, d2)=1$$</span>. Here's some Python code to carry out this procedure.</p> <div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">math</span> <span class="k">def</span> <span class="nf">get_primitive_bends</span><span class="p">(</span><span class="n">n</span><span class="p">):</span> <span class="k">if</span> <span class="n">n</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span> <span class="k">yield</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span> <span class="k">return</span> <span class="k">for</span> <span class="n">m</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">ceil</span><span class="p">(</span><span class="n">n</span> <span class="o">/</span> <span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="mi">3</span><span class="p">))):</span> <span class="n">s</span> <span class="o">=</span> <span class="n">m</span><span class="o">**</span><span class="mi">2</span> <span class="o">+</span> <span class="n">n</span><span class="o">**</span><span class="mi">2</span> <span class="k">for</span> <span class="n">d1</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="nb">max</span><span class="p">(</span><span class="mi">2</span> <span class="o">*</span> <span class="n">m</span><span class="p">,</span> <span class="mi">1</span><span class="p">),</span> <span class="n">math</span><span class="o">.</span><span class="n">floor</span><span class="p">(</span><span class="n">math</span><span class="o">.</span><span class="n">sqrt</span><span class="p">(</span><span class="n">s</span><span class="p">))</span> <span class="o">+</span> <span class="mi">1</span><span class="p">):</span> <span class="n">d2</span><span class="p">,</span> <span class="n">remainder</span> <span class="o">=</span> <span class="nb">divmod</span><span class="p">(</span><span class="n">s</span><span class="p">,</span> <span class="n">d1</span><span class="p">)</span> <span class="k">if</span> <span class="n">remainder</span> <span class="o">==</span> <span class="mi">0</span> <span class="ow">and</span> <span class="n">math</span><span class="o">.</span><span class="n">gcd</span><span class="p">(</span><span class="n">n</span><span class="p">,</span> <span class="n">d1</span><span class="p">,</span> <span class="n">d2</span><span class="p">)</span> <span class="o">==</span> <span class="mi">1</span><span class="p">:</span> <span class="k">yield</span> <span class="o">-</span><span class="n">n</span><span class="p">,</span> <span class="n">d1</span> <span class="o">+</span> <span class="n">n</span><span class="p">,</span> <span class="n">d2</span> <span class="o">+</span> <span class="n">n</span><span class="p">,</span> <span class="n">d1</span> <span class="o">+</span> <span class="n">d2</span> <span class="o">+</span> <span class="n">n</span> <span class="o">-</span> <span class="mi">2</span> <span class="o">*</span> <span class="n">m</span> <span class="k">for</span> <span class="n">n</span> <span class="ow">in</span> <span class="nb">range</span><span class="p">(</span><span class="mi">10</span><span class="p">):</span> <span class="k">for</span> <span class="n">bend</span> <span class="ow">in</span> <span class="n">get_primitive_bends</span><span class="p">(</span><span class="n">n</span><span class="p">):</span> <span class="nb">print</span><span class="p">(</span><span class="n">bend</span><span class="p">)</span> </code></pre></div> <p>This gives us exactly <a href="https://en.wikipedia.org/wiki/Apollonian_gasket#Integral_Apollonian_circle_packings">the list on Wikipedia</a>.</p> <div class="highlight"><pre><span></span><code><span class="p">(</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">1</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">1</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">2</span><span class="p">,</span> <span class="mi">3</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="mi">4</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">13</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">3</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">8</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">4</span><span class="p">,</span> <span class="mi">5</span><span class="p">,</span> <span class="mi">20</span><span class="p">,</span> <span class="mi">21</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">4</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">9</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">6</span><span class="p">,</span> <span class="mi">30</span><span class="p">,</span> <span class="mi">31</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">5</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="mi">18</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">6</span><span class="p">,</span> <span class="mi">7</span><span class="p">,</span> <span class="mi">42</span><span class="p">,</span> <span class="mi">43</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">6</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">15</span><span class="p">,</span> <span class="mi">19</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">6</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">15</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">7</span><span class="p">,</span> <span class="mi">8</span><span class="p">,</span> <span class="mi">56</span><span class="p">,</span> <span class="mi">57</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">7</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">32</span><span class="p">,</span> <span class="mi">32</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">7</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">17</span><span class="p">,</span> <span class="mi">20</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">8</span><span class="p">,</span> <span class="mi">9</span><span class="p">,</span> <span class="mi">72</span><span class="p">,</span> <span class="mi">73</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">8</span><span class="p">,</span> <span class="mi">13</span><span class="p">,</span> <span class="mi">21</span><span class="p">,</span> <span class="mi">24</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">8</span><span class="p">,</span> <span class="mi">12</span><span class="p">,</span> <span class="mi">25</span><span class="p">,</span> <span class="mi">25</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">9</span><span class="p">,</span> <span class="mi">10</span><span class="p">,</span> <span class="mi">90</span><span class="p">,</span> <span class="mi">91</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">9</span><span class="p">,</span> <span class="mi">11</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="mi">50</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">9</span><span class="p">,</span> <span class="mi">14</span><span class="p">,</span> <span class="mi">26</span><span class="p">,</span> <span class="mi">27</span><span class="p">)</span> <span class="p">(</span><span class="o">-</span><span class="mi">9</span><span class="p">,</span> <span class="mi">18</span><span class="p">,</span> <span class="mi">19</span><span class="p">,</span> <span class="mi">22</span><span class="p">)</span> </code></pre></div> <p>We could certainly get more clever about how we find <span class="math">$$d_1$$</span> and <span class="math">$$d_2$$</span>, though trial division works well for small <span class="math">$$n$$</span>. An example speedup is to notice <span class="math">$$d_1d_2=m^2+n^2$$</span> is the sum of two squares. By the aptly-named <a href="https://en.wikipedia.org/wiki/Sum_of_two_squares_theorem">sum of two squares theorem</a>, we know any prime factors of <span class="math">$$m^2+n^2$$</span> which are congruent to <span class="math">$$3$$</span> mod <span class="math">$$4$$</span> must have an even exponent -- for example, if <span class="math">$$m^2+n^2$$</span> is divisible by <span class="math">$$7$$</span> then it must also be divisible by <span class="math">$$49$$</span>. We can use that to find the factorization quicker, possibly requiring many fewer trial divisions.</p> <h1>Using integral bend-centers</h1> <p>In <a href="https://arxiv.org/abs/math/0010302v5">this other article</a>, the authors show that every integral packing can be translated and rotated to make it strongly integral, i.e. with integer bend-centers. They do this by embedding every integral packing into a single integral superpacking, and rotating/translating that integral superpacking to get a strongly integral superpacking.</p> <p>The benefits of having integer bend-centers should be obvious. Not only is it generally faster to do arithmetic on integers, we also no longer have to worry about floating point errors creeping in as we delve deeper. We could use this to make an interactive tool which lets you zoom in as far as you like, without fear of drifting off course.</p> <p>In that paper they do not give an algorithm explicitly for constructing a strongly integral packing for a given integral packing. However, we can adapt a couple of their theorems to create a strategy. That's what I'll describe here.</p> <p>Their theorem 4.2 is proved by creating a superapollonian group action which takes a given Descartes configuration to <span class="math">$$(0, 0, g, g)$$</span>. If we start with a primitive configuration, then we can take <span class="math">$$g=1$$</span>. What we will do is apply their proposed strategy, saving the group action -- specifically, its representation as a 4x4 orthogonal matrix. Taking the transpose gives us the inverse action.</p> <p>We can then apply that action to a strongly integral packing of <span class="math">$$(0, 0, 1, 1)$$</span>. For example, if we apply it to </p> <div class="math">$$\begin{bmatrix} 0 &amp; 0 &amp; 1\\ 0 &amp; 0 &amp;-1\\ 1 &amp; 1 &amp; 0\\ 1 &amp;-1 &amp; 0 \end{bmatrix}$$</div> <p> then we will get the embedding of our desired configuration of bends in the standard strongly integral superpacking. What's more, the location in the plane will be aligned with the superpacking -- we can trivially shift it back to the origin for plotting if we wish, or we can make a neat drawing of the whole superpacking with the desired circle indicated.</p> <h1>Where this will go</h1> <p>I don't personally have too much interest in making more drawings of gaskets. The ones I made are pretty enough, and Apollo does well enough at creating them. I might patch it to use the list of gaskets above. If I do make a new version, I think I would want it to be a tool for exploring the Superapollonian packing. Something where you can scroll through in real time, or pinch to zoom on a touchscreen. Have a side panel to highlight specific integral packings.</p> <script type="text/javascript">if (!document.getElementById('mathjaxscript_pelican_#%@#@#')) { var align = "center", indent = "0em", linebreak = "false"; if (false) { align = (screen.width < 768) ? "left" : align; indent = (screen.width < 768) ? "0em" : indent; linebreak = (screen.width < 768) ? 'true' : linebreak; } var mathjaxscript = document.createElement('script'); mathjaxscript.id = 'mathjaxscript_pelican_#%@#@#'; mathjaxscript.type = 'text/javascript'; mathjaxscript.src = 'https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.3/latest.js?config=TeX-AMS-MML_HTMLorMML'; var configscript = document.createElement('script'); configscript.type = 'text/x-mathjax-config'; configscript[(window.opera ? "innerHTML" : "text")] = "MathJax.Hub.Config({" + " config: ['MMLorHTML.js']," + " TeX: { extensions: ['AMSmath.js','AMSsymbols.js','noErrors.js','noUndefined.js'], equationNumbers: { autoNumber: 'none' } }," + " jax: ['input/TeX','input/MathML','output/HTML-CSS']," + " extensions: ['tex2jax.js','mml2jax.js','MathMenu.js','MathZoom.js']," + " displayAlign: '"+ align +"'," + " displayIndent: '"+ indent +"'," + " showMathMenu: true," + " messageStyle: 'normal'," + " tex2jax: { " + " inlineMath: [ ['\\\$$','\\\$$'] ], " + " displayMath: [ ['$$','$$'] ]," + " processEscapes: true," + " preview: 'TeX'," + " }, " + " 'HTML-CSS': { " + " availableFonts: ['STIX', 'TeX']," + " preferredFont: 'STIX'," + " styles: { '.MathJax_Display, .MathJax .mo, .MathJax .mi, .MathJax .mn': {color: 'inherit ! important'} }," + " linebreaks: { automatic: "+ linebreak +", width: '90% container' }," + " }, " + "}); " + "if ('default' !== 'default') {" + "MathJax.Hub.Register.StartupHook('HTML-CSS Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax['HTML-CSS'].FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "MathJax.Hub.Register.StartupHook('SVG Jax Ready',function () {" + "var VARIANT = MathJax.OutputJax.SVG.FONTDATA.VARIANT;" + "VARIANT['normal'].fonts.unshift('MathJax_default');" + "VARIANT['bold'].fonts.unshift('MathJax_default-bold');" + "VARIANT['italic'].fonts.unshift('MathJax_default-italic');" + "VARIANT['-tex-mathit'].fonts.unshift('MathJax_default-italic');" + "});" + "}"; (document.body || document.getElementsByTagName('head')).appendChild(configscript); (document.body || document.getElementsByTagName('head')).appendChild(mathjaxscript); } </script>Introducing Xanthippe2022-08-04T00:00:00-04:002022-08-04T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-08-04:/introducing-xanthippe.html<p>A formal introduction to my calculator validator</p><p>I have been going on about my project to <a href="/calculator_emulator">emulate a calculator in the browser</a> for some time. A month or so ago I decided that, if I am going to make the more fiddly bits of the calculator work correctly, I should really find a way to generate tests automatically.</p> <p>I have spoken previously about how an emulator is really the ideal venue for test-driven development. All the visible behaviors of the finished product are completely specified, in the sense that any feature not-yet-implemented corresponds to a class of inputs which produce an incorrect output. If you want to know what the emulator should do in a given situation, all you have to do is check against the real deal.</p> <p>Up to now, I have been using a suite of tests I carefully chose based on pressing buttons by hand. That made sense when there were only a dozen or two tests. Because the examples are carefully chosen, I don't necessarily need many tests to cover a wide range of features. Now there are close to 50 tests, and the remaining functionality -- fractions, statistics registers, base 10 encoding, alternative display styles -- rely on hidden information. With more state involved, I expect the number of necessary test to explode.</p> <h1>What is Xanthippe?</h1> <p>This is where Xanthippe comes in. Instead of pressing the buttons myself and documenting what the display shows, I will program a computer to press the buttons for me. This way all I have to do is specify a string of presses, and I can automatically get what the calculator shows at each stage. This way I can really "go ham" designing the emulator, and have real confidence that the emulator behaves like the genuine article.</p> <p>I have already <a href="reflections-on-wiring-a-calculator">soldered test points to a calculator</a> and figured out the electrical characteristics of <a href="reverse-engineering-a-matrix-keypad">the buttons</a> and <a href="reading-a-multiplexed-lcd-signal">the display</a>. I have designed <a href="https://github.com/AldenMB/Xanthippe/tree/main/calculator_interface">a circuit board</a> which reads the signals from the LCD to a shift register and writes button presses from a shift register. Assuming the circuit works as intended (the circuit board is still in the mail right now) I should be able to write code for any microcontroller which controls the calculator and reads its outputs.</p> <p>Currently, my dream for Xanthippe is that it will be an art piece hanging on a wall in a glass box, constantly pressing buttons on the calculator and recording the results. It will send all the results onto Github, so I can test my emulator against them whenever I make a change. I also want to be able to make requests for specific sequences to check, so I can still design test cases explicitly.</p> <h1>Why the name?</h1> <p>I am emulating the TI-30Xa, and there are not too many names which start with the letters "Xa". I think Xavier would be a better name for the emulator itself (easier to type). Xanthippe is the name of the wife of Socrates. I think that's somewhat fitting, for a character who sits behind the scenes and makes sure the famous one is keeping in line. Xanthippe is often portrayed as stubborn and unyielding, which fits the role of a testing suite. Though the emulator may disagree, we will know Xanthippe is right.</p> <h1>What cases to test?</h1> <p>Once I have Xanthippe pushing buttons, the obvious question to ask is "which ones?". There are 40 buttons, and button press sequences often reach over 20 presses in length, so there is no hope of just trying every combination. Many of those combinations will reach an error state long before they reach the end, and calculator does not do much upon reaching an error state, so in reality we can cut lots of sequences short. There are still well too many for an exhaustive search.</p> <p>A few combinations spring to mind as obvious additions. First, I have already specified many button sequences as good tests, may as well do those. Second, since my emulator runs in the browser, it is not too tricky to ask it to phone home. Whenever someone completes a session with the emulator I can have it send off the button presses, and Xanthippe can check them. That way I am sure to have coverage of the cases people actually use, at least the second time they try it. I could even do something silly like loading answers from Xanthippe when I have them instead of using the emulator, which could make people think I am very fast at fixing bugs. Probably unwise since that would lead to inconsistent behavior, which is the only thing harder to troubleshoot than incorrect behavior.</p> <p>Another way I can choose button presses is to explore the area around existing sequences. For example, if there is something in memory, I could press buttons to read what it is. I could subtract the visible part of a mantissa to reveal the hidden part. This surely deserves its own blog post.</p> <h1>How to save the answers?</h1> <p>The calculator does not give us a whole lot of information. Most of the computation happens within the epoxy blob, a "black box" as it were. Really, I just need a way of mapping each sequence of button presses to a display on the screen. Since there are 40 buttons a button press can fit in a single byte. There are 118=8x14 segments on the screen. Not all the combinations are possible of course, so I could fit the LCD into less than 14 bytes, but why bother? The premise of Xanthippe is that I don't know everything the calculator does at all times, so I may as well have a format which can store every result conceivable. This also will take less processing, since what I get from the screen are raw bits.</p> <p>There is actually one more thing I can measure, which I may as well do. The calculator takes longer to process some presses than others. I have to monitor how long it takes to respond anyhow, since I can't give a new button press while I am waiting for the previous one to resolve. Why not record the time each button press takes? In some future version of the emulator I may wish to simulate the actual delay the calculator gives, so I may as well record that as well. That way I actually have every piece of information the calculator will give me.</p> <p>I can be somewhat clever about the data structure I use. After each button press, the calculator gives me a result to store. However, multiple sequences of button presses can start with the same few buttons. Why repeat them in the storage? I can instead do a tree structure. In JSON, one node of the tree might look something like this.</p> <div class="highlight"><pre><span></span><code><span class="p">{</span> <span class="w"> </span><span class="nt">&quot;display&quot;</span><span class="p">:</span><span class="w"> </span><span class="s2">&quot;dQw4w9WgXcQ&quot;</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;time&quot;</span><span class="p">:</span><span class="w"> </span><span class="mi">148</span><span class="p">,</span> <span class="w"> </span><span class="nt">&quot;a&quot;</span><span class="p">:{}</span> <span class="w"> </span><span class="nt">&quot;X&quot;</span><span class="p">:{}</span> <span class="w"> </span><span class="nt">&quot;7&quot;</span><span class="p">:{}</span> <span class="p">}</span> </code></pre></div> <p>Here <code>dQw4w9WgXcQ</code> is an example base-64-encoded LCD reading. Time is how long the button press took, in some appropriate units. Each of those single-character keys would be a button press, and it would lead to a child node of the same type. We would only include those children which we have evaluated. A tree is a simple, sensible way to store the type of data we are creating.</p> <h1>How to encode the keys?</h1> <p>The shift register in hardware which receives the key presses takes two bytes, switching 16 transistors. The most naive way of storing the presses would be to just record the two bytes which are sent.</p> <p>That is quite wasteful, since far from every combination of transistors would ever be turned on. We know there are 40 buttons, and the next power of two up from there is 64=2^6. We should be able to store one button press in less than one byte, rather in one base-64 character.</p> <p>You may recall this table from <a href="reverse-engineering-a-matrix-keypad">a previous post</a>.</p> <table> <thead> <tr> <th></th> <th>A0</th> <th>A1</th> <th>A2</th> <th>A3</th> <th>A4</th> <th>A5</th> <th>A6</th> <th>A7</th> </tr> </thead> <tbody> <tr> <td>B0</td> <td>+/-</td> <td>2nd</td> <td>+</td> <td>0</td> <td>.</td> <td>SIN</td> <td>1/x</td> <td></td> </tr> <tr> <td>B1</td> <td>y^x</td> <td>HYP</td> <td>-</td> <td>1</td> <td>5</td> <td>COS</td> <td>x^2</td> <td></td> </tr> <tr> <td>B2</td> <td>OFF</td> <td>pi</td> <td>X</td> <td>2</td> <td>6</td> <td>TAN</td> <td>sqrt</td> <td></td> </tr> <tr> <td>B3</td> <td></td> <td>Sigma+</td> <td>/</td> <td>3</td> <td>7</td> <td>DRG</td> <td>EE</td> <td></td> </tr> <tr> <td>B4</td> <td></td> <td>STO</td> <td>=</td> <td>4</td> <td>8</td> <td>LOG</td> <td>(</td> <td></td> </tr> <tr> <td>B5</td> <td></td> <td>RCL</td> <td>ab/c</td> <td>&lt;-</td> <td>9</td> <td>LN</td> <td>)</td> <td></td> </tr> <tr> <td>B6</td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td></td> <td>(reset all)</td> </tr> <tr> <td>RESET</td> <td>ON/C</td> <td>ON/C</td> <td>ON/C</td> <td>ON/C</td> <td>ON/C</td> <td>ON/C</td> <td>ON/C</td> <td>ON/C</td> </tr> </tbody> </table> <p>Here I have added two rows and a column to reflect how the shift registers on Xanthippe are configured. When the A register puts A7 high and B6 high, for example, the pad on the reverse of the calculator is shorted, clearing all memory and putting the calculator in a fresh state. This will prove indispensable when I want to start a new session, as a reliable way to clear all memory of the previous run. There are only 7 pins on the calculator labeled B, so I have used the remaining bit there to trigger a transistor which sends an ON/C keypress.</p> <p>In each byte, either none of the bits are on or one of the bits is on. None is the default state, when no button is being pushed. I never need to send an "empty press", that's just the default state in between presses. When the RESET bit is on, it doesn't really matter whether the A bits are on or off, since they can't make a full circuit without one of the B bits on as well. Therefore we can encode the keypress with just 6 bits, 3 for the A byte and 3 for the B+RESET byte. 6 bits is great, because it lets me use base 64 encoding. I like 64 bit encoding because it's standardized and human-readable.</p> <p>The two numbers <code>0b111110</code> and <code>0b111111</code> have ambiguous encodings in base 64, since their original versions use the characters <code>+</code> and <code>/</code> which don't play nice in URLs. I would like to avoid them. Depending on whether I use the high bits for A or B, I could run into them or avoid them. By choosing to put B into the three high bits, I can put those in the last row of the table above. Any number of the form <code>0b111xxx</code> will give a RESET, so I never need to use the two offending characters at all.</p> <p>The resulting encoding is as follows.</p> <table> <thead> <tr> <th>A</th> <th>B</th> <th>Binary</th> <th>Base64</th> <th>label</th> </tr> </thead> <tbody> <tr> <td>0</td> <td>0</td> <td>000000</td> <td>A</td> <td>+/-</td> </tr> <tr> <td>1</td> <td>0</td> <td>000001</td> <td>B</td> <td>2nd</td> </tr> <tr> <td>2</td> <td>0</td> <td>000010</td> <td>C</td> <td>+</td> </tr> <tr> <td>3</td> <td>0</td> <td>000011</td> <td>D</td> <td>0</td> </tr> <tr> <td>4</td> <td>0</td> <td>000100</td> <td>E</td> <td>.</td> </tr> <tr> <td>5</td> <td>0</td> <td>000101</td> <td>F</td> <td>SIN</td> </tr> <tr> <td>6</td> <td>0</td> <td>000110</td> <td>G</td> <td>1/x</td> </tr> <tr> <td>0</td> <td>1</td> <td>001000</td> <td>I</td> <td>y^x</td> </tr> <tr> <td>1</td> <td>1</td> <td>001001</td> <td>J</td> <td>HYP</td> </tr> <tr> <td>2</td> <td>1</td> <td>001010</td> <td>K</td> <td>-</td> </tr> <tr> <td>3</td> <td>1</td> <td>001011</td> <td>L</td> <td>1</td> </tr> <tr> <td>4</td> <td>1</td> <td>001100</td> <td>M</td> <td>5</td> </tr> <tr> <td>5</td> <td>1</td> <td>001101</td> <td>N</td> <td>COS</td> </tr> <tr> <td>6</td> <td>1</td> <td>001110</td> <td>O</td> <td>x^2</td> </tr> <tr> <td>0</td> <td>2</td> <td>010000</td> <td>Q</td> <td>OFF</td> </tr> <tr> <td>1</td> <td>2</td> <td>010001</td> <td>R</td> <td>pi</td> </tr> <tr> <td>2</td> <td>2</td> <td>010010</td> <td>S</td> <td>X</td> </tr> <tr> <td>3</td> <td>2</td> <td>010011</td> <td>T</td> <td>2</td> </tr> <tr> <td>4</td> <td>2</td> <td>010100</td> <td>U</td> <td>6</td> </tr> <tr> <td>5</td> <td>2</td> <td>010101</td> <td>V</td> <td>TAN</td> </tr> <tr> <td>6</td> <td>2</td> <td>010110</td> <td>W</td> <td>sqrt</td> </tr> <tr> <td>1</td> <td>3</td> <td>011001</td> <td>Z</td> <td>Sigma+</td> </tr> <tr> <td>2</td> <td>3</td> <td>011010</td> <td>a</td> <td>/</td> </tr> <tr> <td>3</td> <td>3</td> <td>011011</td> <td>b</td> <td>3</td> </tr> <tr> <td>4</td> <td>3</td> <td>011100</td> <td>c</td> <td>7</td> </tr> <tr> <td>5</td> <td>3</td> <td>011101</td> <td>d</td> <td>DRG</td> </tr> <tr> <td>6</td> <td>3</td> <td>011110</td> <td>e</td> <td>EE</td> </tr> <tr> <td>1</td> <td>4</td> <td>100001</td> <td>h</td> <td>STO</td> </tr> <tr> <td>2</td> <td>4</td> <td>100010</td> <td>i</td> <td>=</td> </tr> <tr> <td>3</td> <td>4</td> <td>100011</td> <td>j</td> <td>4</td> </tr> <tr> <td>4</td> <td>4</td> <td>100100</td> <td>k</td> <td>8</td> </tr> <tr> <td>5</td> <td>4</td> <td>100101</td> <td>l</td> <td>LOG</td> </tr> <tr> <td>6</td> <td>4</td> <td>100110</td> <td>m</td> <td>(</td> </tr> <tr> <td>1</td> <td>5</td> <td>101001</td> <td>p</td> <td>RCL</td> </tr> <tr> <td>2</td> <td>5</td> <td>101010</td> <td>q</td> <td>ab/c</td> </tr> <tr> <td>3</td> <td>5</td> <td>101011</td> <td>r</td> <td>&lt;-</td> </tr> <tr> <td>4</td> <td>5</td> <td>101100</td> <td>s</td> <td>9</td> </tr> <tr> <td>5</td> <td>5</td> <td>101101</td> <td>t</td> <td>LN</td> </tr> <tr> <td>6</td> <td>5</td> <td>101110</td> <td>u</td> <td>)</td> </tr> <tr> <td>7</td> <td>6</td> <td>110111</td> <td>3</td> <td>(reset all)</td> </tr> <tr> <td>x</td> <td>7</td> <td>111xxx</td> <td>4,5,...</td> <td>ON/C</td> </tr> </tbody> </table> <!--- make the above table using the following Python code. for i in range(64): letter = str(base64.b64encode(bytearray([0,0,i])))[-2] A = i % 8 B = i //8 symbol = table[B][A] if symbol: print(f'{A} | {B} | {i:06b} | {letter:<6} | {symbol}') --> <p>Alas, there is no obvious rhyme or reason to how these are laid out on the calculator. Some times many buttons on the same row of the keypad will share an A pin, but this is a pattern and not a rule. I suspect it mostly has to do with whatever was convenient when the engineers at TI were laying out the traces on the keypad. I could have chosen an encoding which corresponds more to the physical layout than the electrical layout, but any such encoding would be completely ad-hoc. This encoding appeals to me because it is quite natural, and it will be easy for Xanthippe to interpret when connecting to the hardware.</p> <p>The issue of how to encode the LCD still remains. There are four batches of 28 bits to store, coming from the four backplanes. For now it makes sense to defer this task, since I currently don't know what cells the majority of the bits correspond to. If, for example, they line up neatly with the 7-segment portion then it would make sense to take advantage of this. Regardless, we will certainly be able to fit the result into 14 bytes somehow, possibly padding it out to 15 bytes to make a 20-character base64 string.</p> <h1>How to store the tree?</h1> <p>We need a way for Xanthippe to keep its records, so that it knows what it has done and other software can read it. I have two bad ideas and a good idea.</p> <p>First, a bad idea: I could define my own binary file format. This would let me load it fast from memory, and store it efficiently. It would also be a lot of work, and it would make it hard for other people to understand the code. If something breaks, I would not have an easy way to recover.</p> <p>A less bad, but still not good, idea is to use JSON. This has the advantage of being easy for humans to read, and easy to pass between Python and Javascript. It is also a natural way to express a tree structure. There are some major disadvantages, though. I want to have Xanthippe running constantly, so it will pile up lots of data. A JSON object really would rather stay in memory while it is being manipulated, and only be written to a string when manipulations are done. If I do it this way, Xanthippe will eventually run out of memory, which will be hard to fix. JSON is also not an economical way to store binary data, in terms of disk space.</p> <p>Instead I will use an actual database. This is a bit tricky since most databases are built for tables not for trees, but others have solved this problem before me. For now this will take the form of an SQLite database, since that will be the easiest to work with in Python. For the Javascript side of things, I will either figure out how to read the SQLite file directly (likely with <a href="https://github.com/sql-js/sql.js">sql.js</a>) or have Python export it to a file for Javascript consumption. It does feel a bit silly to use SQLite to represent a tree, because to do so I must represent my data as a table which SQLite will internally represent as a B-Tree. There may be a way to give SQLite some hints as to the data structure to regain some performance, but this may also just be the price we pay for compatibility.</p> <h1>Next steps</h1> <p>Once the circuit board for Xanthippe arrives it will be time to test it out, make sure I can do the reads and writes I have designed. Exploration will probably happen with a microcontroller, but for the final result I'll probably use an old Raspberry Pi I have lying around, since I need the networking. Once I am sending keypresses and receiving raw LCD segments I will need to figure out exactly which LCD segment is controlled by which bit of the signal. After that I can get to writing the code which will run Xanthippe forever.</p>A 3D-printed Planimeter For Use In Classrooms2022-07-24T00:00:00-04:002022-07-24T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-24:/a-3d-printed-planimeter-for-use-in-classrooms.html<p>A simple 3D-printable design for a planimeter, suitable for making a class set.</p><p>Over the past week I have been developing <a href="https://github.com/AldenMB/Planimeter">a design for a 3D printed planimeter.</a> The idea was to make something so cheap and simple that a whole class of students could each have their own, either individually in groups. I also wanted the parameters of the planimeter (the lengths of the arms) to be configurable by the students, so they are adjustable with thumb screws.</p> <p>Usually, the most intricate part of a planimeter is the system of gears which allows you to count the rotations of the wheel. Here, we side-step that issue by using a 10-turn potentiometer. I intend to have students read the values using a microcontroller, and write a program to compute the area. You could also power it from a battery and use a digital volt meter. For students who are interested in electronics, it could be a nice exercise to design a circuit which runs off a 9 volt battery and displays the swept-out area in appropriate units on a cheap <a href="https://www.amazon.com/bayite-Voltmeter-Motorcycle-Polarity-Protection/dp/B00YALV0NG/">panel mount volt meter</a>.</p> <p>If you don't want to include electronics at all, the design could be modified to turn the wheel on a bearing instead of on a potentiometer, and markings could be added to the wheel to count rotations by eye.</p> <p>I feel this is a good example of how 3D printing can change the way we teach. It would not be feasible to manufacture this type of thing for a reasonable cost using traditional methods, just because the setup time would be so great and the demand so small. 3D printing allows us to incorporate more physical activities in the classroom without breaking the bank.</p>Reading a Multiplexed LCD Signal2022-07-21T00:00:00-04:002022-07-21T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-21:/reading-a-multiplexed-lcd-signal.html<p>I want to read the signal going to the display of a TI-30Xa calculator. The display has 112 segments, multiplexed in a 4x28 configuration. How can we recover the displayed values from just those 32 pins?</p><p>I want to read the signal going to the display of a TI-30Xa calculator. The display has 112 segments, multiplexed in a 4x28 configuration. How can we recover the displayed values from just those 32 pins?</p> <h1>backplanes</h1> <p>First, we have to identify the signals going to the backplanes. Those don't change no matter what is displayed, always giving the same repeating signal. Using the pin assignments <a href="/reflections-on-wiring-a-calculator">I chose previously</a>, those are pins 1-4. Here is what the voltage at each of them is throughout one 24 millisecond cycle. To keep things tidy, the header row gives the time at the start of the period. The following table is in units of 1.5 volts, so that everything ends up an integer.</p> <table> <thead> <tr> <th>pin</th> <th>0ms</th> <th>3ms</th> <th>6ms</th> <th>9ms</th> <th>12ms</th> <th>15ms</th> <th>18ms</th> <th>21ms</th> </tr> </thead> <tbody> <tr> <td>3</td> <td>0</td> <td>2</td> <td>2</td> <td>2</td> <td>3</td> <td>1</td> <td>1</td> <td>1</td> </tr> <tr> <td>4</td> <td>2</td> <td>0</td> <td>2</td> <td>2</td> <td>1</td> <td>3</td> <td>1</td> <td>1</td> </tr> <tr> <td>2</td> <td>2</td> <td>2</td> <td>0</td> <td>2</td> <td>1</td> <td>1</td> <td>3</td> <td>1</td> </tr> <tr> <td>1</td> <td>2</td> <td>2</td> <td>2</td> <td>0</td> <td>1</td> <td>1</td> <td>1</td> <td>3</td> </tr> </tbody> </table> <p>We can see that in the second half of the cycle the same pattern of voltages is repeated. Treating each voltage as a function of time V(t), the signal obeys the rule V(t) = 3-V(t-12). In fact, every pin going to the display obeys this rule. This is done so that every LCD segment is guaranteed to see a balanced load. This leads to our first helpful observation for reading the signal.</p> <p><strong>We only need to measure the pins four times per cycle, during the first 12 milliseconds.</strong></p> <h1>data</h1> <p>That's the four backplanes identified, what about the data pins? To give an example, let's look at pin 32. Its signal changes depending on whether the "g" in the "de/g/rad" display is on. We can test this easily, because the display will cycle between saying "deg", "rad", and "grad". By looking during the transition between "rad" and "grad", we can be sure the "g" is the only change.</p> <table> <thead> <tr> <th>pin</th> <th>0ms</th> <th>3ms</th> <th>6ms</th> <th>9ms</th> <th>12ms</th> <th>15ms</th> <th>18ms</th> <th>21ms</th> </tr> </thead> <tbody> <tr> <td>32(no g)</td> <td>1</td> <td>1</td> <td>1</td> <td>1</td> <td>2</td> <td>2</td> <td>2</td> <td>2</td> </tr> <tr> <td>32(g)</td> <td>1</td> <td>1</td> <td>3</td> <td>1</td> <td>2</td> <td>2</td> <td>0</td> <td>2</td> </tr> </tbody> </table> <p>We can see it obeys the same symmetry relation. The only difference between the two signals is in the 6 millisecond position, where it goes to 3 units when the "g" is showing. In other cases, it stays at 1 unit.</p> <p>This makes sense. When a segment is meant to be off, this scheme keeps its voltage always within 1.5 volts of the four backplanes. When it is meant to be on, it goes all the way up to 4.5 volts making for a 1.5 volt difference with three of the backplanes and a 4.5 volt difference with the backplane it targets.</p> <p>Assuming the others segments are driven in the same way, this tells us how to tell which are activated. <strong>For each pin, its corresponding cells will be active when it reaches 4.5 volts during each of the four measurement windows.</strong></p> <p>There are a few downsides to this scheme which we can certainly see in the final product. For one thing, every cell always has at least 1.5 volts across it, so it is very slightly on. If you hold the display at an angle, you can faintly see all the supposedly-turned-off cells. This effect goes away when the calculator is turned off for real, so it is clearly due to the multiplexing scheme.</p> <p>Another downside is that cells are only activated for 1/4 the time. Presumably we could achieve deeper tone depth in the turned-on cells if they were driven the whole time.</p> <p>Both of those pale in comparison to the big advantage of a multiplexed display: we got 112 segments with only 32 pins from the microcontroller, four of which are always giving the same signal.</p> <h1>from measurement, to circuit</h1> <p>To begin, let's take all 32 pins and put them into comparators. <a href="https://aldenbradford.com/KA339A-D.pdf">The comparators I have</a> prefer to work near their lower voltage end, so let's use the 0 value during the second half of the cycle to read our data pins. We can get the start of our read by looking at groundplane 1, for example. This way, all our comparators can share a reference value of 0.75 volts. This gives us a clean signal we can feed into any microcontroller, with the output voltage determined by the comparator chips.</p> <p>After that, we just need a microcontroller to act as a timed shift register. 29 pins (28 for data and one for the timing pulse) is too many for any of the ones I have on hand -- the Arduino Nano has 22 and the Raspberry Pi Pico has 25. I am inclined to set two Arduino Nanos on this task, each reading 14 pins, and have them communicate their combined signal to a separate microcontroller which will handle the keypad. This way I can be sure that the timing of reading the display will be handled correctly. The Arduinos can take 5 volts on their data line, so I can give the comparators extra breathing room with a 5 volt input. This is perhaps a bit wasteful, leaving 7 pins on each Arduino untouched, though of course a couple of those will be needed to communicate. The benefits are significant however: it's a modular approach which uses parts I have on hand.</p> <h1>luxuries</h1> <p>Since the comparators come in chips of four, we have enough to read every backplane for no additional cost. The Arduinos also have plenty of room to pick up the additional pins, they can certainly take in 18 pins. We could use this to shift in our data in an interrupt, without having to mess with timers. I hope this will make it more resilient against timing bugs, and make it easier to write code which handles communication since I won't need to handle reading data in the main loop.</p>Reverse-Engineering a Matrix Keypad2022-07-21T00:00:00-04:002022-07-21T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-21:/reverse-engineering-a-matrix-keypad.html<p>The process of figuring out the layout of a keypad without looking at it directly.</p><p>In order to improve <a href="/calculator_emulator">my calculator emulator</a>, I am reverse-engineering a TI-30Xa. I want to be able to send key presses electrically from a microcontroller. This requires knowing the layout of the keypad matrix, i.e. what wires connect to what buttons. Unfortunately, the circuit board is riveted down with plastic. I don't have direct access to the far side of the circuit board because I want to keep the calculator in a working state.</p> <p><img alt="a picture of the inside of the calculator before it was modified" src="/images/calculator/inside_before.jpg" width="600"></p> <p>There are a couple ways I could still get access to the other side of the circuit board.</p> <ul> <li> <p>I could get the thing x-rayed. That would be expensive. It would be way more cost-effective to just buy another calculator to disassemble.</p> </li> <li> <p>I could drill out the rivets and replace them with appropriate screws. I'm not sure if the right screws would be a common size, so this might also get expensive. Besides, I would rather not have to take the thing apart again.</p> </li> </ul> <p>Instead, I used an indirect method. Since I have electrical access to all the test points, I can just connect an ohmmeter between an A wire and a B wire, then look for a button press which changes the measured resistance. In theory there should be one button for every pair of wires in the matrix.</p> <p>Here is the result, after measuring every pair.</p> <table> <thead> <tr> <th></th> <th>A0</th> <th>A1</th> <th>A2</th> <th>A3</th> <th>A4</th> <th>A5</th> <th>A6</th> </tr> </thead> <tbody> <tr> <td>B0</td> <td>+/-</td> <td>2nd</td> <td>+</td> <td>0</td> <td>.</td> <td>SIN</td> <td>1/x</td> </tr> <tr> <td>B1</td> <td>y^x</td> <td>HYP</td> <td>-</td> <td>1</td> <td>5</td> <td>COS</td> <td>x^2</td> </tr> <tr> <td>B2</td> <td>OFF</td> <td>pi</td> <td>X</td> <td>2</td> <td>6</td> <td>TAN</td> <td>sqrt</td> </tr> <tr> <td>B3</td> <td></td> <td>Sigma+</td> <td>/</td> <td>3</td> <td>7</td> <td>DRG</td> <td>EE</td> </tr> <tr> <td>B4</td> <td></td> <td>STO</td> <td>=</td> <td>4</td> <td>8</td> <td>LOG</td> <td>(</td> </tr> <tr> <td>B5</td> <td></td> <td>RCL</td> <td>ab/c</td> <td>&lt;-</td> <td>9</td> <td>LN</td> <td>)</td> </tr> </tbody> </table> <p>There are a few things about this layout that surprised me.</p> <ol> <li> <p>There are only 39 of the 40 keys represented here, even though there are three unused connections in the A0 spot. Further probing shows that the ON key pulls the RESET pin to ground, which we can see from the visible traces has an external pullup to B+. It makes sense in retrospect that ON would have its own pin, since they would want to activate it from an interrupt, not with the active polling that a matrix keypad requires.</p> </li> <li> <p>B6 and A7 don't seem to be connected to the rest of the matrix. We can see on the circuit board that they are connected to either side of the RESET pad, which (if we believe what's written on the board) should put the calculator in a stable state after power loss. It's strange that this would use two pins, when the same effect could be done with a pullup and a ground. Perhaps this has something to do with wanting the RESET pad to work even if the calculator is in an off state, so that whoever is changing the battery does not need to turn the board over while it is disassembled. I don't have an explanation, it is still a mystery to me.</p> </li> </ol> <p>Since each key has two coordinates which range from 0 to 7, it seems natural to represent each key with a single byte. Three bits for A, three bits for B. That leaves two bits left over, which could be used to signal the special values ON and RESET, though of course we could also use them for something else, since we have so many of the 64 places in the table unoccupied. I am getting ahead of myself here.</p> <p>Now that I know a bit more about what the wires on this calculator do, it is interesting to compare a previous revision which I have also photographed but have not probed.</p> <p><img alt="a picture of the inside of an older revision of the calculator" src="/images/calculator/older_inside.jpg" width="600"></p> <p>A few differences are obvious -- it runs at 3 volts instead of 1.5, all the soldering was done by hand, there are six screws instead of four. If you look at the display from the front you can see it has all the same segments, though they are a bit smaller and slightly different in shape. It also has 32 wires going to the LCD, so I imagine it is driving them in the same 28x4 manner. Some of the LCD wires are also going elsewhere using vias, for example you can see S2 in the lower left. What's it doing there? Are some of the LCD wires doubling as keypad timing signals? There are only seven traces leading to the microcontroller which don't also touch the LCD, so I suppose it must be using those 15 LCD wires with vias to poll buttons some of the time.</p> <p>We have the familiar A1 through A6 and B1 though B6, though no A0 or B0 so I expect they are counting from 1 in this model. There is a big A7 printed in the middle, but it doesn't seem to be associated with any one trace or pad. Most puzzling of all are the pads labeled K1 through K4. What could they be doing? Maybe in this version only 36 of the buttons are in a matrix, and the other four use pads K1 through K4.</p> <p>Those questions will have to wait for another day. For now it is enough to see that in the modern revision we now have a correspondence between traces and buttons, without having to disassemble it any further.</p>Reflections on wiring a calculator2022-07-19T00:00:00-04:002022-07-19T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-19:/reflections-on-wiring-a-calculator.html<p>Some thoughts on soldering, connectors, circuit repair, and test setups.</p><p>This past weekend I sat down to solder some connectors onto a TI-30Xa calculator, which will be used in an upcoming project to validate <a href="/calculator_emulator">the emulator I have written</a>. This stirred up some thoughts which might be helpful to anyone who is beginning in electronics, as I once was. In no particular order, I will share those thoughts here.</p> <h1>Why use connectors?</h1> <p>The most direct way to get electrical access to a circuit is with probes -- the pointy things which come with most multimeters or oscilloscopes. In fact, that's what I used at first, to begin understanding the circuit I am dealing with. Looking at the circuit board before modification, we can see some handy labeled test points. Those are the black circles without holes in them.</p> <p><img alt="a picture of the inside of the calculator before it was modified" src="/images/calculator/inside_before.jpg" width="600"></p> <p>You can also see the same black material up at the top, where the ribbon for the LCD is glued on. This material is electrically conductive, so we can just push a probe into it to find the signal we wish to measure.</p> <p>That's all well and good for measuring one or two pins at a time. It is not so good for measuring several at once, and it is not good for making a permanent connection. Instead, we should solder onto the circuit to make the connection permanent. The most obvious way to do this would be to split apart one (or multiple) ribbon cables and leave them running out of the calculator, ready to wire onto a circuit board at the far end. I considered this. I decided against it for a few reasons, and it turned out to be a good decision.</p> <h2>Separation of concerns</h2> <p>A ribbon cable would require making every connection right on the first try. If I can use a separate wire for each connection then I have the freedom to throw it out if I make a mistake, make each connection the perfect length, and rework after the fact if I decide to change just one part. All of that would be near impossible if I were using one big ribbon cable, since all the wires used in soldering would be fused together. Also, I don't even want to think about the nightmare of keeping a bunch of loose wires in order running out of the calculator directly. The connector allows me to separate my concerns, letting me separately use the best wires for inside the calculator and for outside the calculator, without having to compromise.</p> <h2>Modularity</h2> <p>This project is an infant. I don't know what kind of circuit will be at the other end of the wires just yet -- which ones should go where? By using a connector I can easily build a simple test setup now, and replace the whole thing later once I know the permanent solution.</p> <h2>Reuse</h2> <p>In the future, I may want to repurpose this part for something else -- maybe a version of the calculator which connects to a display for projection, for example. Also, there is no reason why I should not allow it to still function as a pocket calculator. Those would be much harder to do in the future if I have a bulky bundle of wires hanging out of it. By using connectors I free myself up to do different things in the future without having to modify the calculator even further.</p> <h1>Choice of connector</h1> <p>The calculator has 32 pins going to the display, 15 for the keypad, one labeled "reset", and two battery connections. That makes 50 connections in all which I want to be able to access from the outside. What connector could be appropriate?</p> <p>I have grown to like IDC connectors from prototyping, since they have the 0.1 inch pin spacing that most prototyping boards share and they are keyed preventing reverse insertion. The obvious choice for this project would be a 50 pin IDC cable, but I discarded this option for two reasons:</p> <ol> <li> <p>While the cables are readily available since they are used for 8-bit SCSI drives, the headers are not so easy to come by.</p> </li> <li> <p>The calculator is just a bit narrower than the 50 pin header is long. Using this connector would threaten the structural integrity of the case.</p> </li> </ol> <p>I opted to use a 40 pin IDC cable, which fits nicely in the space where a solar panel would go in the school edition of the calculator. These headers and cables are also much cheaper. The down side is that I need to find a space for ten more pins. There is a common 10 pin IDC cable, but there is not enough room in the calculator for it. Two more rows of pins would spill over the top of the calculator. There is a 10 pin JST connector available which is keyed and has all 10 pins in a single row, but it is expensive in small quantities. That's why for these 10 additional pins I chose to just use a row of bare headers affixed to the case with hot glue. Not perfectly elegant, but compact and flexible enough for this project. Here you can see how it all fit together, inside and out.</p> <p><img alt="a picture of the inside of the calculator after it was modified" src="https://aldenbradford.com/inside_after.jpg" width="600"> <img alt="a picture of the outside of the calculator after it was modified" src="https://aldenbradford.com/outside_after.jpg" width="600"></p> <p>If I had an unlimited budget, I think the best solution would be a DB50 D-sub connector. Since these put the pins in three offset rows they can be a lot more compact, and they are available in panel mount configurations so we could get a secure connection to the calculator case. They strike a good balance between being small enough to fit the space I have and large enough to still be able to solder individual wires. At $10 a connector and$30 a cable, the price is too much.</p> <p>Because it would be unpleasant to have to trace out all the connections I just made by hand, I will relay them here in a table. Here is what each pin connects to, as viewed from the outside.</p> <table> <thead> <tr> <th>-</th> <th>-</th> <th>-</th> <th>-</th> <th>-</th> <th>RESET</th> <th>B6</th> <th>B5</th> <th>B4</th> <th>B3</th> <th>A7</th> <th>A6</th> <th>A5</th> <th>A4</th> <th>A3</th> <th>-</th> <th>-</th> <th>-</th> <th>-</th> <th>-</th> </tr> </thead> <tbody> <tr> <td>B+</td> <td>B2</td> <td>B1</td> <td>B0</td> <td>1</td> <td>3</td> <td>5</td> <td>7</td> <td>9</td> <td>11</td> <td>13</td> <td>15</td> <td>17</td> <td>19</td> <td>21</td> <td>23</td> <td>25</td> <td>27</td> <td>29</td> <td>31</td> </tr> <tr> <td>GND</td> <td>A2</td> <td>A1</td> <td>A0</td> <td>2</td> <td>4</td> <td>6</td> <td>8</td> <td>10</td> <td>12</td> <td>14</td> <td>16</td> <td>18</td> <td>20</td> <td>22</td> <td>24</td> <td>26</td> <td>28</td> <td>30</td> <td>32</td> </tr> </tbody> </table> <p>B0 through B6 and A0 through A7 are the traces for the keypad matrix, matching the labels on the test pads. I have also copied the labels on the test pads for B+, GND, and RESET. I have chosen to number the traces going to the LCD in increasing order, starting from the trace nearest to the battery.</p> <p>Notice I also removed the battery. The calculator would still work with the battery, and I could even read values from it with the battery installed. It would save me from having to make an external 1.5 volt source. I chose to remove the battery because it is a pain to open the calculator up, even more so now that there is delicate wiring throughout. If I left the battery in, one day I would have to open this up again to remove it, because dead batteries can leak. It is better to power it externally for the duration of this project. If in the future I want to use this as a normal calculator again then I can simply put the battery back in.</p> <h1>Soldering thoughts</h1> <p>I am not sure what the black conductive coating on the test points in this circuit are. In most circuit boards the test points and through holes are plated with a zinc alloy to make it easier to solder onto them. This board has no through hole components, so I am guessing they skipped the plating step altogether. I am guessing the coating they used is ideal for the membrane keypad on the other side of the circuit board.</p> <p>Unfortunately for me, that black coating is not solderable. That means I have to solder onto bare copper. Here are some things I figured out along the way.</p> <h2>Flux is mandatory</h2> <p>The flux in the core of my solder was not nearly enough. I was able to get a connection one or two times, but the repeated heating and cooling from several solder attempts caused the copper to corrode making it impossible to connect in some places. I even broke one of the traces at one point from scraping away the corrosion too many times, which I then had to repair. These problems went away when I started using some cheap paste solder. I was able to get a good connection on the first try most of the time using flux.</p> <h2>Match the size of the wire to the trace</h2> <p>It became much easier to solder onto the traces when i started using <a href="https://www.adafruit.com/product/3522">magnet wire</a> instead of the random scraps of wire I had laying around. It is slightly thinner than the traces I was soldering onto. This made the whole space less crowded which made it easier to make a connection without damaging a previous connection. The magnet wire was also better matched to the thermal mass of the trace, so they heated at a similar rate which made the soldering go quicker.</p> <h2>Mechanical contact precedes thermal contact</h2> <p>The soldering iron has to get the soldering surfaces hot, and it does that by touching them. If there is not much pressure between the iron and the soldering surface, heat will flow slowly. This is a particular challenge for the magnet wire, as it does not resist bending at all. In order to tin the wire, I got a scrap of wood and pressed the wire down onto it using the soldering iron. This probably also helped in scraping the insulation from the end of the wire. Doing it this way I was able to tin the end of the magnet wire quickly and reliably.</p> <h1>My test setup</h1> <p>On a project with so many parts I am bound to get confused at some point, and things are bound to not work on the first try. This means it is extra important that I always know what parts are working and what parts are not. I don't want to be debugging this circuit a month down the line to find that my problems are from a bad solder joint that went undetected. That why before and after every solder connection I tested the circuit to make sure the signal from each trace made it to its designated pin. This would get tedious if I had to put in and remove the battery every time -- I could even have damaged the battery connector doing that. Instead, I got out an LM317 voltage regulator and powered the calculator from a bench power supply. By wiring a switch in line, I could easily engage or disengage power as needed without putting wear on the calculator itself.</p> <p>Before making each solder connection I probed the trace to see what signal was there. These are low frequency signals, changing on the order of 100 Hz, so it was not essential to have the oscilloscope probe grounded directly to the board -- grounding to the power supply was perfectly adequate. Once I knew the shape of signal, I could turn off the calculator with the switch, solder the wire to the connector, turn it back on, and look for the signal on the output pin. This caught faulty connections several times, which saved me the effort of going back to previous work after having already moved on.</p> <p>I could have done this a more basic way, just setting my multimeter to continuity mode and probing a couple points each time, maybe checking the signals at the end using the battery. That would have been quicker to set up since all it would take would be removing the battery. The method I chose was slightly faster to use, since I only needed one hand for the oscilloscope probe. I also got a more direct confirmation that the entire path from the LCD driver to the output pin was intact. By investing a bit of time in the setup, I was able to save a lot of time during the tedious portion of the operation. This is a general principle: it's better to take the time to find a good way to do a repeated task, rather than diving in to the method with the fastest setup time.</p> <h1>Conclusion</h1> <p>Part of the joy of this hobby is finding small ways to optimize, finding a cheap solution which is still reliable and doesn't need too much labor. We got to experience some of that in picking out the connectors and wires, and ensuring reliability by testing at every stage. Another source of joy is in taking a complicated system and exploring it until you know all there is to know about it. We had to do some of that exploring here to make sure we are extracting the right test points, but mostly the wiring done here will enable us to explore the rest more easily in the future. This is a slow hobby, where we are constantly laying the groundwork for what is to come. If we already understood the circuit and knew how we would interface with it, we could jump straight to the end and print a PCB that handles most of this for us. That would also remove all of the fun, at least from my point of view.</p>Review: How to Do Nothing by Jenny Odell2022-07-17T00:00:00-04:002022-07-17T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-17:/how_to_do_nothing.html<p>I liked this book, it makes good points in interesting ways</p><p>I know I'm late to the boat on this one. This book came out in 2019. It was one of <a href="https://www.instagram.com/p/B6oYKxAgCn7/">Barack Obama's favorite books that year</a>. I only just got around to finishing it. I don't think Jenny Odell will mind that I took this long. She seems to be okay with things that go slow.</p> <p>She wisely opens the book with <a href="https://terebess.hu/english/chuangtzu.html#4">an old Daoist story</a> about a useless tree. Its wood is so knotted, split, and crooked that it could not possibly be used to build anything. By being useless, it resists getting chopped down, becoming the oldest and strongest tree around.</p> <p>That was the most memorable part of the book, and it plays well with the other anecdotes spread throughout. Odell resists simple instructions and admonitions by grounding each point in concrete stories, and complicating her conclusions wherever she can. Nevertheless, she has some clear and valuable messages to share.</p> <p>One of those messages is that it's valuable to look backward as well as forward. Novelty is overrated. I don't hear this enough, living as I do in academia. In the academic world it goes unsaid that to be publishable (i.e. worthy of the time of an academic) an idea has to be new. This shows up in two old math cliches.</p> <ul> <li> <p>After finally completing a problem which took months or years to solve, the mathematician is distraught when they receive feedback from reviewer number 1: this problem was solved by Euler in 1775.</p> </li> <li> <p>The student asks "why do we use this notation?", "why is this ambiguous case defined in this manner?". The teacher replies, "for historical reasons".</p> </li> </ul> <p>I invite you to imagine, if you will, an academic in any other discipline responding in this way. "Why do we call it Germany while the people there call it Deutschland?" Oh, for historical reasons.</p> <p>This obsession with novelty is of course a false and dangerous one, since we are all indebted to the work and ideas of those who came before us. Without revisiting, renewing, and reinterpreting old ideas we risk losing them. There is value in learning old ways of thinking and rediscovering old techniques. When we are constantly told the opposite, it is good to hear this reaffirmed.</p> <p>I won't try to summarize the book here. Instead, I will just recommend you read it yourself. It changed the way I think about some things, and made me feel a bit better about some of my weird, useless hobbies and interests.</p>A Two Bit Analog-Digital Converter2022-07-12T00:00:00-04:002022-07-12T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-12:/two_bit_adc.html<p>A surprisingly simple circuit gives a reliable two bit analog-digital converter using only one integrated circuit chip.</p><p>For a project I am working on I need to read the signal driving a multiplexed segment LCD. The signal switches between four voltage levels (0, 1.5, 3, and 4.5), across 32 different pins. Here I will describe a few ways to recover that signal onto a microcontroller.</p> <h1>An off-the-shelf ADC chip</h1> <p>We only need two bits of information to distinguish between the four voltage levels. Even the cheapest ADCs give a whole eight bits -- <a href="https://www.digikey.com/en/products/detail/touchstone-semiconductor/TS7003ITD833T/3645626">the cheapest ADC on Digikey</a> right now gives you eight bits and communicates over SPI, and would cost just under a dollar a piece. More realistically, there are eight-input ADCs which cost more like $4 each in small quantities, which would be a better deal over all.</p> <h2>Advantages</h2> <ul> <li>It's dead simple to implement, fairly easy to understand.</li> </ul> <h2>Disadvantages</h2> <ul> <li> <p>It's power-hungry, both from the power rails and from the the data signals. An LCD driver usually has to put out basically zero current, and a low-end ADC might consume a noticeable amount, possibly more than the LCD driver is comfortable supplying.</p> </li> <li> <p>It's noisy, since it puts a time-varying load on the signal wires.</p> </li> <li> <p>It's expensive!</p> </li> </ul> <h1>A handfull of microcontrollers</h1> <p>Here is a cheaper solution, which would also need way fewer parts. I have a bunch of Arduino Nano microcontrollers around, and each one has eight ADC inputs. We can set four of them to sample the circuit.</p> <h2>Advantages</h2> <ul> <li> <p>Very few additional components, since it has most of the circuitry we need included.</p> </li> <li> <p>Cheap: I already have them so it's basically free, but even if you didn't have them on hand the off-brand ones cost about$3 each.</p> </li> </ul> <h2>Disadvantages</h2> <ul> <li> <p>Still noisy and power-hungry</p> </li> <li> <p>We have to write code for the microcontrollers</p> </li> </ul> <h1>A resistance ladder (flash) ADC</h1> <p>A better solution is to wire up our own 2-bit ADC. A traditional way to do this would be to use three comparators and a resistance ladder. Something like this.</p> <p><img alt="a picture of a traditional flash ADC" src="https://aldenbradford.com/traditional_flash_adc.svg" width="300"></p> <h2>Advantages</h2> <ul> <li> <p>Low power, low noise. If we use a good mosfet comparator this can consume basically no power except when the input value changes. The resistance ladder sets thresholds at 1.25, 2.5, and 3.75 which nicely divides the voltage levels we are comparing.</p> </li> <li> <p>Independent of component values. We can use any resistors we like, and any comparator chip. This makes it potentially very cheap.</p> </li> <li> <p>Solid-state. The outputs are simple digital high and low values, so we can hook them up to a shift register, or even use them for interrupts on our microcontroller. This also makes it very easy to debug, since all the logic is visible.</p> </li> </ul> <h2>Disadvantages</h2> <ul> <li> <p>Uses an odd number of components. Comparators are usually available in packages of two or four, and this uses three.</p> </li> <li> <p>The output requires processing. We could take a digital output straight from the comparators, but then we would be using three wires to send two bits. We need those some additional logic chips to bring it down to two, which adds to the complexity. A repeatable unit of these on a board would necessarily process multiple signals and include several ICs so as to make use of every available gate.</p> </li> </ul> <h1>A creative solution</h1> <p>Here is the design I have settled on.</p> <p><img alt="a compact 2-bit ADC" src="https://aldenbradford.com/compact_adc.svg" width="300"></p> <p>This manages to get our output using just two comparators. The two resistors take the average from the high-bit and the 2.5 volt rail, and use that as the threshold for the other comparator.</p> <h2>Advantages</h2> <p>This has all the benefits from the flash ADC, plus it only takes 2 resistors and half of a 4-comparator IC. This makes it easier to assemble, and way cheaper -- less than 22 cents per input, in small quantities.</p> <h2>Disadvantages</h2> <p>Requires a low-impedance 2.5 volt rail. This is not a problem for me, since I have a handful of 2.5 volt regulators on hand. Of course, all the comparators for the whole project can share the same rail, so this doesn't add much to our part count. If you only had a couple of these to do, here is an alternative arrangement you could use which only needs the power rail, and a few more resistors.</p> <p><img alt="a version which does not need a 2.5 volt rail" height="250" src="https://aldenbradford.com/no_reference_adc.svg"></p> <p>This also reduces the noise on the 2.5 volt reference line, which could reduce the chance of jittering on the output. In the previous version, if signal A changes its low bit while B is changing its high bit, the small leakage from the feedback circuit of B could prevent a clean transition on A. This circuit solves that.</p> <h1>Generalizing</h1> <p>I think this is a neat topology! If you are familiar with R-2R ladders, it is probably clear to you how we could generalize this circuit to make an ADC of arbitrary precision, at least on paper. Bit n would be generated like this.</p> <p><img alt="how to form bit n of an ADC of this topology" height="200" src="https://aldenbradford.com/r_2r_adc.svg"></p> <p>Essentially, we use an R-2R ladder as a DAC, giving the best approximation to the signal if bit n were high. We compare that to the actual signal to see if bit n should be high. Each bit only uses inputs from the higher-order bits, so I don't expect there is any danger of oscillation.</p> <h1>Conclusion</h1> <p>This is actually a special case of a really common design called a "subranging" flash. Usually designers will use more bits in a stage before going to an ADC, which gets them decreased latency and better monotonicity. Since these are usually all integrated onto a single chip they can make the priority encoder cheaply on the chip without any messy routing.</p> <p>In my design it makes sense to incorporate feedback after just one bit. I get to use an off-the-shelf part (the comparator) for a very specialized purpose, all with the addition of just two resistors. It's good to keep an open mind, try out several designs, even for an undemanding task like this. Doing it the traditional way with a full 8-bit ADC on each line, or even using standard 2-bit ADC design, would lead to a needlessly expensive and complicated circuit. This custom design will do more with less.</p>Measures of Bimodality2022-07-10T00:00:00-04:002022-07-10T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-07-10:/bimodality.html<p>What are the most popular ways to measure the bimodality of a sample from a random variable?</p><p>Much of my research has dealt with the problem of deciding if a sample from a random variable indicates that the underlying distribution is bimodal. We have a general intuition, passed down from Francis Galton (I'm not sure where he got the idea), that random effects tend to produce unimodal distributions. This has since then been justified using the central limit theorem -- if you have lots of small random perturbations, the cumulative effect of them will be Gaussian, and hence unimodal. If your distribution is not unimodal, his argument goes, you have good reason to believe that your data are coming from two different sources, each of which has a unimodal distribution.</p> <p>This is the basic principle at the heart of the nTARP clustering procedure. In this form of projection pursuit, we seek a distribution of points which appears bimodal. We set a threshold in between the two modes, then bin the data according to which side it falls upon. We hope that, because the observed distribution is bimodal, it must have originated from two distinct sources. That is what makes it a clustering method: it seeks to break apart a signal into distinct sources, based only on patterns in the data.</p> <p>Up to now, nTARP has always used the within-group sum of squares statistic, denoted W. This is used under various names in many different contexts: it is the objective function which the k-means algorithm seeks to minimize in one dimension, and also the objective function of Otsu's method for image thresholding. Our results using this method have been mixed. One one hand, when a given sample is very plainly bimodal, the statistic indicates this reliably by assigning it a small W value. On the other hand we have two significant issues:</p> <ul> <li>W is significantly smaller on distributions with heavy tails. This makes it so that some distributions which appear somewhat bimodal can have a higher W than other distributions which are clearly unimodal, but which have heavy tails.</li> <li>W is not stable for small sample sizes. Because W can be computed in a way which depends continuously upon the cumulative distribution function, the Glivenko-Cantelli theorem guarantees that, as the number of samples drawn from a distribution grows, the empirical W will converge to the value of W for the underlying distribution. Unfortunately, this convergence appears to be too slow in many circumstances. For certain data sets, we find that two independent samples of hundreds of points will lead to very different estimates of the population W. Note that the meaning of "small" depends on the underlying distribution in a manner I do not yet fully understand -- for some data sets it is very stable, for others very unstable.</li> </ul> <p>For this reason, we are interested in looking for other tests of bimodality. That is, given a sample of several points from a one-dimensional random variable, what techniques exist to estimate whether (or to what extent) the random variable is bimodal? Here we attempt to survey a few of the most popular techniques.</p> <h1>A survey of tests</h1> <h2>Statistics based on labeled data</h2> <p>These are generally unsuitable for clustering, because they rely on data which has already been split into two proposed sources. Most of these assume each mode of the data is Gaussian. I list a few here (from Wikipedia) for completeness.</p> <ul> <li>Ashman's D</li> <li>Bimodal separation</li> <li>Bimodality amplitude</li> <li>Bimodal ratio</li> <li>Wilcock's bimodality parameter</li> <li>Wang's bimodality index</li> <li>Sambrook Smith's index</li> </ul> <p>Linear discriminant analysis would also fall into this category, though it is less concerned with detecting bimodality than with fitting bimodal data.</p> <h2>Kurtosis</h2> <p>There have been decades of arguments at this point over exactly what qualitative features are encoded by kurtosis. The only one which is generally uncontroversial is that distributions with high kurtosis have "heavy tails". Of course, this is in some sense kicking the can down the road, since people may mean different things by "heavy tailed". One of the more controversial perspectives is that kurtosis measures how bimodal a distribution is. This is usually justified by the observation that a Bernoulli random variable, which is in some sense the ideal bimodal distribution, minimizes the kurtosis among those distributions with a common variance. This interpretation is frowned upon by some, but is still widely used by researchers analyzing ordinal data with a bounded support, such as Likert scales. For a sophisticated discussion, see <a href="https://www.tandfonline.com/doi/pdf/10.1080/00031305.1971.10477241">"Kurtosis Measures Bimodality?" by David Hildebrand.</a></p> <h2>Bimodality coefficient</h2> <p>This one is attributed to various sources, but certainly goes back at least to Karl Pearson. (skewness^2+1)/kurtosis. The rationale is the same as for kurtosis, but it always gives a number between 0 and 1. I have not found any strong theoretical justification for this statistic. One reason for its wide use is that it is available in many common statistical software packages, such as SAS. Another is its age -- it is well-established.</p> <h2>Sturrock's index</h2> <p>A Gaussian fit is performed (any model could be used in principle, but they chose Gaussian), giving a theoretical CDF. The inverse CDF is applied to the data mapping them to quantiles between 0 and 1. Sturrock's index is the spectral power of the resulting distribution due to the frequency 2. This technique is essentially equivalent to comparing the empirical CDF to the theoretical CDF on a P-P plot. Their strategy is quite ad-hoc, probably appropriate for their domain, but would not generalize to other types of data very well. In particular, it would only make sense over a certain range of separations. The strategy of comparing the empirical CDF with a theoretical CDF is very general, and could be used to quantify how well any sample matches a given theoretical model. This would more accurately be termed a test for discrepancy from a proposed theoretical CDF, rather than a true test of multimodality. It gets to be on this list because it is substantially different from any of the other tests described here.</p> <h2>Hartigan's dip test</h2> <p>This is based on comparing the empirical CDF to the nearest unimodal CDF. Hartigan and Hartigan show that the nearest unimodal CDF is always a mixture between the empirical CDF and a uniform distribution. The dip statistic can be computed efficiently, using some FORTRAN code which has been passed around since the 80s and translated into other languages. This only gives an index; to make it into a proper "test", Hargian and Hartigan suggest computing it on samples from a known distribution for comparison.</p> <h2>Silverman's kernel density test</h2> <p><a href="https://purdue.primo.exlibrisgroup.com/permalink/01PURDUE_PUWL/5imsd2/cdi_proquest_journals_1302943262">Silverman</a> takes the problem of estimating a kernel density bandwidth, and turns it on its head. Usually, to get a good kernel density estimate, you need unimodal data. By choosing the smallest bandwidth which keeps the estimate unimodal, you can resolve as much detail as possible without introducing artificial wiggles. It becomes a problem if the data are bimodal, because this approach would smear out the distribution removing all detail. Silverman takes this burden and turns it into an asset: by looking for distributions which require a large bandwidth to reach unimodality, we can pick out those which are bimodal. Silverman also gives a significance test, based on bootstrapping.</p> <p>As Hartigan and Hartigan note, this statistic has the disadvantage of depending on the scale of the data. You can normalize the data of course, but care must be taken to do so fairly. This would also naturally deal poorly with heavy-tailed distributions, where there are regions which are severely undersampled. Another disadvantage is that is is slow to compute. If a Gaussian kernel is chosen then the number of modes depends monotonically on the bandwidth, which allows for a binary search, but Silverman does not propose any better technique than this.</p> <p>I do wonder whether we could get somewhere with a slightly more subtle version of this test, somewhat akin to the Topologist's bar codes. Might we say the correct bandwidth is not where the data becomes unimodal, but where the modality of the data does not depend strongly on the bandwidth? That is, there should be a wide range of bandwidths over which the density estimate has the same modality. That range might tell us more than just a single number can.</p> <h2>information-theoretic approaches</h2> <p><a href="https://link.springer.com/content/pdf/10.3758%2Fs13428-012-0225-x.pdf">Some people</a> seem to be applying an information-theoretic approach as well, to see whether a two-component mixture model does a significantly better job of expressing the information in a sample. This is a purely parametric approach, in that the underlying distribution is assumed.</p> <h1>Conclusions</h1> <p>Three of the above methods see widespread use across disciplines: the bimodality coefficient, the Hartigan dip test, and Silverman's kernel density test.Many papers cite these three together, in a scattershot approach to statistical analysis. For some reason, the statistic W only sees much use in the image processing community, presumably due to the influence of Otsu's method for image segmentation, and even there is goes by another name. I have not seen much written with the goal of improving on these statistics. I think most authors find it more valuable to have statistics which are directly comparable to other researchers' numbers, rather than seeking marginal improvements to the computation methods.</p> <p>Of course, if a parametric form for the data are known, far more options are available -- one could use gradient descent to estimate parameters, for example. In the case where more is known about the parametric form of the data, we would expect a custom method to work better in principle because it does not have to learn what we already know. We are looking for a truly general statistic, something which does not depend on the form of the distribution. This rules out most published approaches other than the four identified above.</p>Pinball Science2022-02-22T00:00:00-05:002022-04-19T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2022-02-22:/pinball_science.html<p>an old game I enjoyed as a kid, which is now hard to install.</p><h1>the game</h1> <p>One of my favorite video games from childhood was Pinball Science. It seems that it was mostly sold (in the US in any case) at Scholastic book fairs, to accompany the book The Way Things Work by David Macaulay. It's hard to find much about it online. The best source I have found is <a href="https://www.youtube.com/watch?v=4VeQDGTrruA">this youtube video</a>, where someone in a similar situation to me managed to get it running in a virtual machine. I have had no luck running it on a virtual machine. This is not terribly surprising, since it never ran particularly well even when it was new -- the loading times were long, and it was prone to graphical glitches and crashes. The only way I have personally gotten it working in recent years was by installing Windows 98 on a contemporaneous laptop.</p> <h1>installation woes</h1> <p>If you want to run it, you may find yourself in a pickle due to a subtle problem. Pinball Science is compatible with Windows XP, but not every version is compatible. If you go looking online, you may find ISO copies of the disk. Out of all those I have checked, none will run on XP without additional configuration. This is because Pinball Science relied on behavior which changed in Windows XP Service Pack 1. The DK website (when it was still up) claimed that they did publish a version of the disk which is compatible with XP including later service packs, but I have never found a copy of that disk.</p> <p>Do not despair: they published a patch which I have used before with success. It used to be available for download <a href="support.selectsoft.com/products/A/LDAMAFAMEJ.htm#Downloads">from their website</a>, but their website went offline many years ago. Archive.org does <a href="https://web.archive.org/web/20061127031515/support.selectsoft.com/products/A/LDAMAFAMEJ.htm">have a backup of that website</a> including <a href="https://web.archive.org/web/20061127031515/http://support.selectsoft.com/download/SP1fix.exe">a backup of that patch file</a>. Just in case, I have also <a href="https://aldenbradford.com/SP1fix.exe">hosted that file here</a>. For what it's worth, here is a link to <a href="https://web.archive.org/web/20050209013234/http://www.learnatglobal.com/html/xp_sound.html">another patch from that publisher</a>, which claims to be compatible with service pack 2 and has a different file name; however, the files appear to be identical. I have also <a href="https://aldenbradford.com/SPupdate.exe">rehosted that patch here</a>, just in case.</p> <h2>multiple versions</h2> <p>I have found at least two versions of the installation disk, and I believe there are more. These are the two versions I have in my possession. I have posted ISO copies of these disks <a href="https://github.com/AldenMB/AldenMB.github.io/releases/tag/Pinball_Science">on my Github</a>.</p> <h2>Scholastic</h2> <p>This seems to be the earlier one; all the files on it have a modification date of 1998 or earlier. When loaded on Windows, the name of the disk reads as DKMMTWSW.</p> <ul> <li>Case: <img alt="case photo" src="https://aldenbradford.com/scholastic_case.jpg" width="300px"></li> <li>Disk: <img alt="disk photo" src="https://aldenbradford.com/scholastic_disk.jpg" width="300px"></li> <li>Front: <img alt="front scan" src="https://aldenbradford.com/scholastic_front.png" width="300px"></li> <li>Back: <img alt="back scan" src="https://aldenbradford.com/scholastic_back.png" width="300px"></li> <li>Inside: <img alt="inside scan" src="https://aldenbradford.com/scholastic_inside.png" width="300px"></li> <li>Inside back: <img alt="inside back scan" src="https://aldenbradford.com/scholastic_back_inside.png" width="300px"></li> </ul> <h2>Global Software Publishing</h2> <p>This one is more recent, as some of its files have a modification date of May 15 2002. Since XP Service Pack 1 would not be released until November of that year, it seems unlikely that this would have the service pack 1 patch. Notably, it appears to have dropped support for MacOS, presumably because OS X came out the previous year and would be incompatible with this game, which was written with System 7 in mind. When you load this in Windows, the name of the disk reads as Dkmmtwsw.</p> <ul> <li>Case: <img alt="case photo" src="https://aldenbradford.com/global_case.jpg" width="300px"></li> <li>Disk: <img alt="disk photo" src="https://aldenbradford.com/global_disk.jpg" width="300px"></li> <li>Front: <img alt="front scan" src="https://aldenbradford.com/global_front.png" width="300px"></li> <li>Back: <img alt="back scan" src="https://aldenbradford.com/global_back.png" width="300px"></li> <li>Inside: <img alt="inside scan" src="https://aldenbradford.com/global_inside.png" width="300px"></li> </ul> <h2>The New Way Things Work</h2> <p>There's not much online about Pinball Science, but there is even less about the companion disk The New Way things Work. Perhaps it would be more accurate to say Pinball Science was the companion disk. Here's how the parentage seems to go:</p> <ul> <li>There is a book <a href="https://en.wikipedia.org/wiki/The_Way_Things_Work">The Way Things Work</a> released in 1988</li> <li>There is <a href="https://archive.org/details/the-way-things-work-cdrom">a CD-ROM version</a> released in 1994, which features animations and quizzes as well as text from the book. The idea seems to have been a children's digital encyclopedia, in the style of Encarta.</li> <li>In 1998 an updated version of the book is released, The New Way Things Work, featuring material about digital electronics. Separately, an updated version of the CD-ROM encyclopedia is sold, in a box set together with Pinball Science.</li> </ul> <p>I remember having a copy of The New Way Things Work around when I was little, but I don't remember anything about its contents. It must not have been too much fun, at least in comparison with Pinball Science. In any case, I have also preserved <a href="https://github.com/AldenMB/AldenMB.github.io/releases/tag/Pinball_Science">an ISO of this disk</a> along with the scans below. When loaded in Windows, the name of the disk reads as DKTNWTW.</p> <ul> <li>Case: <img alt="case photo" src="https://aldenbradford.com/way_things_work_case.jpg" width="300px"></li> <li>Disk: <img alt="disk photo" src="https://aldenbradford.com/way_things_work_disk.jpg" width="300px"></li> <li>Front: <img alt="front scan" src="https://aldenbradford.com/way_things_work_front.png" width="300px"></li> <li>Back: <img alt="back scan" src="https://aldenbradford.com/way_things_work_back.png" width="300px"></li> <li>Inside: <img alt="inside scan" src="https://aldenbradford.com/way_things_work_inside.png" width="300px"></li> <li>Inside back: <img alt="inside back scan" src="https://aldenbradford.com/way_things_work_back_inside.png" width="300px"></li> </ul>What's Inside "The REAL Cotton Candy Maker"?2019-10-01T00:00:00-04:002019-10-01T00:00:00-04:00Alden Bradfordtag:aldenbradford.com,2019-10-01:/whats-inside-the-real-cotton-candy-maker.html<p>Tracing out the circuit of an old, constantly-breaking toy.</p><p>When I was a child, I once owned a toy called "The REAL Cotton Candy Maker". It made cotton candy a couple of times, in small quantities. It stopped working after a few uses, for a reason I do not recall. Perhaps I never knew.</p> <p>Recently, I had the good fortune to find a used version of the same toy at a flea market for a couple dollars. I bought it, tried it out, and determined it did not work. I figured I would take it apart, and see what I could see.</p> <p><img alt="a hand-drawn circuit diagram" src="https://aldenbradford.com/cotton_candy_circuit.png" width="700px"></p> <p>Above is the circuit diagram I reconstructed. Here are a few observations:</p> <ul> <li>The circuit uses a reed relay to determine whether the lid is closed and in place. This is hooked up at JE1, on pin 7 of U2.</li> <li>JE2 is connected to a hall effect sensor, which detects a magnet on the machine's rotating shaft.</li> <li>JE3 is just connected to a couple LEDs.</li> <li>I have no idea what pin 11 of U2 is for. It seems to go out to a pad where it can be accessed. Perhaps this is used for programming the chip.</li> </ul> <p>What was wrong with my particular model? I will never know, because I got bored and threw it out. My hope is that the diagram I worked out along the way will someday help someone in a similar situation to diagnose more quickly, and perhaps get an answer.</p>