<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom"><title>Alden Bradford</title><link href="https://aldenbradford.com/" rel="alternate"></link><link href="https://aldenbradford.com/feeds/all.atom.xml" rel="self"></link><id>https://aldenbradford.com/</id><updated>2025-10-17T00:00:00-04:00</updated><entry><title>An old statistics exam</title><link href="https://aldenbradford.com/an-old-statistics-exam.html" rel="alternate"></link><published>2025-10-17T00:00:00-04:00</published><updated>2025-10-17T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2025-10-17:/an-old-statistics-exam.html</id><summary type="html">&lt;p&gt;Sharing an old statistics exam with my students, while the college's LMS is down.&lt;/p&gt;</summary><content type="html">&lt;p&gt;As I write this, my college is having technical issues with Canvas and with student email. I will extend homework deadlines to accommodate students, but I also want to make sure all students have an opportunity to study effectively for their exam on Monday. So, I am sharing a past exam here on my personal website.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aldenbradford.com/exam.pdf"&gt;Here is the old exam.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="https://aldenbradford.com/key.pdf"&gt;Here is the grading key for that same exam.&lt;/a&gt;&lt;/p&gt;</content><category term="Blog"></category><category term="Teaching"></category><category term="Statistics"></category></entry><entry><title>Pass the Pigs for Teaching Statistics</title><link href="https://aldenbradford.com/pass-the-pigs-for-teaching-statistics.html" rel="alternate"></link><published>2025-06-02T00:00:00-04:00</published><updated>2025-06-02T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2025-06-02:/pass-the-pigs-for-teaching-statistics.html</id><summary type="html">&lt;p&gt;A useful manipulative&lt;/p&gt;</summary><content type="html">&lt;h1&gt;The problem&lt;/h1&gt;
&lt;p&gt;Most people don't understand empirical probabilities. There is a common misconception: if a random event has two outcomes, then it is a 50/50 chance of each outcome. One of my goals teaching introductory probability is to replace that misconception.&lt;/p&gt;
&lt;p&gt;The simplest way would be to take something with known, obvious probabilities and use them to construct other probabilities. Take a 6-sided die, and it is obvious from symmetry that each side has a 1/6 chance of showing. From that we could compute the probability of rolling at least a 3 -- there are four atomic probabilities, so the probability is 4/6. There are two problems I see with this. First, not every probability model can be broken down into equally likely "atomic" probabilities. Take a coin weighted with &lt;span class="math"&gt;\(P(heads)=1/\sqrt{2}\)&lt;/span&gt;, for example -- the probability is irrational. Second, it kicks the can down the road: students are still left with the idea that all "atomic" probabilities are equally likely, if we can only identify what those probabilities are.&lt;/p&gt;
&lt;p&gt;One way I like to deal with this is by having my students roll Hershey's Kisses as dice. From my experience, they land flat-side-down around 30% of the time. This is far enough from 50% to convince the students that it's not a 50/50, yet still big enough to use a normal approximation for the sample proportion without an enormous sample size. More importantly, it is not clear how, if indeed it is possible, we could arrive at the probability from a theoretical perspective. I can think of a few different methods which give slightly different theoretical probabilities. Perhaps in a future post I will share a couple of those methods. For now, the point is: if you want to know the probability for sure, the best way is an empirical approach. The process is clearly random, but without the symmetry which is so common in toy problems. It also has a more visceral feel than, say, a spinner problem. Those always seem a bit constructed, artificial. The candy is something students encounter in the world outside of math class.&lt;/p&gt;
&lt;p&gt;The Hershey rolling activity is great for broaching the topic of empirical probability, and it can segue nicely into inferential statistics. Natural questions arise -- how many times would we have to roll it to find the probability to the nearest percent? Does our sample show one theoretical model is better than another? Does the rolling technique or surface affect the probabilities? This is the kind of richness I want for every in-class activity. I would like to use a similar approach for teaching random processes with more than two outcomes.&lt;/p&gt;
&lt;h1&gt;Enter the Pigs&lt;/h1&gt;
&lt;p&gt;Pass the Pigs is a game based on the classic press-your-luck dice game, Pig. The twist is that, instead of regular 6-sided dice, it uses dice shaped like pigs. There are six orientations a pig can land, so it is still in some way a six-sided die. From the shape of the pigs, it is obvious that some sides will fall more easily than others. There is no symmetry in the pig -- not even bilateral symmetry. And perhaps best of all, &lt;a href="https://mathnificent.com/product/109750/Piglet%2C-Probability-%287-way%29/"&gt;the pigs are available in bulk from Math 'n' Stuff&lt;/a&gt;. My college ordered a class set of 100 to use.&lt;/p&gt;
&lt;p&gt;I am not the first math teacher to use these dice in this way. Back in the late 1990s and early 2000s, &lt;a href="https://deanbal.net/"&gt;Dean Ballard&lt;/a&gt; was an instructor at Lakeside School in Seattle. He put his student to rolling pigs in search of an empirical probability, and &lt;a href="https://web.archive.org/web/20240423104957/https://passpigs.tripod.com/prob.html"&gt;his rolls were published online&lt;/a&gt;. This gives us a baseline for what to expect. Incidentally, that website says it was written by Freddie W. Could this be the same &lt;a href="https://en.wikipedia.org/wiki/Freddie_Wong"&gt;Freddie Wong&lt;/a&gt; who is now an internet celebrity? He attended Lakeside School at around the right time! Ballard's numbers are cited in an interesting paper from 2004, &lt;a href="https://cupola.gettysburg.edu/csfac/4/"&gt;Optimal Play of the Dice Game Pig.&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;I wanted to give my students some practice with Chi Squared tests, so I set them to rolling pigs to get some new data. The first task was to decide as a class on a sample size. Using the estimate that Leaning Jowlers show up 1% of the time, we conclude that we need 500 rolls all together to be able to apply the goodness-of-fit test. Too many for one person, but with the whole class working together each pig only needs to be rolled and counted 5 times.&lt;/p&gt;
&lt;p&gt;I recommend dividing students into groups of 2-4 students to do the rolling. It is much more engaging when you can count together, and it produces better discussions about how to keep track of the work. I project a &lt;a href="https://aldenbradford.com/qr-code.pdf"&gt;QR code&lt;/a&gt; to &lt;a href="https://forms.gle/XootMHtgraXygGC47"&gt;a survey&lt;/a&gt; where each group can upload their totals. Then it is a simple matter to copy the results from &lt;a href="https://docs.google.com/spreadsheets/d/15gnhBVvOTXxyt0DorLQTxRZCdNE1wwdlHSrpTwKDtTM/"&gt;an online spreadsheet&lt;/a&gt;, which students can also get to with &lt;a href="https://aldenbradford.com/qr-code-results.pdf"&gt;a QR code&lt;/a&gt;. If you teach this lesson, you could build your own form, or use the same one. By sharing the same spreadsheet we can have a pretty big sample size.&lt;/p&gt;
&lt;p&gt;I was surprised to find my students got significantly different results from Ballard. They found around 2% each of the snouters and leaning jowlers, about 30% razorbacks, and only 6% trotters. I can't explain why there is such a difference, but it does seem to be real.&lt;/p&gt;
&lt;h1&gt;Additional exploration&lt;/h1&gt;
&lt;p&gt;Why the discrepancy? It could be that the razorbacks were preferred because I had my students rolling several pigs at once. Rolling next to other pigs may have caused them to naturally align. Could the results be different if we rolled fewer at a time? What if we drop them from a greater height? What if we roll them out of a dice cup, rather than from the hand? This activity has some room for exploration, at a level which is accessible to first-time statistics students. I hope to use it for more inquiry-based learning in the future. For now though, it is a refreshing way to get hands-on in what would otherwise be a relatively theory-heavy part of the quarter.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="Statistics"></category><category term="Probability"></category><category term="Teaching"></category></entry><entry><title>We should all use OpenIntro Statistics</title><link href="https://aldenbradford.com/we-should-all-use-openintro-statistics.html" rel="alternate"></link><published>2025-05-14T00:00:00-04:00</published><updated>2025-05-14T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2025-05-14:/we-should-all-use-openintro-statistics.html</id><summary type="html">&lt;p&gt;My argument for switching textbooks&lt;/p&gt;</summary><content type="html">&lt;h1&gt;Background&lt;/h1&gt;
&lt;p&gt;In Washington State, we teach a unified statistics curriculum across all the community colleges. The course is called Math&amp;amp;146, and our rule for what goes into it is the American Statistical Association's &lt;a href="https://www.amstat.org/education/guidelines-for-assessment-and-instruction-in-statistics-education-(gaise)-reports"&gt;Guidelines for Assessment and Instruction in Statistics Education&lt;/a&gt; (GAISE) College Report. Currently, most instructors at Olympic College use the OER textbook &lt;a href="https://openstax.org/details/books/introductory-statistics-2e"&gt;Introductory Statistics&lt;/a&gt; published by OpenStax. I believe we should transition away from the OpenStax textbook, and instead adopt a different OER textbook, published by a different group with a similar name: &lt;a href="https://openintro.org/book/os/"&gt;OpenIntro&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;Introductory Statistics, published by OpenStax, has two primary authors and eight contributing authors. The primary authors were professors at De Anza College, a community college in Cupertino California. They are both now retired. One of them has a master's degree in statistics and a PhD in education, and the other has a master's degree in applied mathematics. Out of the eight other contributing authors, one has a master's degree in statistics. The rest have degrees in mathematics or related disciplines.&lt;/p&gt;
&lt;!---
Barbara Illowsky: MS Statistics, Wharton at University of Pennsylvania. PhD Education, Capella University.
Susan Dean: MS Applied Mathematics, Santa Clara University.
http://nebula2.deanza.edu/~kathyplum/story.html

Daniel Birmajer: PhD mathematics
Bryan Blount: MA education
Sheri Boyd: PhD mathematics
Matthew Einsohn: MA Environmental Studies
James Helmreich: BA Bowdoin College (Mathematics, Economics) MA University of Maryland (Mathematical Logic) PhD University of Maryland (Mathematical Logic) MS SUNY at Albany (Statistics)
Lynette Kenyon: math professor
Sheldon Lee: PhD mathematics
Jeffrey Taub: MS logistics
--&gt;

&lt;p&gt;OpenIntro Statistics was created by three PhD students at UCLA, each of whom completed their PhD in statistics. Two of them later did postdocs in statistics, one of them is now a statistics professor at Duke University. It is currently in its fourth edition, with a fifth on the way. This is a mature textbook, the first edition being published in 2011. Also, unlike the OpenStax book, OpenIntro has the approval of the American Institute of Mathematics &lt;a href="https://textbooks.aimath.org/"&gt;Open Textbook Initiative&lt;/a&gt;.&lt;/p&gt;
&lt;!---
Mine Çetinkaya-Rundel: MS and PhD in Stats, UCLA. Prof. at Duke University in Stats.
David Diez: PhD in Stats, UCLA. Postdoc at Harvard. Works at YouTube.
Chris Barr: PhD in Stats, UCLA. Postdoc at Johns Hopkins.
--&gt;

&lt;p&gt;I have a few different reasons I think we should switch, and it amounts to more than just a matter of personal preference. On a couple key topics, the exposition given in OpenStax is arguably incorrect from a statistical point of view. More broadly, OpenIntro has better alignment with the standards laid out in GAISE, in ways I will explain below. I think this difference can mostly be attributed to the different backgrounds of the authors. The OpenStax book was written by mostly mathematicians, while the OpenIntro book was written entirely by statisticians. Particularly since we have more mathematical expertise than statistical expertise among the faculty here at Olympic College, it would be better for us to use the book with a stronger statistical bent.&lt;/p&gt;
&lt;h1&gt;Necessary improvements&lt;/h1&gt;
&lt;p&gt;We could argue about preferences indefinitely and never reach a conclusion. There are lots of stylistic, pacing, and presentation choices which matter a lot for textbooks. There is no one best way to write a book. That's why I want to start with a couple of things which are not just preferences. There are two major areas where OpenIntro Statistics is right, and OpenStax is wrong. Even if you do not switch to OpenIntro, you should be aware of these problems so as to limit the spread of false statistics information.&lt;/p&gt;
&lt;h2&gt;Retrospective One-Tailed Tests&lt;/h2&gt;
&lt;p&gt;There is a fundamental issue with how OpenStax structures the hypothesis testing chapter. Consider the &lt;a href="https://openstax.org/books/introductory-statistics-2e/pages/9-5-additional-information-and-full-hypothesis-test-examples"&gt;Try It example 9.19 from the OpenStax text&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A car soap gets rid of 30% of stains on the car. After adding a new compound to the soap, the soap is used on a car and found to wash 20 stains out of the 50 stains on the car. With the level of significance being 10%, find out if adding the new compound to soap is beneficial.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;We know that 40% of stains in the sample were removed, we want to know if that difference is statistically significant. I will set aside concerns about whether the stains are really independent events. On problems like this, the OpenStax text instructs you to do a one-tailed test. Using the null hypothesis value of &lt;span class="math"&gt;\(p=30\%\)&lt;/span&gt; we would expect to see samples where at least 20 stains are removed about 8.5% of the time. That's less than our rejection threshold of &lt;span class="math"&gt;\(\alpha=10\%\)&lt;/span&gt;, so we reject the null hypothesis and conclude the new soap is beneficial.&lt;/p&gt;
&lt;p&gt;That's the solution OpenStax expects. That solution is also wrong.&lt;/p&gt;
&lt;p&gt;To understand the error, let's see what would have happened if instead of 20 stains, the soap managed to get rid of 10 stains. If this had happened, we would have asked: with a significance level of 10%, is the new compound detrimental? Again, using &lt;span class="math"&gt;\(p=30\%\)&lt;/span&gt;, we would expect to see samples where at most 10 stains are removed about 7.9% of the time.  That's less than the threshold of &lt;span class="math"&gt;\(\alpha=10\%\)&lt;/span&gt;, so we reject the null hypothesis and conclude the new soap is detrimental.&lt;/p&gt;
&lt;p&gt;I hope the error is becoming clearer. The problem has us inspecting the data before we decide what test to use. Following the OpenStax procedure, we end up rejecting the null hypothesis &lt;span class="math"&gt;\(8.5\%+7.9\%=16.4\%\)&lt;/span&gt; of the time, assuming the null hypothesis is true. That should not be possible with the significance level chosen. &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; is meant to be the probability of rejecting the null hypothesis assuming the null hypothesis is correct. We should have done a 2-tailed test, and we should not have rejected the null hypothesis at all.&lt;/p&gt;
&lt;p&gt;Some of the problems in OpenStax are more carefully worded, and make it clear that the researcher picked the test to use before inspecting the data. Not all of the questions are so careful. Any time we inspect the data before deciding on which direction of test to use, we must do a 2-tailed test. Otherwise, we end up doubling our &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; value by mistake.&lt;/p&gt;
&lt;p&gt;For this reason, the OpenIntro book defers one-tailed tests to a special topic. Most of the book avoids one-tailed tests.&lt;/p&gt;
&lt;p&gt;It is true that there are cases where a one-tailed test is appropriate. For example, when you try a new treatment against the null hypothesis of "this does no better than the best currently established treatment", it makes perfect sense to do a one-tailed test. You will get a little more statistical power that way. However, for almost every case our students are likely to encounter, it is safer to just do a 2-tailed test every time. Some disciplines have banned one-tailed tests entirely, because they are so often misapplied. When I teach Math&amp;amp;146, I do not cover one-tailed tests. The consequence is that the rules for students to learn are simpler, and the students' results are more accurate. That's a win!&lt;/p&gt;
&lt;p&gt;For a nuanced discussion of this issue, see &lt;a href="https://biology.sdsu.edu/pub/stuart/2009MisprescriptionOneTailed.pdf"&gt;Misprescription and misuse of one-tailed tests (2009)&lt;/a&gt;, &lt;a href="https://besjournals.onlinelibrary.wiley.com/doi/full/10.1111/j.2041-210X.2010.00014.x"&gt;When should we use one-tailed hypothesis testing? (2010)&lt;/a&gt; and &lt;a href="https://pubmed.ncbi.nlm.nih.gov/37917504/"&gt;One-tailed tests: Let's do this (responsibly) (2024)&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;T test or Z test&lt;/h2&gt;
&lt;p&gt;There are several places in statistics where a z-test is always the right test to do. For example, when we are computing a population proportion confidence interval, we are safe to use a z-score whenever &lt;span class="math"&gt;\(np\geq 10\)&lt;/span&gt; and &lt;span class="math"&gt;\(n(1-p)\geq 10\)&lt;/span&gt; because the central limit theorem kicks in very fast for the binomial distribution. Since we use the binomial distribution formula to compute the standard deviation, we don't need the small-sample correction that Student's T distribution provides. One of the improvements of the OpenIntro text is that it starts the inferential statistics section looking only at sample proportions. With only the normal distribution to compare against, students have more energy to pay attention to the hypothesis testing framework -- terms like standard error, point estimate, confidence interval, and p value. Later on, once students are used to the framework, we introduce the t-test.&lt;/p&gt;
&lt;p&gt;When I was first taught statistics, I was taught to use a t-distribution for sample sizes less than 30. For larger samples, we were told to use the z-distribution since the t-distribution converges asymptotically to a standard normal distribution when the sample size is larger. This is the strategy taught by OpenStax. When I was a kid, this made sense -- the textbook only had t-tables for sample sizes up to 30. These days, most people are not looking up values in a table, they are using computer software to find the t-scores. When you have a computer on hand, it does not make sense to deal with a special case for large samples. It is much simpler to just use the t-distribution for anything where you are using the sample variance. The CDF is a hypergeometric function, which is one of the fastest types of special function to compute by machine. The OpenIntro book teaches students to use a t-score regardless of sample size. This method is easier to remember, less prone to error, and gives slightly more accurate results. That's a win!&lt;/p&gt;
&lt;p&gt;As an aside, I will note one other case where OpenStax advises students to use a z-test. That's in the case where the population standard deviation is known, but the population mean is unknown. From a mathematical standpoint, this approach is sound. From a statistical standpoint, it is unfounded. There are almost no cases in practice where you would know the standard deviation of a population but you would not know the mean. This case has some merit as a teaching tool, a stepping stone, emphasizing the reason why the t-test is necessary. I believe this would be a worthwhile special topic in a more advanced probability class, particularly one where students will derive the t-test for themselves. It does not belong in an introductory stats class. OpenIntro does without it.&lt;/p&gt;
&lt;p&gt;&lt;img alt="A comparison of decision trees used by each textbook for hypothesis testing" src="https://aldenbradford.com/decision_tree.svg"&gt;&lt;/p&gt;
&lt;h1&gt;Technology&lt;/h1&gt;
&lt;p&gt;Modern statistics is not done without computers. GAISE exhorts us to&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;-5. Use technology to explore concepts and analyze data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Two of the Goals for Students in Introductory Statistics Courses are&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;-3. Students should be able to produce graphical displays and numerical summaries and interpret what graphs do and do not reveal.&lt;/p&gt;
&lt;p&gt;-8. Students should be able to interpret and draw conclusions from standard output from
statistical software packages.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;OpenIntro and OpenStax are built around different computer tools. My philosophy for choosing a tool is that it should have a low floor and a high ceiling.&lt;/p&gt;
&lt;p&gt;A low floor means that students should be able to get useful results after just one 50-minute lesson using the tool. This precludes a general-purpose programming language like Python. In my experience, it takes at least two lessons in Python before students are comfortable enough with the syntax to be able to import the required libraries and use the relevant functions.&lt;/p&gt;
&lt;p&gt;A high ceiling means that students should be able to continue to use the tool throughout their education and career. The tools that we teach should be powerful enough that students will not have to abandon them when their next course, or next job, comes along.&lt;/p&gt;
&lt;h2&gt;The TI-84&lt;/h2&gt;
&lt;p&gt;Every problem in the OpenStax book can be solved using the TI-84. There are even helpful guides sprinkled throughout, explaining exactly which buttons to press. This tool definitely meets our low floor criterion. Many, possibly most, of our students have used a similar calculator in high school. Students can often get useful results without any new instruction on our part. Unfortunately, the TI-84 does not have a very high ceiling. It is hard to load data onto it. It is not practical to export the graphics it creates. Even as we teach Math&amp;amp;146 now, we often supplement with web applets that do much better than the TI-84 at specific tasks. There was a period in the 80s and 90s when it would have been an acceptable tool for a professional context, but that time has long since passed.&lt;/p&gt;
&lt;p&gt;Why does OpenStax use the TI-84? I believe this can be traced back to Texas Instruments' educational outreach division. In order to maintain their market dominance in the high school education space, TI has produced excellent educational resources to help high school teachers prepare their students for standardized tests, notably the AP Stats exam. The authors of the OpenStax book made an effort to incorporate the TI-84 emulator into their curriculum, which Texas Instruments made free for anyone to use, as a way to promote the use of the calculator. Using those educational resources was an economical way to get technology into the curriculum, helping OpenStax get established quickly.&lt;/p&gt;
&lt;p&gt;I am a fan of Texas Instruments. Their calculators are excellent. They are the makers of my personal favorite calculator, the &lt;a href="https://aldenbradford.com/calculator_emulator/"&gt;TI-30Xa&lt;/a&gt;. I still think the TI-84 is the wrong tool to teach college statistics.&lt;/p&gt;
&lt;h2&gt;Spreadsheets&lt;/h2&gt;
&lt;p&gt;By far the most common statistical tool used in industry is the humble spreadsheet. No matter what our students end up studying, no matter where they work, they will have access to spreadsheet software. Lots of tasks in intro statistics can be handled with a spreadsheet alone: means, medians, quartiles, standard deviations, even some chi squared tests can be done without leaving the spreadsheet. Students don't need much instruction to get underway with a spreadsheet because many of them have used spreadsheets in other contexts. The graphical interface means spreadsheets certainly meet our low floor criterion. Some people make whole careers out of spreadsheet wizardry, so it also has a high ceiling.&lt;/p&gt;
&lt;p&gt;I teach my students to use spreadsheets for data acquisition, data cleaning, and basic summary statistics. I teach them using LibreOffice Calc, which is a free spreadsheet program available on every operating system. The skills are transferable to other spreadsheet programs.&lt;/p&gt;
&lt;p&gt;I don't leave it there. Once students know how to format their data correctly, I proceed quickly to R.&lt;/p&gt;
&lt;h2&gt;R&lt;/h2&gt;
&lt;p&gt;R is a completely free, open-source statistics and data visualization package which has been around since 1993, based closely on its predecessor S which was created at Bell Laboratories in 1976. Unlike Python, it is not meant as a general-purpose programming language. Rather, it is designed specifically to simplify routine statistics tasks. All interaction in R can be done through a command line, and the commands are simple enough that my students rarely have trouble formatting them correctly. Here is an example of an R session a student might use to answer a homework problem.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;c&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;summary&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="n"&gt;Min.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="n"&gt;st&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Qu.&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="n"&gt;Median&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Mean&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3&lt;/span&gt;&lt;span class="n"&gt;rd&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Qu.&lt;/span&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Max.&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;1.000&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;1.750&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;2.500&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;3.875&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;&lt;span class="m"&gt;5.500&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="m"&gt;10.000&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;sd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="m"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="m"&gt;3.226564&lt;/span&gt;
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;boxplot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;&lt;img alt="the boxplot produced by R" src="https://aldenbradford.com/boxplot.png"&gt;&lt;/p&gt;
&lt;p&gt;Unlike many simple applets, R will correctly terminate the whiskers at &lt;span class="math"&gt;\(1.5\times IQR\)&lt;/span&gt; away from the quartiles. This is how it behaves without any additional configuration. R satisfies the low floor criterion: my students are eager to start using it for their homework the day they learn it.&lt;/p&gt;
&lt;p&gt;R also has a very high ceiling. It is a tool used by professional data scientists for exploratory analysis, simulation, and creating publication-quality graphics. You may have seen the popular website FiveThirtyEight. They used R to generate most of their award-winning data visualizations. Just as some people have made a career on being expert in spreadsheets, some people make their living using R.&lt;/p&gt;
&lt;p&gt;The OpenIntro textbook comes with a set of labs which get students solving problems relevant to the textbook chapters, using R. These are meant for a class with a weekly computer lab session supervised by a teaching assistant. I have adapted several of these into homework projects, with associated in-class lessons. The projects are not very complicated, typically they will take less than an hour to complete. They are mostly graded automatically through Canvas. Their purpose is to get students using the statistical tools on some real data. &lt;/p&gt;
&lt;p&gt;GAISE cautions us that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;SAS certification, non-introductory R programming, and other more extensive programming topics belong in subsequent courses.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I do not teach my students about string manipulation, looping, conditionals, function writing, or any object-oriented aspects of R. The most basic parts, essentially using R as a calculator, are more than sufficient for what we need.&lt;/p&gt;
&lt;h1&gt;Using Real Data&lt;/h1&gt;
&lt;p&gt;GAISE is unequivocal: &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Recommendation 3: Integrate real data with a context and a purpose.
Using real data in context is crucial in teaching and learning statistics, both to give students experience with analyzing genuine data and to illustrate the usefulness and fascination of our discipline.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;On this score, OpenIntro does much, much better than OpenStax. OpenIntro is littered with real data sets, complete with a context and a citation. The included lecture slides also include additional data sets not covered in the main book, so students get more exposure to the process of exploring data. Every example in the text comes with a cleaned-up copy of the data, ready to download from teh website or as an R package. This makes it easy to create your own graphics, follow-up exercises, and tests based on the same data used in the book.&lt;/p&gt;
&lt;p&gt;Contrast this with OpenStax. Its data appendix contains only 2 data sets, and their provenance is dubious. Most of the exercises in the book use made-up values. This can be a good thing! A well-chosen example, used to illustrate an important point, doesn't always have to be true. However, we deprive our students when the only examples we give are made-up. It is ubiquitous to use made-up examples in a math class. It should not be so common in a statistics class.&lt;/p&gt;
&lt;p&gt;When I taught from OpenStax, I tried to compensate for this by using data collected with in-class experiments. This worked okay. Now that I am using spreadsheets and R, there are many, many more choices of data available. I recommend &lt;a href="https://vincentarelbundock.github.io/Rdatasets/articles/data.html"&gt;Vincent Arel-Bundock's list&lt;/a&gt; of just about every data set anyone has published for free as part of an R package. Every one of these comes with a description of how the data was collected and suggestions of how it can be analyzed. By picking from these data sets, it is easy to find data with the types of variables I am looking for. Also, in some cases, the students will already have the data on their machine ready to load in R.&lt;/p&gt;
&lt;p&gt;It can be tempting to make up examples for short problems, like making confidence intervals for a population proportion. Since we won't work with them very long, it doesn't make sense to spend a lot of time explaining the context. However, these are also some of the easiest examples to find suitable data for. Our library has a subscription to Harper's magazine, which every month publishes &lt;a href="https://harpers.org/harpers-index/"&gt;Harper's Index&lt;/a&gt;. This is a list of brief, interesting statistics from the news. I regularly peruse Harper's Index for examples to use in class or in quizzes. I find that students are more engaged when we use a number from the news, even if the mathematics is the same.&lt;/p&gt;
&lt;h1&gt;Topic Coverage&lt;/h1&gt;
&lt;p&gt;Most of the topic coverage overlaps between the two books. Here I will point out a few notable differences, as we transition from OpenStax to OpenIntro.&lt;/p&gt;
&lt;h2&gt;Stem-and-leaf plots&lt;/h2&gt;
&lt;p&gt;I love stem-and-leaf plots. They hold a place dear to my heart. I also adore the slide rule and adding machine -- I own more slide rules and adding machines than could ever be practical. Alas, OpenIntro does not cover the stem-and-leaf plot, and I (reluctantly) must agree that it probably does not belong in an intro stats course, at least not any more than a slide rule belongs in an introductory algebra course. It is a neat device, but it is mostly practical when a pencil and paper are the best tools at hand for data visualization. Today, when a histogram is just a few clicks away, there is no need to bother a new student with the rules of the stem and leaf plot. The role of the stem-and-leaf plot is mostly supplanted by the dot plot and the histogram. GAISE cautions us against&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Constructing plots by hand. Data displays are now made by computers. Students need to
know how to read and interpret them. Instead of spending lots of time creating
histograms by hand, use some of that time instead to develop a deeper understanding and
ask more challenging questions about what the plots tell us about the data.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I do still have my students create boxplots by hand, with some technological assistance. I feel this is the best way to ensure they can really interpret the whiskers and the outliers. I don't make them compute medians and quartiles by hand, since that process is prone to error. When students mess up computing the median, it is rarely because of a conceptual misunderstanding.&lt;/p&gt;
&lt;h2&gt;Probability Theory&lt;/h2&gt;
&lt;p&gt;Here is where my review of OpenIntro is more measured. As OpenIntro was written by statisticians, it carries a statistician's understanding of probability theory. The section on random variables leaves much to be desired. For example, the OpenIntro book comes with a mostly-excellent set of slides suitable for lectures. The slides on random variables include the bold claim that&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Random variables do not work like normal algebraic variables: &lt;div class="math"&gt;$$X+X\neq 2X$$&lt;/div&gt;
&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This is nonsense. The whole point of random variables is that they do work just like algebraic variables. What the authors mean here is that, given two i.i.d. variables &lt;span class="math"&gt;\(X_1\)&lt;/span&gt; and &lt;span class="math"&gt;\(X_2\)&lt;/span&gt;, the random variable &lt;span class="math"&gt;\(X_1+X_2\)&lt;/span&gt; will in general not have the same distribution as &lt;span class="math"&gt;\(2X_1\)&lt;/span&gt;. In particular, while &lt;span class="math"&gt;\(Var(X_1+X_2)=Var(X_1)+Var(X_2)=2Var(X_1)\)&lt;/span&gt;, &lt;span class="math"&gt;\(Var(2X_1)=4Var(X_1)\)&lt;/span&gt;. This is a useful point to make, but the way the authors make it is confused and a little clumsy.&lt;/p&gt;
&lt;p&gt;If I were choosing a textbook for an introductory probability course, I would probably go with one of the texts by Sheldon Ross. Alas, I do not think any OER textbook exists which is up to my standards for a probability book. Luckily, in our case, we are not choosing a book for a probability course. GAISE tells us&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The original GAISE report recommended less emphasis on probability in the introductory course and we continue to endorse that recommendation.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;I think the OpenIntro text is well-suited to our course, despite its relatively poor coverage of random variables. I just skip those sections entirely. For comparison, the OpenStax book makes no explicit reference to the algebra of random variables. Also, while the above error does appear in the slides, I have not found any similar error in the text itself. The textbook section on random variables is probably not presented the way I would do it as a probability theorist, but the information presented in the book is correct. There is no danger posed to the curious student who reads outside the required sections.&lt;/p&gt;
&lt;h1&gt;Aesthetics&lt;/h1&gt;
&lt;p&gt;The OpenStax book borrows from many other OER textbooks, dating back to at least 2007. There is a mix of many different graphical styles, citation styles, and problem formats. Personally, I feel the book has a disjointed tone. The sections don't flow together, making the book hard to read. I think this is part of why so few of our students read the book. Also, the paperback version is laid out based on the online version of the text, with unnecessarily big margins leading to a bulky book.&lt;/p&gt;
&lt;p&gt;The OpenIntro book has a much more consistent tone and flow, having been created and maintained by relatively few authors. They make available the configuration files needed to create your own figures in the same style as the book, if you wish. The end-of-chapter problems are of about the same length across chapters. The problems also reference previous problems and examples from the text, which gives a sense of planning and cohesion to the narrative.&lt;/p&gt;
&lt;p&gt;Aesthetic considerations naturally take a back seat compared to concerns about accuracy, accessibility, and content coverage. Aesthetics still matters, because it affects how much and how well students will use the textbook. From my experience so far, I believe that my students are using the OpenIntro textbook more, compared to how my students used the OpenStax textbook in the past.&lt;/p&gt;
&lt;h1&gt;Recommendation&lt;/h1&gt;
&lt;p&gt;Over the course of, say, the next two years, we should transition away from OpenStax for statistics. I do like many other OpenStax books, but for Statistics, OpenIntro is better. By transitioning we will be teaching our students more accurate information, we will  have better alignment with GAISE standards, and our student will have a better reading experience.&lt;/p&gt;
&lt;p&gt;I don't expect that we will change textbooks over night. It will take time for professors to learn the new order of the topics, prepare new lesson plans, and prepare new assessment materials. We should be compensated for that time. The number one request I hear from my students in this course is to have more homework. Many of the online problems we use in the OpenStax course could be adapted to the newer textbook. There are many problems in the printed OpenIntro textbook that don't have online randomized versions yet. This is an area where I would welcome collaboration with colleagues more experienced in online homework creation.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="OER"></category><category term="Statistics"></category><category term="Teaching"></category></entry><entry><title>An Even More Straightforward Proof of Descartes’s Circle Theorem</title><link href="https://aldenbradford.com/an-even-more-straightforward-proof-of-descartess-circle-theorem.html" rel="alternate"></link><published>2025-02-05T00:00:00-05:00</published><updated>2025-02-05T00:00:00-05:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2025-02-05:/an-even-more-straightforward-proof-of-descartess-circle-theorem.html</id><summary type="html">&lt;p&gt;A full upload of my paper on Descartes's circle theorem&lt;/p&gt;</summary><content type="html">&lt;p&gt;I was just thinking about this old paper of mine, and I realized that the embargo period has long since ended. That means I can share it with you, for free! &lt;a href="https://aldenbradford.com/CircleTheorem.pdf"&gt;Here it is!&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;The publisher has asked me to share the following with you:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This version of the article has been accepted for publication, after peer review (when applicable) but is not the Version of Record and does not reflect post-acceptance improvements, or any corrections. The Version of Record is available online at: &lt;a href="http://dx.doi.org/10.1007/s00283-022-10234-6"&gt;http://dx.doi.org/10.1007/s00283-022-10234-6&lt;/a&gt;. Use of this Accepted Version is subject to the publisher’s Accepted Manuscript terms of use &lt;a href="https://www.springernature.com/gp/open-research/policies/accepted-manuscript-terms"&gt;https://www.springernature.com/gp/open-research/policies/accepted-manuscript-terms&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;</content><category term="Blog"></category><category term="Descartes Circle Theorem"></category><category term="Metric Geometry"></category></entry><entry><title>Solution to a puzzle on the incircle of a triangle</title><link href="https://aldenbradford.com/solution-to-a-puzzle-on-the-incircle-of-a-triangle.html" rel="alternate"></link><published>2023-04-09T00:00:00-04:00</published><updated>2023-04-09T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2023-04-09:/solution-to-a-puzzle-on-the-incircle-of-a-triangle.html</id><summary type="html">&lt;p&gt;Solution to a geometry puzzle&lt;/p&gt;</summary><content type="html">&lt;p&gt;Some times I hang around the &lt;a href="https://www.reddit.com/r/MathHelp/"&gt;math help subreddit&lt;/a&gt;. 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 &lt;a href="https://www.reddit.com/r/MathHelp/comments/12cgipu/geometry_question/"&gt;where I found&lt;/a&gt; the following puzzle.&lt;/p&gt;
&lt;h1&gt;The puzzle&lt;/h1&gt;
&lt;p&gt;Begin with a general triangle &lt;span class="math"&gt;\(\triangle ABC\)&lt;/span&gt;. Construct the incenter &lt;span class="math"&gt;\(I\)&lt;/span&gt;, altitude &lt;span class="math"&gt;\(AH\)&lt;/span&gt;, and &lt;span class="math"&gt;\(M\)&lt;/span&gt; the midpoint of &lt;span class="math"&gt;\(BC\)&lt;/span&gt;. Define &lt;span class="math"&gt;\(K\)&lt;/span&gt; to be the intersection of the lines &lt;span class="math"&gt;\(AH\)&lt;/span&gt; and &lt;span class="math"&gt;\(MI\)&lt;/span&gt;. Show that &lt;span class="math"&gt;\(\overline{AK}\)&lt;/span&gt; is equal to the inradius &lt;span class="math"&gt;\(r\)&lt;/span&gt; of the triangle.&lt;/p&gt;
&lt;iframe src="https://www.geogebra.org/calculator/sfdk3mqv?embed" width="600" height="400" allowfullscreen style="border: 1px solid #e4e4e4;border-radius: 4px;" frameborder="0"&gt;&lt;/iframe&gt;

&lt;p&gt;Just for convenience, let's also define &lt;span class="math"&gt;\(D\)&lt;/span&gt; to be the point on &lt;span class="math"&gt;\(BC\)&lt;/span&gt; which touches the incircle, so that &lt;span class="math"&gt;\(\overline{ID}=r\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;The solution strategy&lt;/h1&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(\triangle ABC\)&lt;/span&gt; in two different ways. First, using the given altitude, the area must be &lt;span class="math"&gt;\(\frac{1}{2}\overline{AH}\overline{BC}\)&lt;/span&gt;. Second, by partitioning &lt;span class="math"&gt;\(\triangle ABC\)&lt;/span&gt; along its angle bisectors (into &lt;span class="math"&gt;\(\triangle ABI\)&lt;/span&gt;, &lt;span class="math"&gt;\(\triangle BCI\)&lt;/span&gt;, and &lt;span class="math"&gt;\(\triangle CAI\)&lt;/span&gt;) we find another formula for the area, &lt;span class="math"&gt;\(\frac{1}{2}r(\overline{AB}+\overline{BC}+\overline{AC})\)&lt;/span&gt;. That is,&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
r(\overline{AB}+\overline{BC}+\overline{AC})=\overline{AH}\overline{BC}.\tag{I}
\end{equation}&lt;/div&gt;
&lt;p&gt;We will use one other fact about the incenter, which may not be immediately obvious.&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\overline{AB}+\overline{CD} = \overline{AC}+\overline{DB}.\tag{II}
\end{equation}&lt;/div&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(r\)&lt;/span&gt;. Equation &lt;span class="math"&gt;\(\text{(II)}\)&lt;/span&gt; simply follows from grouping those triangles and considering their bases.&lt;/p&gt;
&lt;iframe src="https://www.geogebra.org/calculator/axrqzdyb?embed" width="600" height="400" allowfullscreen style="border: 1px solid #e4e4e4;border-radius: 4px;" frameborder="0"&gt;&lt;/iframe&gt;

&lt;p&gt;Besides that, we will only use Pythagoras, 
&lt;/p&gt;
&lt;div class="math"&gt;\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}&lt;/div&gt;
&lt;p&gt;
the definition of midpoint,
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
\overline{CM}=\overline{BM},\tag{IV}
\end{equation}&lt;/div&gt;
&lt;p&gt;
and similar triangles,
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
r\overline{MH}=\overline{KH}\overline{MD}.\tag{V}
\end{equation}&lt;/div&gt;
&lt;p&gt;If you would like to try it for yourself, have a go at manipulating the above formulas to get the answer. Otherwise read on.&lt;/p&gt;
&lt;h1&gt;The arithmetic&lt;/h1&gt;
&lt;p&gt;Depending on the relative positions of &lt;span class="math"&gt;\(A\)&lt;/span&gt;, &lt;span class="math"&gt;\(B\)&lt;/span&gt;, and &lt;span class="math"&gt;\(C\)&lt;/span&gt;, the lengths will add up differently. To make it simpler to follow, we will assume that (as in the diagram above) &lt;span class="math"&gt;\(H\)&lt;/span&gt; lies between &lt;span class="math"&gt;\(M\)&lt;/span&gt; and &lt;span class="math"&gt;\(B\)&lt;/span&gt;. The strategy will work in any case, but this will make a world of difference for readability.&lt;/p&gt;
&lt;p&gt;Let's begin at &lt;span class="math"&gt;\(\text{(III)}\)&lt;/span&gt;, subtracting the two equations to cancel the &lt;span class="math"&gt;\(\overline{AH}\)&lt;/span&gt; term.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\overline{AC}^2-\overline{AB}^2
&amp;amp;=\overline{CH}^2-\overline{HB}^2\\
&amp;amp;=(\overline{CH}+\overline{HB})(\overline{CH}-\overline{HB})\\
&amp;amp;=\overline{BC}(\overline{CM}+\overline{MH}-\overline{HB})\\
&amp;amp;=\overline{BC}(\overline{BM}+\overline{MH}-\overline{HB})\\
&amp;amp;=2\overline{BC}\overline{MH}.\tag{VI}
\end{align*}&lt;/div&gt;
&lt;p&gt;Inspired by the difference-of-squares formula, let's manipulate &lt;span class="math"&gt;\(\text{(II)}\)&lt;/span&gt; similarly.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\overline{AC}-\overline{AB}
&amp;amp;= \overline{CD}-\overline{DB}\\
&amp;amp;= \overline{CM}+\overline{MD} -\overline{DB}\\
&amp;amp;= \overline{BM}+\overline{MD} -\overline{DB}\\
&amp;amp;= 2\overline{MD}.\tag{VII}
\end{align*}&lt;/div&gt;
&lt;p&gt;Do the same with &lt;span class="math"&gt;\(\text(I)\)&lt;/span&gt;,
&lt;/p&gt;
&lt;div class="math"&gt;\begin{equation}
r(\overline{AC}+\overline{AB})=\overline{BC}(\overline{AH}-r).\tag{VIII}
\end{equation}&lt;/div&gt;
&lt;p&gt;Difference of squares takes us home, and &lt;span class="math"&gt;\(\text{(V)}\)&lt;/span&gt; lays the problem to rest.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
r(\overline{AC}^2-\overline{AB}^2) &amp;amp;= r(\overline{AC}+\overline{AB})(\overline{AC}-\overline{AB})\\
2r\overline{BC}\overline{MH} &amp;amp;= \overline{BC}(\overline{AH}-r)2\overline{MD}\\
r\overline{MH} &amp;amp;= (\overline{AH}-r)\overline{MD}\\
\overline{KH}\overline{MD} &amp;amp;= (\overline{AH}-r)\overline{MD}\\
r&amp;amp;=\overline{AH}-\overline{KH}\\
&amp;amp;=\overline{AK}.
\end{align*}&lt;/div&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;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, &lt;span class="math"&gt;\(H\)&lt;/span&gt; could lie to the left of &lt;span class="math"&gt;\(C\)&lt;/span&gt;, between &lt;span class="math"&gt;\(C\)&lt;/span&gt; and &lt;span class="math"&gt;\(M\)&lt;/span&gt;, between &lt;span class="math"&gt;\(M\)&lt;/span&gt; and &lt;span class="math"&gt;\(B\)&lt;/span&gt;, or to the right of &lt;span class="math"&gt;\(B\)&lt;/span&gt;. 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.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="Geometry"></category><category term="Puzzles"></category></entry><entry><title>Orrell's Quick Wall Paper Calculator</title><link href="https://aldenbradford.com/oqwpc.html" rel="alternate"></link><published>2022-12-05T00:00:00-05:00</published><updated>2022-12-05T00:00:00-05:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-12-05:/oqwpc.html</id><summary type="html">&lt;p&gt;A trade show freebie which is almost a slide rule&lt;/p&gt;</summary><content type="html">&lt;p&gt;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;amp; Company, it serves as a rough area estimation tool for wall paper.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;script type="text/javascript"&gt;
  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[0]; }
      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;
    }
  }
&lt;/script&gt;

&lt;p&gt;&lt;svg xmlns="http://www.w3.org/2000/svg"
     viewBox="0 0 4000 1732"
     onload="makeDraggable(evt)"
     cursor="grab"&gt;
&lt;image x="40" y="10" id="slide" height="1715" width="3325" xlink:href="/front_slide.png" /&gt;
&lt;image xlink:href="/front.png" /&gt;
&lt;/svg&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;a href="https://www.petercollingridge.co.uk/tutorials/svg/interactive/dragging/"&gt;Peter Collingridge&lt;/a&gt; for the dragging code. If you would like the bare images, here is the &lt;a href="https://aldenbradford.com/front.png"&gt;envolope&lt;/a&gt; and the 
&lt;a href="https://aldenbradford.com/front_slide.png"&gt;card&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a table of celing areas" src="https://aldenbradford.com/back.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;On the back of the inserted card is a calendar, hence my claim that this was made in 1923 or 1924.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a 1924 calendar" src="https://aldenbradford.com/back_slide.jpeg"&gt;&lt;/p&gt;
&lt;p&gt;I have not found out much about the company that made this, except for a digitized version of &lt;a href="https://commons.wikimedia.org/wiki/File:Sample_Book,_L.C._Orrell_and_Co.,_Book_No._2,_1906_(CH_18802803-71).jpg"&gt;one of their sample catalogs&lt;/a&gt;.&lt;/p&gt;
&lt;h1&gt;How it works&lt;/h1&gt;
&lt;p&gt;Mostly the mechanism is fairly straightforward. The area of the sides of a room is simply&lt;/p&gt;
&lt;div class="math"&gt;$$
2(\text{length}+\text{width})\times\text{height}.
$$&lt;/div&gt;
&lt;p&gt;Each position of the slide corresponds to one sum, &lt;span class="math"&gt;\(\text{length}+\text{width}\)&lt;/span&gt;. 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.&lt;/p&gt;
&lt;h1&gt;The mystery&lt;/h1&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(14\times 13\times 6 = 1092\)&lt;/span&gt; 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 &lt;span class="math"&gt;\(\binom{13}{2}+13=91\)&lt;/span&gt; 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. &lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;If you have any idea where 800 comes from, let me know. I am stumped!&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="Calculators"></category><category term="Slide rules"></category></entry><entry><title>My proof of Descartes's theorem was just published</title><link href="https://aldenbradford.com/my-proof-of-descartess-theorem-was-just-published.html" rel="alternate"></link><published>2022-11-30T00:00:00-05:00</published><updated>2022-11-30T00:00:00-05:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-11-30:/my-proof-of-descartess-theorem-was-just-published.html</id><summary type="html">&lt;p&gt;Where to find my latest publication&lt;/p&gt;</summary><content type="html">&lt;p&gt;Three months ago, I shared an original proof of Descartes's theorem &lt;a href="https://aldenbradford.com/an-intuitive-proof-of-the-descartes-circle-theorem.html"&gt;here on my blog&lt;/a&gt;. Today it was finally published! It will be part of the december issue of The Mathematical Intelligencer, but &lt;a href="https://doi.org/10.1007/s00283-022-10234-6"&gt;you can already find it online&lt;/a&gt;. 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 &lt;a href="https://arxiv.org/abs/2211.05539"&gt;a preprint on ArXiV&lt;/a&gt; which is almost the same, just with a few more typos.&lt;/p&gt;</content><category term="Blog"></category><category term="Descartes Circle Theorem"></category><category term="Metric Geometry"></category></entry><entry><title>Another little puzzle</title><link href="https://aldenbradford.com/another-little-puzzle.html" rel="alternate"></link><published>2022-11-29T00:00:00-05:00</published><updated>2022-11-29T00:00:00-05:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-11-29:/another-little-puzzle.html</id><summary type="html">&lt;p&gt;A little puzzle involving nesteed circles&lt;/p&gt;</summary><content type="html">&lt;p&gt;Here is another little puzzle. Should be short. Have fun!&lt;/p&gt;
&lt;p&gt;&lt;img alt="the puzzle" src="https://aldenbradford.com/circles.svg"&gt;&lt;/p&gt;</content><category term="Blog"></category><category term="geometry"></category><category term="puzzle"></category></entry><entry><title>A simple geometry puzzle</title><link href="https://aldenbradford.com/a-simple-geometry-puzzle.html" rel="alternate"></link><published>2022-11-15T00:00:00-05:00</published><updated>2022-11-15T00:00:00-05:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-11-15:/a-simple-geometry-puzzle.html</id><summary type="html">&lt;p&gt;A little puzzle involving a circle with inscribed squares&lt;/p&gt;</summary><content type="html">&lt;p&gt;I found a fun puzzle last week, which I would like to share with you. It concerns the following figure.&lt;/p&gt;
&lt;p&gt;&lt;img alt="the puzzle" src="https://aldenbradford.com/baseball.svg"&gt;&lt;/p&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(\sqrt{250}\)&lt;/span&gt;. What, then, are the side lengths of the inscribed squares?&lt;/p&gt;
&lt;h1&gt;Hints&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Hint 1&lt;/h2&gt;
&lt;p&gt;&lt;button onclick="document.getElementById('hint1').toggleAttribute('hidden')"&gt;Show&lt;/button&gt;&lt;/p&gt;
&lt;div id="hint1" hidden="true"&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;Hint 2&lt;/h2&gt;
&lt;p&gt;&lt;button onclick="document.getElementById('hint2').toggleAttribute('hidden')"&gt;Show&lt;/button&gt;&lt;/p&gt;
&lt;div id="hint2" hidden="true"&gt;
&lt;p&gt;The Pythagorean theorem is your friend. Look for ways to make right triangles.&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;Hint 3&lt;/h2&gt;
&lt;p&gt;&lt;button onclick="document.getElementById('hint3').toggleAttribute('hidden')"&gt;Show&lt;/button&gt;&lt;/p&gt;
&lt;div id="hint3" hidden="true"&gt;
&lt;p&gt;&lt;img alt="the first graphical hint" src="/baseball_hint1.svg"&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;h2&gt;Hint 4&lt;/h2&gt;
&lt;p&gt;&lt;button onclick="document.getElementById('hint4').toggleAttribute('hidden')"&gt;Show&lt;/button&gt;&lt;/p&gt;
&lt;div id="hint4" hidden="true"&gt;
&lt;p&gt;&lt;img alt="the second graphical hint" src="/baseball_hint2.svg"&gt;&lt;/p&gt;
&lt;/div&gt;

&lt;p&gt;&lt;a href="https://aldenbradford.com/baseball_hint1.svg"&gt;&lt;/a&gt;
&lt;a href="https://aldenbradford.com/baseball_hint2.svg"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="geometry"></category><category term="puzzle"></category></entry><entry><title>A Cubic Spline for Animation</title><link href="https://aldenbradford.com/a-cubic-spline-for-animation.html" rel="alternate"></link><published>2022-09-09T00:00:00-04:00</published><updated>2022-09-09T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-09-09:/a-cubic-spline-for-animation.html</id><summary type="html">&lt;p&gt;Developing a simple linear program for animating an LED string&lt;/p&gt;</summary><content type="html">&lt;p&gt;I am designing &lt;a href="https://github.com/AldenMB/addressable_led_patterns"&gt;animations for a string of addressable LEDs&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;The setting&lt;/h1&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\([0, 3]\)&lt;/span&gt;. For symmetry, let's describe time in the same way, as an interval &lt;span class="math"&gt;\([0, 3]\)&lt;/span&gt;. For reasons which will become clear, we will focus on the time subinterval &lt;span class="math"&gt;\([1, 2]\)&lt;/span&gt;. We will put the key frames at &lt;span class="math"&gt;\(t=0\)&lt;/span&gt;, &lt;span class="math"&gt;\(t=1\)&lt;/span&gt;, &lt;span class="math"&gt;\(t=2\)&lt;/span&gt;, and &lt;span class="math"&gt;\(t=3\)&lt;/span&gt;.  We will specify each key frame by the values at &lt;span class="math"&gt;\(s=0\)&lt;/span&gt;, &lt;span class="math"&gt;\(s=1\)&lt;/span&gt;, &lt;span class="math"&gt;\(s=2\)&lt;/span&gt;, and &lt;span class="math"&gt;\(s=3\)&lt;/span&gt;. That is, we begin with a function defined on &lt;span class="math"&gt;\(\{0, 1, 2, 3\}\times\{0, 1, 2, 3\}\)&lt;/span&gt;, and we wish to extend it to a function on &lt;span class="math"&gt;\([1, 2]\times[0, 3]\)&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;Why use this specific number of key values? I want to make a cubic polynomial, which we can write as &lt;span class="math"&gt;\(f(t, s) = \sum_{i, j=0}^3 A_{ij}t^i s^j\)&lt;/span&gt;. 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(f\)&lt;/span&gt; by defining &lt;span class="math"&gt;\(f(t, s) = g(t-1, s)\)&lt;/span&gt; for &lt;span class="math"&gt;\(t\in\{1\}\cup[2,3]\)&lt;/span&gt;. In this way, &lt;span class="math"&gt;\(g\)&lt;/span&gt; has the same form as &lt;span class="math"&gt;\(f\)&lt;/span&gt;, with values of &lt;span class="math"&gt;\(g(t)\)&lt;/span&gt; specified for &lt;span class="math"&gt;\(t\in \{0, 1, 2\}\)&lt;/span&gt;. By dropping the values of &lt;span class="math"&gt;\(f\)&lt;/span&gt; at &lt;span class="math"&gt;\(t=0\)&lt;/span&gt; and making new values for &lt;span class="math"&gt;\(g\)&lt;/span&gt; at &lt;span class="math"&gt;\(t=3\)&lt;/span&gt;, the program repeats with &lt;span class="math"&gt;\(g\)&lt;/span&gt; taking the role of &lt;span class="math"&gt;\(f\)&lt;/span&gt;.&lt;/p&gt;
&lt;h1&gt;The clever bit&lt;/h1&gt;
&lt;p&gt;When we restrict &lt;span class="math"&gt;\(f\)&lt;/span&gt; to &lt;span class="math"&gt;\(t=2\)&lt;/span&gt;, we get a polynomial in &lt;span class="math"&gt;\(s\)&lt;/span&gt;. Since it interpolates four values at &lt;span class="math"&gt;\(s=0, 1, 2, 3\)&lt;/span&gt;, it is completely specified. Since &lt;span class="math"&gt;\(g\)&lt;/span&gt; restricted to &lt;span class="math"&gt;\(t=1\)&lt;/span&gt; is also a cubic interpolant, we can see that &lt;span class="math"&gt;\(f\)&lt;/span&gt; is continuous not only at &lt;span class="math"&gt;\(\{2\}\times \{0, 1, 2, 3\}\)&lt;/span&gt; but on all of &lt;span class="math"&gt;\(\{2\}\times [0, 3]\)&lt;/span&gt;. If we are clever about how we specify &lt;span class="math"&gt;\(f\)&lt;/span&gt;, we can do even better. Notice that &lt;span class="math"&gt;\(f_t\)&lt;/span&gt; is also a cubic polynomial in &lt;span class="math"&gt;\(s\)&lt;/span&gt; when restricted to &lt;span class="math"&gt;\(t=2\)&lt;/span&gt;. If we can specify &lt;span class="math"&gt;\(f_t(2, s)=g_t(1, s)\)&lt;/span&gt; then we can make &lt;span class="math"&gt;\(f'\)&lt;/span&gt; continuous as well!&lt;/p&gt;
&lt;p&gt;How to do this? Since &lt;span class="math"&gt;\(f\)&lt;/span&gt; is never directly evaluated at &lt;span class="math"&gt;\(t=3\)&lt;/span&gt;, we will use the funciton values there to instead specify &lt;span class="math"&gt;\(f_t\)&lt;/span&gt; at &lt;span class="math"&gt;\(t=2\)&lt;/span&gt;. That is, we will enforce &lt;span class="math"&gt;\(f_t(2, s) = (1-c) (f(3, s)-f(1, s))/2\)&lt;/span&gt;, where &lt;span class="math"&gt;\(c\)&lt;/span&gt; is a "tension" term allowing us to tweak the behavior of the interpolant. By assigning the same rule for &lt;span class="math"&gt;\(g\)&lt;/span&gt;, that is, &lt;span class="math"&gt;\(g_t(1, s) = (1-c)(f(3, s)-f(1, s))/2\)&lt;/span&gt;, we get a function which is continuous and smooth.&lt;/p&gt;
&lt;h1&gt;Computing it&lt;/h1&gt;
&lt;p&gt;We began by writing &lt;span class="math"&gt;\(f(t, s)=\sum_{i, j=0}^3 A_{ij}t^i s^j\)&lt;/span&gt;. We can write this as a matrix product, 
&lt;/p&gt;
&lt;div class="math"&gt;$$
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}.
$$&lt;/div&gt;
&lt;p&gt;
to keep things tidy, let's define &lt;span class="math"&gt;\(P_x = [1, x, x^2, x^3]^T\)&lt;/span&gt;. Then we can write this as &lt;span class="math"&gt;\(f(t, s)=P_t^T A P_s\)&lt;/span&gt;. The goal then is to specify the matrix &lt;span class="math"&gt;\(A\)&lt;/span&gt;, in terms of the data, the key-frame values of &lt;span class="math"&gt;\(f\)&lt;/span&gt; on &lt;span class="math"&gt;\(\{0, 1, 2, 3\}^2\)&lt;/span&gt;. Write &lt;span class="math"&gt;\(F=[f(i, j)]_{i, j=0}^3\)&lt;/span&gt;, so we are looking for how to write &lt;span class="math"&gt;\(A\)&lt;/span&gt; in terms of &lt;span class="math"&gt;\(F\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;Continuity&lt;/h2&gt;
&lt;p&gt;We use the values at &lt;span class="math"&gt;\(t=1, 2\)&lt;/span&gt; to enforce the continuity of the spline. This is straightforward: we want &lt;span class="math"&gt;\(P_1^TAP_s = F_{1s}\)&lt;/span&gt; for &lt;span class="math"&gt;\(s=0, 1, 2, 3\)&lt;/span&gt;. Writing &lt;span class="math"&gt;\(P_S = [P_0P_1P_2P_3]\)&lt;/span&gt; we can write this more compactly as &lt;span class="math"&gt;\(P_1^TAP_S = F_1\)&lt;/span&gt;. Similarly, we want &lt;span class="math"&gt;\(P_2^TAP_S = F_2\)&lt;/span&gt;. Together, these give &lt;/p&gt;
&lt;div class="math"&gt;$$
[P_1, P_2]^T A P_S = \begin{bmatrix} 0 &amp;amp; 1 &amp;amp; 0 &amp;amp; 0 \\ 0 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0 \end{bmatrix} F.
$$&lt;/div&gt;
&lt;p&gt;Not enough to solve for &lt;span class="math"&gt;\(A\)&lt;/span&gt; yet, but we have not used smoothness yet.&lt;/p&gt;
&lt;h2&gt;Smoothness&lt;/h2&gt;
&lt;p&gt;Differentiation yields&lt;/p&gt;
&lt;div class="math"&gt;$$
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}.
$$&lt;/div&gt;
&lt;p&gt;Write &lt;span class="math"&gt;\(D_x=\frac{d}{dx} P_x\)&lt;/span&gt;, so we can write this as &lt;span class="math"&gt;\(f_t = D_t^T A P_s\)&lt;/span&gt;. We want &lt;span class="math"&gt;\(f(1, s) = \frac{1-c}{2}(f(2, s) - f(0, s))\)&lt;/span&gt;. As a matrix equation, this becomes&lt;/p&gt;
&lt;div class="math"&gt;$$
D_1^T A P_S = \frac{1-c}{2} \begin{bmatrix}-1 &amp;amp; 0 &amp;amp; 1 &amp;amp; 0\end{bmatrix}F.
$$&lt;/div&gt;
&lt;p&gt;Repeating for the &lt;span class="math"&gt;\(t=2\)&lt;/span&gt; case yields&lt;/p&gt;
&lt;div class="math"&gt;$$
[D_1, D_2]^T A P_S = \frac{1-c}{2}\begin{bmatrix}-1&amp;amp;0&amp;amp;1&amp;amp;0\\0&amp;amp;-1&amp;amp;0&amp;amp;1\end{bmatrix}F.
$$&lt;/div&gt;
&lt;p&gt;Writing it this way shows how to combine with the continuity equation,&lt;/p&gt;
&lt;div class="math"&gt;$$
[P_1, P_2, D_1, D_2]^T A P_S = \frac{1}{2}\left(
\begin{bmatrix}
0 &amp;amp; 2 &amp;amp; 0 &amp;amp; 0\\
0 &amp;amp; 0 &amp;amp; 2 &amp;amp; 0\\
-1&amp;amp; 0 &amp;amp; 1 &amp;amp; 0\\
0 &amp;amp;-1 &amp;amp; 0 &amp;amp; 1
\end{bmatrix}
+c\begin{bmatrix}
0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0\\
0 &amp;amp; 0 &amp;amp; 0 &amp;amp; 0\\
1 &amp;amp; 0 &amp;amp;-1 &amp;amp; 0\\
0 &amp;amp; 1 &amp;amp; 0 &amp;amp;-1
\end{bmatrix}
\right)F.
$$&lt;/div&gt;
&lt;h2&gt;Solving for &lt;span class="math"&gt;\(A\)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Now all the matrices are square, so we are in business. We have enough to solve for &lt;span class="math"&gt;\(A\)&lt;/span&gt;. Writing &lt;span class="math"&gt;\(U=[P_1,P_2,D_1,D_2]^T\)&lt;/span&gt; we find
&lt;/p&gt;
&lt;div class="math"&gt;$$
U^{-1}
=
\begin{bmatrix}-4 &amp;amp; 5 &amp;amp; -4 &amp;amp; -2\\12 &amp;amp; -12 &amp;amp; 8 &amp;amp; 5\\-9 &amp;amp; 9 &amp;amp; -5 &amp;amp; -4\\2 &amp;amp; -2 &amp;amp; 1 &amp;amp; 1\end{bmatrix}.
$$&lt;/div&gt;
&lt;p&gt;
We can also compute 
&lt;/p&gt;
&lt;div class="math"&gt;$$
P_S^{-1} = \begin{bmatrix}1 &amp;amp; 1 &amp;amp; 1 &amp;amp; 1\\0 &amp;amp; 1 &amp;amp; 2 &amp;amp; 3\\0 &amp;amp; 1 &amp;amp; 4 &amp;amp; 9\\0 &amp;amp; 1 &amp;amp; 8 &amp;amp; 27\end{bmatrix}^{-1}
=\frac{1}{6} \begin{bmatrix}6 &amp;amp; -11 &amp;amp; 6 &amp;amp; -1\\0 &amp;amp; 18 &amp;amp; -15 &amp;amp; 3\\0 &amp;amp; -9 &amp;amp; 12 &amp;amp; -3\\0 &amp;amp; 2 &amp;amp; -3 &amp;amp; 1\end{bmatrix}.
$$&lt;/div&gt;
&lt;p&gt;
Multiplying on both sides yields&lt;/p&gt;
&lt;div class="math"&gt;$$
A = \frac{1}{12} \left(\begin{bmatrix}4 &amp;amp; -6 &amp;amp; 6 &amp;amp; -2\\-8 &amp;amp; 19 &amp;amp; -16 &amp;amp; 5\\5 &amp;amp; -14 &amp;amp; 13 &amp;amp; -4\\-1 &amp;amp; 3 &amp;amp; -3 &amp;amp; 1\end{bmatrix} + c \begin{bmatrix}-4 &amp;amp; -2 &amp;amp; 4 &amp;amp; 2\\8 &amp;amp; 5 &amp;amp; -8 &amp;amp; -5\\-5 &amp;amp; -4 &amp;amp; 5 &amp;amp; 4\\1 &amp;amp; 1 &amp;amp; -1 &amp;amp; -1\end{bmatrix} \right)
F \begin{bmatrix}-4 &amp;amp; 5 &amp;amp; -4 &amp;amp; -2\\12 &amp;amp; -12 &amp;amp; 8 &amp;amp; 5\\-9 &amp;amp; 9 &amp;amp; -5 &amp;amp; -4\\2 &amp;amp; -2 &amp;amp; 1 &amp;amp; 1\end{bmatrix}.
$$&lt;/div&gt;
&lt;h2&gt;Applying the formula&lt;/h2&gt;
&lt;p&gt;Now that we have a formula for &lt;span class="math"&gt;\(f(t, s)=P_t^TAP_s\)&lt;/span&gt;, we will want to apply it to our light string, which is discrete. Choose some discrete times &lt;span class="math"&gt;\(T = [1, 1+\Delta t, 1+2\Delta t, \dots, 2]\)&lt;/span&gt; and string positions &lt;span class="math"&gt;\(S=[0,\Delta s, 2\Delta s, ..., 3]\)&lt;/span&gt;. Form them into polynomials &lt;span class="math"&gt;\(P_T\)&lt;/span&gt; and &lt;span class="math"&gt;\(P_S\)&lt;/span&gt; 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.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;I won't belabor this any further here. You can see how this algorithm is implemented &lt;a href="https://github.com/AldenMB/addressable_led_patterns"&gt;in the repository I have shared&lt;/a&gt;. 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.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="Interpolation"></category><category term="Neopixels"></category><category term="Animation"></category></entry><entry><title>A Problem in Optimal Transport</title><link href="https://aldenbradford.com/a-problem-in-optimal-transport.html" rel="alternate"></link><published>2022-09-02T00:00:00-04:00</published><updated>2022-09-02T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-09-02:/a-problem-in-optimal-transport.html</id><summary type="html">&lt;p&gt;We show why convex cost functions lead to certain types of solutions to optimal transport problems, using a simple example.&lt;/p&gt;</summary><content type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;In one dimension, the optimal transport problem is sometimes quite easy to solve. So long as the cost function &lt;span class="math"&gt;\(c(x,y)\)&lt;/span&gt; can be written as &lt;span class="math"&gt;\(\phi(|x-y|)\)&lt;/span&gt; where &lt;span class="math"&gt;\(\phi\)&lt;/span&gt; is convex and increasing, then there is an optimal transport plan &lt;span class="math"&gt;\(T\)&lt;/span&gt; and it is monotone, in the sense that if &lt;span class="math"&gt;\(x_1&amp;lt;x_2\)&lt;/span&gt; then &lt;span class="math"&gt;\(T(x_1)\leq T(x_2)\)&lt;/span&gt;. 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 &lt;span class="math"&gt;\(\delta(x_1)+\delta(x_2)\)&lt;/span&gt; and the target is &lt;span class="math"&gt;\(\delta(y_1)+\delta(y_2)\)&lt;/span&gt;. We will take &lt;span class="math"&gt;\(x_1&amp;lt;x_2\)&lt;/span&gt; and &lt;span class="math"&gt;\(y_1&amp;lt;y_2\)&lt;/span&gt;. There are two transport plans to consider.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
T_\parallel&amp;amp;\begin{cases} x_1 \mapsto y_1 \\ x_2\mapsto y_2 \end{cases} \\
T_\perp&amp;amp;\begin{cases} x_1 \mapsto y_2 \\ x_2\mapsto y_1 \end{cases}
\end{align*}&lt;/div&gt;
&lt;p&gt;
In a sense the plan &lt;span class="math"&gt;\(T_\perp\)&lt;/span&gt; crosses itself, while &lt;span class="math"&gt;\(T_\parallel\)&lt;/span&gt; does not. Our claim is that, given &lt;span class="math"&gt;\(\phi\)&lt;/span&gt; is convex and nondecreasing, &lt;span class="math"&gt;\(c(T_\parallel)\leq c(T_\perp)\)&lt;/span&gt;. We will prove this together, paying special attention to where we use each of the conditions on &lt;span class="math"&gt;\(\phi\)&lt;/span&gt;. That is, we will show that
&lt;/p&gt;
&lt;div class="math"&gt;$$
\phi(|y_1-x_1|)+\phi(|y_2-x_2|) \leq \phi(|y_1-x_2|)+\phi(|y_2-x_1|).\tag{$*$}
$$&lt;/div&gt;
&lt;h1&gt;The proof&lt;/h1&gt;
&lt;p&gt;Inequality &lt;span class="math"&gt;\((*)\)&lt;/span&gt; is symmetric in terms of &lt;span class="math"&gt;\(x\)&lt;/span&gt; and &lt;span class="math"&gt;\(y\)&lt;/span&gt;, so we can take &lt;span class="math"&gt;\(x_1&amp;lt;y_1\)&lt;/span&gt; without loss of generality. Let's treat two cases, either &lt;span class="math"&gt;\(x_2&amp;gt;y_2\)&lt;/span&gt; or &lt;span class="math"&gt;\(x_2&amp;lt;y_2\)&lt;/span&gt;.&lt;/p&gt;
&lt;h2&gt;When &lt;span class="math"&gt;\(x_2&amp;gt;y_2\)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Our assumptions together tell us &lt;span class="math"&gt;\(x_1\leq y_1\leq y_2 \leq x_2\)&lt;/span&gt;. Then our goal &lt;span class="math"&gt;\((*)\)&lt;/span&gt; can be written as
&lt;/p&gt;
&lt;div class="math"&gt;$$
\phi(y_1-x_1)+\phi(x_2-y_2) \leq \phi(y_2-x_1)+\phi(x_2-y_1).
$$&lt;/div&gt;
&lt;p&gt;
This case is solved by the nondecreasing assumption on &lt;span class="math"&gt;\(\phi\)&lt;/span&gt;, since &lt;span class="math"&gt;\(\phi(y_1-x_1)\leq \phi(y_2-x_1)\)&lt;/span&gt; and &lt;span class="math"&gt;\(\phi(x_2-y_2)\leq \phi(x_2-y_1)\)&lt;/span&gt;. We do not require convexity at all in this case.&lt;/p&gt;
&lt;h2&gt;When &lt;span class="math"&gt;\(x_2&amp;lt;y_2\)&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;We can rewrite our goal &lt;span class="math"&gt;\((*)\)&lt;/span&gt; as 
&lt;/p&gt;
&lt;div class="math"&gt;$$
\phi(y_1-x_1)+\phi(y_2-x_2) \leq \phi(y_2-x_1) + \phi(|y_1-x_2|).
$$&lt;/div&gt;
&lt;p&gt;
By repeatedly applying &lt;span class="math"&gt;\(x_1&amp;lt;x_2\)&lt;/span&gt; and &lt;span class="math"&gt;\(y_1&amp;lt;y_2\)&lt;/span&gt; we find &lt;span class="math"&gt;\(y_1-x_2\leq y_1-x_1\leq y_2-x_1\)&lt;/span&gt; and &lt;span class="math"&gt;\(y_1-x_2\leq y_2-x_2 \leq y_2-x_1\)&lt;/span&gt;. By convexity, we have
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\phi(y_1-x_1)&amp;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;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*}&lt;/div&gt;
&lt;p&gt;
Adding these yields
&lt;/p&gt;
&lt;div class="math"&gt;$$
\phi(y_1-x_1)+\phi(y_2-x_2) \leq \phi(y_2-x_1) + \phi(y_1-x_2).
$$&lt;/div&gt;
&lt;p&gt;
Finally, the nondecreasing assumption tells us &lt;span class="math"&gt;\(\phi(y_1-x_2)\leq \phi(|y_1-x_2|)\)&lt;/span&gt;.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;The &lt;span class="math"&gt;\(x_2&amp;gt;y_2\)&lt;/span&gt; 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 &lt;span class="math"&gt;\(T_\perp\)&lt;/span&gt; to &lt;span class="math"&gt;\(T_\parallel\)&lt;/span&gt;. In the other case, &lt;span class="math"&gt;\(x_2&amp;lt;y_2\)&lt;/span&gt;, we used convexity because &lt;span class="math"&gt;\(|x_1-T(x_1)|\)&lt;/span&gt; decreased while &lt;span class="math"&gt;\(|x_2-T(x_2)|\)&lt;/span&gt; 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 &lt;span class="math"&gt;\(x_1\)&lt;/span&gt; had farther to go.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="Optimization"></category><category term="Convex Functions"></category></entry><entry><title>An intuitive proof of the Descartes circle theorem</title><link href="https://aldenbradford.com/an-intuitive-proof-of-the-descartes-circle-theorem.html" rel="alternate"></link><published>2022-09-01T00:00:00-04:00</published><updated>2022-09-01T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-09-01:/an-intuitive-proof-of-the-descartes-circle-theorem.html</id><summary type="html">&lt;p&gt;An original, intuitive proof of the Descartes circle theorem.&lt;/p&gt;</summary><content type="html">&lt;p&gt;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
&lt;/p&gt;
&lt;div class="math"&gt;$$
\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),
$$&lt;/div&gt;
&lt;p&gt;
where the radius is taken to be negative if the corresponding circle encloses the others. This convention allows us to say &lt;span class="math"&gt;\(d_{12} = r_1 + r_2\)&lt;/span&gt; where &lt;span class="math"&gt;\(d_{12}\)&lt;/span&gt; is the distance between the centers of circles 1 and 2.&lt;/p&gt;
&lt;p&gt;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 &lt;a href="https://arxiv.org/abs/math/0101066"&gt;discovered in 2001&lt;/a&gt;, which gives a similar relation between the centers of the circles treating them as complex numbers. Kocik's proof is &lt;a href="https://arxiv.org/abs/1910.09174"&gt;available on arXiv&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Heron's formula and the Cayley-Menger determinant&lt;/h1&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(A\)&lt;/span&gt; and side lengths &lt;span class="math"&gt;\(a\)&lt;/span&gt;, &lt;span class="math"&gt;\(b\)&lt;/span&gt;, and &lt;span class="math"&gt;\(c\)&lt;/span&gt;, we have the equation&lt;/p&gt;
&lt;div class="math"&gt;$$
4A^2 = (a^2+b^2+c^2)^2 - 2(a^4+b^4+c^4).
$$&lt;/div&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div class="math"&gt;$$
-16A^2 = 
\begin{vmatrix}
0 &amp;amp; 1 &amp;amp; 1 &amp;amp; 1\\
1 &amp;amp; 0 &amp;amp; c^2 &amp;amp; b^2\\
1 &amp;amp; c^2 &amp;amp; 0 &amp;amp; a^2\\
1 &amp;amp; b^2 &amp;amp; a^2 &amp;amp; 0
\end{vmatrix}.
$$&lt;/div&gt;
&lt;p&gt;For this proof I prefer this form because it generalizes nicely to higher dimensions. This is the &lt;a href="https://en.wikipedia.org/wiki/Cayley%E2%80%93Menger_determinant"&gt;Cayley-Menger determinant&lt;/a&gt; which gives a formula for the volume of an &lt;span class="math"&gt;\(n\)&lt;/span&gt;-dimensional simplex in terms of its edge lengths. For our proof we will use the formula for the volume of the tetrahedron,&lt;/p&gt;
&lt;div class="math"&gt;$$
288V^2= \begin{vmatrix}
0&amp;amp;1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;0&amp;amp;d_{12}^{2}&amp;amp;d_{13}^{2}&amp;amp;d_{14}^{2}\\
1&amp;amp;d_{21}^{2}&amp;amp;0&amp;amp;d_{23}^{2}&amp;amp;d_{24}^{2}\\
1&amp;amp;d_{31}^{2}&amp;amp;d_{32}^{2}&amp;amp;0&amp;amp;d_{34}^{2}\\
1&amp;amp;d_{42}^{2}&amp;amp;d_{42}^{2}&amp;amp;d_{43}^{2}&amp;amp;0\\
\end{vmatrix}.
$$&lt;/div&gt;
&lt;h1&gt;The proof&lt;/h1&gt;
&lt;p&gt;Begin with four pairwise-tangent spheres with radii &lt;span class="math"&gt;\(r_1\)&lt;/span&gt;, &lt;span class="math"&gt;\(r_2\)&lt;/span&gt;, &lt;span class="math"&gt;\(r_3\)&lt;/span&gt;, and &lt;span class="math"&gt;\(r_4\)&lt;/span&gt;. The centers of these spheres define a tetrahedron, and the Cayley-Menger determinant formula gives us the volume of that tetrahedron in terms of &lt;span class="math"&gt;\(d_{ij}=r_i+r_j\)&lt;/span&gt;. After simplification, we are left with the tidy formula&lt;/p&gt;
&lt;div class="math"&gt;$$
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].
$$&lt;/div&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(r_1r_2r_3r_4\)&lt;/span&gt; cannot be zero, so that leaves&lt;/p&gt;
&lt;div class="math"&gt;$$
\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
$$&lt;/div&gt;
&lt;p&gt;and the proof is complete.&lt;/p&gt;
&lt;h1&gt;The details&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Adding (or subtracting) one row or column to another does not change the determinant.&lt;/li&gt;
&lt;li&gt;Multiplying a row or column by a constant scales the determinant by that multiplicative factor.&lt;/li&gt;
&lt;li&gt;When &lt;span class="math"&gt;\(D\)&lt;/span&gt; is invertible, the block determinant &lt;div class="math"&gt;$$\begin{vmatrix}A&amp;amp;B\\ C&amp;amp;D\end{vmatrix}$$&lt;/div&gt; can be computed as &lt;span class="math"&gt;\(\left| D\right | \left| A - B D^{-1} C\right|\)&lt;/span&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
288V^2
&amp;amp;= \begin{vmatrix}
0&amp;amp;1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;0&amp;amp;d_{12}^{2}&amp;amp;d_{13}^{2}&amp;amp;d_{14}^{2}\\
1&amp;amp;d_{21}^{2}&amp;amp;0&amp;amp;d_{23}^{2}&amp;amp;d_{24}^{2}\\
1&amp;amp;d_{31}^{2}&amp;amp;d_{32}^{2}&amp;amp;0&amp;amp;d_{34}^{2}\\
1&amp;amp;d_{42}^{2}&amp;amp;d_{42}^{2}&amp;amp;d_{43}^{2}&amp;amp;0\\
\end{vmatrix}\\
&amp;amp;= \begin{vmatrix}
0&amp;amp;1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;0&amp;amp;(r_1+r_2)^2&amp;amp;(r_1+r_3)^2&amp;amp;(r_1+r_4)^2\\
1&amp;amp;(r_2+r_1)^2&amp;amp;0&amp;amp;(r_2+r_3)^2&amp;amp;(r_2+r_4)^2\\
1&amp;amp;(r_3+r_1)^2&amp;amp;(r_3+r_2)^2&amp;amp;0&amp;amp;(r_3+r_4)^2\\
1&amp;amp;(r_4+r_1)^2&amp;amp;(r_4+r_2)^2&amp;amp;(r_4+r_3)^2&amp;amp;0\\
\end{vmatrix}\\
\frac{288V^2}{(r_1r_2r_3r_4)^4}&amp;amp;= \begin{vmatrix}
0&amp;amp;\frac{1}{r_1^2}&amp;amp;\frac{1}{r_2^2}&amp;amp;\frac{1}{r_3^2}&amp;amp;\frac{1}{r_4^2}\\
\frac{1}{r_1^2}&amp;amp;0&amp;amp;(\frac{1}{r_1}+\frac{1}{r_2})^2&amp;amp;(\frac{1}{r_1}+\frac{1}{r_3})^2&amp;amp;(\frac{1}{r_1}+\frac{1}{r_4})^2\\
\frac{1}{r_2^2}&amp;amp;(\frac{1}{r_2}+\frac{1}{r_1})^2&amp;amp;0&amp;amp;(\frac{1}{r_2}+\frac{1}{r_3})^2&amp;amp;(\frac{1}{r_2}+\frac{1}{r_4})^2\\
\frac{1}{r_3^2}&amp;amp;(\frac{1}{r_3}+\frac{1}{r_1})^2&amp;amp;(\frac{1}{r_3}+\frac{1}{r_2})^2&amp;amp;0&amp;amp;(\frac{1}{r_3}+\frac{1}{r_4})^2\\
\frac{1}{r_4^2}&amp;amp;(\frac{1}{r_4}+\frac{1}{r_1})^2&amp;amp;(\frac{1}{r_4}+\frac{1}{r_2})^2&amp;amp;(\frac{1}{r_4}+\frac{1}{r_3})^2&amp;amp;0\\
\end{vmatrix}\\
&amp;amp;= \begin{vmatrix}
0&amp;amp;\frac{1}{r_1^2}&amp;amp;\frac{1}{r_2^2}&amp;amp;\frac{1}{r_3^2}&amp;amp;\frac{1}{r_4^2}\\
\frac{1}{r_1^2}&amp;amp;0&amp;amp;\frac{1}{r_1^2}+\frac{2}{r_1r_2}+\frac{1}{r_2^2}&amp;amp;\frac{1}{r_1^2}+\frac{2}{r_1r_3}+\frac{1}{r_3^2}&amp;amp;\frac{1}{r_1^2}+\frac{2}{r_1r_4}+\frac{1}{r_4^2}\\
\frac{1}{r_2^2}&amp;amp;\frac{1}{r_2^2}+\frac{2}{r_2r_1}+\frac{1}{r_1^2}&amp;amp;0&amp;amp;\frac{1}{r_2^2}+\frac{2}{r_2r_3}+\frac{1}{r_3^2}&amp;amp;\frac{1}{r_2^2}+\frac{2}{r_2r_4}+\frac{1}{r_4^2}\\
\frac{1}{r_3^2}&amp;amp;\frac{1}{r_3^2}+\frac{2}{r_3r_1}+\frac{1}{r_1^2}&amp;amp;\frac{1}{r_3^2}+\frac{2}{r_3r_2}+\frac{1}{r_2^2}&amp;amp;0&amp;amp;\frac{1}{r_3^2}+\frac{2}{r_3r_4}+\frac{1}{r_4^2}\\
\frac{1}{r_4^2}&amp;amp;\frac{1}{r_4^2}+\frac{2}{r_4r_1}+\frac{1}{r_1^2}&amp;amp;\frac{1}{r_4^2}+\frac{2}{r_4r_2}+\frac{1}{r_2^2}&amp;amp;\frac{1}{r_4^2}+\frac{2}{r_4r_3}+\frac{1}{r_3^2}&amp;amp;0\\
\end{vmatrix}\\
&amp;amp;= \begin{vmatrix}
0&amp;amp;\frac{1}{r_1^2}&amp;amp;\frac{1}{r_2^2}&amp;amp;\frac{1}{r_3^2}&amp;amp;\frac{1}{r_4^2}\\
\frac{1}{r_1^2}&amp;amp;\frac{-2}{r_1^2}&amp;amp;\frac{2}{r_1r_2}&amp;amp;\frac{2}{r_1r_3}&amp;amp;\frac{2}{r_1r_4}\\
\frac{1}{r_2^2}&amp;amp;\frac{2}{r_2r_1}&amp;amp;\frac{-2}{r_2^2}&amp;amp;\frac{2}{r_2r_3}&amp;amp;\frac{2}{r_2r_4}\\
\frac{1}{r_3^2}&amp;amp;\frac{2}{r_3r_1}&amp;amp;\frac{2}{r_3r_2}&amp;amp;\frac{-2}{r_3^2}&amp;amp;\frac{2}{r_3r_4}\\
\frac{1}{r_4^2}&amp;amp;\frac{2}{r_4r_1}&amp;amp;\frac{2}{r_4r_2}&amp;amp;\frac{2}{r_4r_3}&amp;amp;\frac{-2}{r_4^2}\\
\end{vmatrix}\\
\frac{288V^2}{(r_1r_2r_3r_4)^2}&amp;amp;= \begin{vmatrix}
0&amp;amp;\frac{1}{r_1}&amp;amp;\frac{1}{r_2}&amp;amp;\frac{1}{r_3}&amp;amp;\frac{1}{r_4}\\
\frac{1}{r_1}&amp;amp;-2&amp;amp;2&amp;amp;2&amp;amp;2\\
\frac{1}{r_2}&amp;amp;2&amp;amp;-2&amp;amp;2&amp;amp;2\\
\frac{1}{r_3}&amp;amp;2&amp;amp;2&amp;amp;-2&amp;amp;2\\
\frac{1}{r_4}&amp;amp;2&amp;amp;2&amp;amp;2&amp;amp;-2
\end{vmatrix}\\
\frac{288V^2}{8(r_1r_2r_3r_4)^2}&amp;amp;= \begin{vmatrix}
0&amp;amp;\frac{1}{r_1}&amp;amp;\frac{1}{r_2}&amp;amp;\frac{1}{r_3}&amp;amp;\frac{1}{r_4}\\
\frac{1}{r_1}&amp;amp;-1&amp;amp;1&amp;amp;1&amp;amp;1\\
\frac{1}{r_2}&amp;amp;1&amp;amp;-1&amp;amp;1&amp;amp;1\\
\frac{1}{r_3}&amp;amp;1&amp;amp;1&amp;amp;-1&amp;amp;1\\
\frac{1}{r_4}&amp;amp;1&amp;amp;1&amp;amp;1&amp;amp;-1
\end{vmatrix}\\
&amp;amp;=-\begin{vmatrix}
-1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;-1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;-1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;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;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;-1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;-1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;amp;-1
\end{bmatrix}^{-1}
\begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}
\end{align*}&lt;/div&gt;
&lt;p&gt;Notice the matrix 
&lt;/p&gt;
&lt;div class="math"&gt;$$
D=\begin{bmatrix}
-1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;-1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;-1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;amp;-1
\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;
satisfies &lt;span class="math"&gt;\(D^2=4I\)&lt;/span&gt;. Thus, &lt;span class="math"&gt;\(D^{-1} = \frac{1}{4}D\)&lt;/span&gt;. We can also compute &lt;span class="math"&gt;\(|D| = -16\)&lt;/span&gt;. Just a couple more lines and we are done with the algebra.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\frac{288V^2}{32(r_1r_2r_3r_4)^2}
&amp;amp;=
\begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}^T
\begin{bmatrix}
-1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;-1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;-1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;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;amp;=
\begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}^T
\begin{bmatrix}
1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;amp;1\\
1&amp;amp;1&amp;amp;1&amp;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;amp;0&amp;amp;0&amp;amp;0\\
0&amp;amp;1&amp;amp;0&amp;amp;0\\
0&amp;amp;0&amp;amp;1&amp;amp;0\\
0&amp;amp;0&amp;amp;0&amp;amp;1
\end{bmatrix}
\begin{bmatrix} 1/r_1 \\ 1/r_2 \\ 1/r_3 \\ 1/r_4 \end{bmatrix}\\
&amp;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*}&lt;/div&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="Descartes Circle Theorem"></category><category term="Apollo"></category><category term="Metric Geometry"></category></entry><entry><title>Return to Battleship</title><link href="https://aldenbradford.com/battleship.html" rel="alternate"></link><published>2022-08-28T00:00:00-04:00</published><updated>2022-08-28T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-08-28:/battleship.html</id><summary type="html">&lt;p&gt;Reflecting on an old coding project, and what I would do differently now.&lt;/p&gt;</summary><content type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;The tool&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;form id="input_form"&gt;
Rows:&lt;br&gt; 
&lt;input type="number" name="rows" max="99" min="1" value="10"&gt;&lt;br&gt;
Columns:&lt;br&gt;
&lt;input type="number" name="columns" max="52" min="1" value="10"&gt;&lt;br&gt;
Ships:&lt;br&gt;
&lt;textarea name="ships" rows="3" cols="17"&gt;
(C,5),(B,4),(c,3),(S,3),(D,2)
&lt;/textarea&gt;&lt;br&gt;
&lt;input type="reset"&gt;
&lt;button type="button" id="generate" onclick="generateBoard()"&gt;Generate!&lt;/button&gt;
&lt;/form&gt;

&lt;pre id="the_board"&gt;
&lt;/pre&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;script&gt;
"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 &lt; inputList.length; i++){
        let pair = [];
        pair[0] = inputList[i].match(/\w/)[0];
        pair[1] = parseInt(inputList[i].match(/\d+/)[0],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&lt;this.cols;i++){
            this.cells[i]=[];
            for(let j=0;j&lt;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 &lt; this.rows; j++){
            if( j &lt; 9)  string = string.concat(' ');
            string = string.concat(`${j+1}`);
            for(let i = 0; i &lt; 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]-1);
        if(direction === "h") max_col -= (charLenPair[1]-1);
        if(max_row &lt;1 || max_col&lt;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 &lt; 1000000; attempt++){
            this.initializeCells();
            let attemptSuccessful = true;
            for(let i = 0; i&lt;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[0];
        this.length = charLenPair[1];
        this.orientation = orientation;
        this.corner = corner;
        this.board = board;
    }
    cells(){
        let cellList = [];
        if(this.orientation === "h"){
            for(let i = 0; i &lt; this.length; i++){
                cellList[i] = this.board.cells[this.corner[0]+i][this.corner[1]  ];
            }
        }
        if(this.orientation === "v"){
            for(let i = 0; i &lt; this.length; i++){
                cellList[i] = this.board.cells[this.corner[0]  ][this.corner[1]+i];
            }
        }
        return cellList;
    }
    canFit(){
        let cellList = this.cells();
        for(let i = 0; i&lt;this.length; i++){
            if(cellList[i].contains) return false;
        }
        return true;
    }
    insert(){
        let cellList=this.cells();
        for(let i = 0; i&lt;this.length; i++){
            cellList[i].contains = this;
        }   
    }
}

class Cell {
    constructor(){
    this.contains = null;
    }
    appearance(){
        return this.contains ? this.contains.appearance : '~';
    }
}

window.addEventListener('load', (event) =&gt; generateBoard());
&lt;/script&gt;

&lt;h1&gt;The good&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Let's have a look at my old code and see what nice qualities it has, lest we become overcritical.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="s2"&gt;&amp;quot;use strict&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;var&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;theForm&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;input_form&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;generateBoard&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;theForm&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;getElementById&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;the_board&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeShips&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;container&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerHTML&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;I tried 1,000,000 times, but \nI could not make the board work.&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parseShips&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inputList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\w,\d+/g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ships&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inputList&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;inputList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\w/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;parseInt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;inputList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/\d+/&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="mf"&gt;10&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;ships&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;pair&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;ships&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Board&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueAsNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;columns&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;valueAsNumber&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeCells&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shipPairs&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;parseShips&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;ships&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;initializeCells&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="s2"&gt;&amp;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 &amp;quot;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39; &amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="si"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sb"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot; &amp;quot;&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nx"&gt;j&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;concat&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;\n&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;placeShip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="mf"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max_col&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cols&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max_row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;rows&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max_row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max_col&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;-=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;max_row&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;||&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;max_col&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;max_col&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;Math&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;random&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="nx"&gt;max_row&lt;/span&gt;&lt;span class="p"&gt;)];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;newShip&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="ow"&gt;new&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ship&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;newShip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;canFit&lt;/span&gt;&lt;span class="p"&gt;()){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;newShip&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;placeShips&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;1000000&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attempt&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;initializeCells&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;attemptSuccessful&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shipPairs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;placeShip&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;shipPairs&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;])){&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="nx"&gt;attemptSuccessful&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                    &lt;/span&gt;&lt;span class="k"&gt;break&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;attemptSuccessful&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;failed&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Ship&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;orientation&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appearance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;charLenPair&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orientation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;orientation&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[];&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orientation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;h&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;orientation&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;===&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;v&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;                &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;board&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;  &lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;corner&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mf"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;canFit&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;insert&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;cells&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;&lt;span class="p"&gt;){&lt;/span&gt;
&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="nx"&gt;cellList&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;   &lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="kd"&gt;class&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Cell&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="kr"&gt;constructor&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nx"&gt;appearance&lt;/span&gt;&lt;span class="p"&gt;(){&lt;/span&gt;
&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;?&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;contains&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;appearance&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s1"&gt;&amp;#39;~&amp;#39;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;The bad&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href="https://howjavascriptworks.com/"&gt;Douglas Crockford's book&lt;/a&gt;. Essentially, we can avoid ever having to deal with &lt;code&gt;this&lt;/code&gt; and all its oddities by avoiding the &lt;code&gt;class&lt;/code&gt; 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.&lt;/p&gt;
&lt;p&gt;This brings me to a more fundamental problem with the code above. What's that class &lt;code&gt;Cell&lt;/code&gt; supposed to be doing? All it does is store a reference to an object which is either &lt;code&gt;null&lt;/code&gt; 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.&lt;/p&gt;
&lt;h1&gt;The janky&lt;/h1&gt;
&lt;p&gt;I don't wish to pick nits over strange formatting or missing semicolons. I won't harp on the liberal use of &lt;code&gt;let&lt;/code&gt; where &lt;code&gt;const&lt;/code&gt; would be more appropriate. Instead I will conclude by pointing out a few "What was I thinking?" absurdities.&lt;/p&gt;
&lt;p&gt;Why oh why is there something called a &lt;code&gt;charLenPair&lt;/code&gt; floating around? That should plainly just be an object, so that you can refer to the character or length as you see fit.&lt;/p&gt;
&lt;p&gt;Why is there a function called &lt;code&gt;generateBoard&lt;/code&gt; that lives outside the class &lt;code&gt;Board&lt;/code&gt;? 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Why do I left pad the digits in &lt;code&gt;Board.appearance&lt;/code&gt; by checking if they are less than 9? That's much harder to read than &lt;code&gt;String.padStart&lt;/code&gt;, for example. Of course, &lt;code&gt;String.padStart&lt;/code&gt; was only introduced in 2017 after the famous &lt;code&gt;left-pad&lt;/code&gt; scandal, so I may well not have heard of it yet at the time.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="Programming"></category><category term="Javascript"></category></entry><entry><title>Decoding a multiplexed LCD</title><link href="https://aldenbradford.com/decoding-a-multiplexed-lcd.html" rel="alternate"></link><published>2022-08-12T00:00:00-04:00</published><updated>2022-08-12T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-08-12:/decoding-a-multiplexed-lcd.html</id><summary type="html">&lt;p&gt;Figuring out which segments are wired where on the TI-30Xa&lt;/p&gt;</summary><content type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Naming conventions&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a hand-made SVG of the TI-30Xa display" src="https://aldenbradford.com/display.svg" width="600"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;I have put together a simple script which constantly polls the screen and produces the following output.&lt;/p&gt;
&lt;pre&gt;
   33322222222221111111111000000000
   21098765432109876543210987654321
A: ......##....................#...
B: #.....##.....................#..
C: #.....#.......................#.
D: ......##.......................#
&lt;/pre&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;The table&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;32&lt;/th&gt;
&lt;th&gt;31&lt;/th&gt;
&lt;th&gt;30&lt;/th&gt;
&lt;th&gt;29&lt;/th&gt;
&lt;th&gt;28&lt;/th&gt;
&lt;th&gt;27&lt;/th&gt;
&lt;th&gt;26&lt;/th&gt;
&lt;th&gt;25&lt;/th&gt;
&lt;th&gt;24&lt;/th&gt;
&lt;th&gt;23&lt;/th&gt;
&lt;th&gt;22&lt;/th&gt;
&lt;th&gt;21&lt;/th&gt;
&lt;th&gt;20&lt;/th&gt;
&lt;th&gt;19&lt;/th&gt;
&lt;th&gt;18&lt;/th&gt;
&lt;th&gt;17&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;STAT&lt;/td&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;K&lt;/td&gt;
&lt;td&gt;E0D&lt;/td&gt;
&lt;td&gt;()&lt;/td&gt;
&lt;td&gt;E1D&lt;/td&gt;
&lt;td&gt;0DP&lt;/td&gt;
&lt;td&gt;0D&lt;/td&gt;
&lt;td&gt;1DP&lt;/td&gt;
&lt;td&gt;1D&lt;/td&gt;
&lt;td&gt;2DP&lt;/td&gt;
&lt;td&gt;2D&lt;/td&gt;
&lt;td&gt;3DP&lt;/td&gt;
&lt;td&gt;3D&lt;/td&gt;
&lt;td&gt;4DP&lt;/td&gt;
&lt;td&gt;4D&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;DE&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;E0C&lt;/td&gt;
&lt;td&gt;E0E&lt;/td&gt;
&lt;td&gt;E1C&lt;/td&gt;
&lt;td&gt;E1E&lt;/td&gt;
&lt;td&gt;0C&lt;/td&gt;
&lt;td&gt;0E&lt;/td&gt;
&lt;td&gt;1C&lt;/td&gt;
&lt;td&gt;1E&lt;/td&gt;
&lt;td&gt;2C&lt;/td&gt;
&lt;td&gt;2E&lt;/td&gt;
&lt;td&gt;3C&lt;/td&gt;
&lt;td&gt;3E&lt;/td&gt;
&lt;td&gt;4C&lt;/td&gt;
&lt;td&gt;4E&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;RAD&lt;/td&gt;
&lt;td&gt;E0B&lt;/td&gt;
&lt;td&gt;E0G&lt;/td&gt;
&lt;td&gt;E1B&lt;/td&gt;
&lt;td&gt;E1G&lt;/td&gt;
&lt;td&gt;0B&lt;/td&gt;
&lt;td&gt;0G&lt;/td&gt;
&lt;td&gt;1B&lt;/td&gt;
&lt;td&gt;1G&lt;/td&gt;
&lt;td&gt;2B&lt;/td&gt;
&lt;td&gt;2G&lt;/td&gt;
&lt;td&gt;3B&lt;/td&gt;
&lt;td&gt;3G&lt;/td&gt;
&lt;td&gt;4B&lt;/td&gt;
&lt;td&gt;4G&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;FIX&lt;/td&gt;
&lt;td&gt;E2G&lt;/td&gt;
&lt;td&gt;E0A&lt;/td&gt;
&lt;td&gt;E0F&lt;/td&gt;
&lt;td&gt;E1A&lt;/td&gt;
&lt;td&gt;E1F&lt;/td&gt;
&lt;td&gt;0A&lt;/td&gt;
&lt;td&gt;0F&lt;/td&gt;
&lt;td&gt;1A&lt;/td&gt;
&lt;td&gt;1F&lt;/td&gt;
&lt;td&gt;2A&lt;/td&gt;
&lt;td&gt;2F&lt;/td&gt;
&lt;td&gt;3A&lt;/td&gt;
&lt;td&gt;3F&lt;/td&gt;
&lt;td&gt;4A&lt;/td&gt;
&lt;td&gt;4F&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;16&lt;/th&gt;
&lt;th&gt;15&lt;/th&gt;
&lt;th&gt;14&lt;/th&gt;
&lt;th&gt;13&lt;/th&gt;
&lt;th&gt;12&lt;/th&gt;
&lt;th&gt;11&lt;/th&gt;
&lt;th&gt;10&lt;/th&gt;
&lt;th&gt;9&lt;/th&gt;
&lt;th&gt;8&lt;/th&gt;
&lt;th&gt;7&lt;/th&gt;
&lt;th&gt;6&lt;/th&gt;
&lt;th&gt;5&lt;/th&gt;
&lt;th&gt;4&lt;/th&gt;
&lt;th&gt;3&lt;/th&gt;
&lt;th&gt;2&lt;/th&gt;
&lt;th&gt;1&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;5DP&lt;/td&gt;
&lt;td&gt;5D&lt;/td&gt;
&lt;td&gt;6DP&lt;/td&gt;
&lt;td&gt;6D&lt;/td&gt;
&lt;td&gt;7DP&lt;/td&gt;
&lt;td&gt;7D&lt;/td&gt;
&lt;td&gt;8DP&lt;/td&gt;
&lt;td&gt;8D&lt;/td&gt;
&lt;td&gt;9DP&lt;/td&gt;
&lt;td&gt;9D&lt;/td&gt;
&lt;td&gt;M3&lt;/td&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;5C&lt;/td&gt;
&lt;td&gt;5E&lt;/td&gt;
&lt;td&gt;6C&lt;/td&gt;
&lt;td&gt;6E&lt;/td&gt;
&lt;td&gt;7C&lt;/td&gt;
&lt;td&gt;7E&lt;/td&gt;
&lt;td&gt;8C&lt;/td&gt;
&lt;td&gt;8E&lt;/td&gt;
&lt;td&gt;9C&lt;/td&gt;
&lt;td&gt;9E&lt;/td&gt;
&lt;td&gt;10G&lt;/td&gt;
&lt;td&gt;HYP&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;5B&lt;/td&gt;
&lt;td&gt;5G&lt;/td&gt;
&lt;td&gt;6B&lt;/td&gt;
&lt;td&gt;6G&lt;/td&gt;
&lt;td&gt;7B&lt;/td&gt;
&lt;td&gt;7G&lt;/td&gt;
&lt;td&gt;8B&lt;/td&gt;
&lt;td&gt;8G&lt;/td&gt;
&lt;td&gt;9B&lt;/td&gt;
&lt;td&gt;9G&lt;/td&gt;
&lt;td&gt;M2&lt;/td&gt;
&lt;td&gt;ENG&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;5A&lt;/td&gt;
&lt;td&gt;5F&lt;/td&gt;
&lt;td&gt;6A&lt;/td&gt;
&lt;td&gt;6F&lt;/td&gt;
&lt;td&gt;7A&lt;/td&gt;
&lt;td&gt;7F&lt;/td&gt;
&lt;td&gt;8A&lt;/td&gt;
&lt;td&gt;8F&lt;/td&gt;
&lt;td&gt;9A&lt;/td&gt;
&lt;td&gt;9F&lt;/td&gt;
&lt;td&gt;M1&lt;/td&gt;
&lt;td&gt;SCI&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;The backplanes&lt;/h2&gt;
&lt;p&gt;These are free spaces. Bingo!&lt;/p&gt;
&lt;h2&gt;DEGRAD&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Indicators&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Digits&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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&lt;/p&gt;
&lt;h2&gt;Exponent&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Conclusions&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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!&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="Xanthippe"></category><category term="LCD"></category></entry><entry><title>Lessons from the Xanthippe circuit board</title><link href="https://aldenbradford.com/lessons-from-the-xanthippe-circuit-board.html" rel="alternate"></link><published>2022-08-11T00:00:00-04:00</published><updated>2022-08-11T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-08-11:/lessons-from-the-xanthippe-circuit-board.html</id><summary type="html">&lt;p&gt;Regrets and insights brought from the Xanthippe circuit board&lt;/p&gt;</summary><content type="html">&lt;blockquote&gt;
&lt;p&gt;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—!&lt;/p&gt;
&lt;p&gt;-- Friedrich Nietsche, &lt;cite&gt;Beyond Good and Evil&lt;/cite&gt;, section 277, translated by Helen Zimmern&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;It's here! The board I designed has arrived!&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Board size&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Default behavior&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Resistor arrays&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;TO-92 footprints&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Breakouts&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Disconnects&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;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. &lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="Xanthippe"></category><category term="circuit boards"></category></entry><entry><title>Revisiting Apollonian gaskets</title><link href="https://aldenbradford.com/revisiting-apollonian-gaskets.html" rel="alternate"></link><published>2022-08-06T00:00:00-04:00</published><updated>2022-08-06T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-08-06:/revisiting-apollonian-gaskets.html</id><summary type="html">&lt;p&gt;A better way of generating Apollonian gaskets.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Last summer I designed &lt;a href="/Apollo"&gt;Apollo&lt;/a&gt;, 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.&lt;/p&gt;
&lt;h1&gt;Computing bends to start from&lt;/h1&gt;
&lt;h2&gt;No primitive bends&lt;/h2&gt;
&lt;p&gt;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
&lt;/p&gt;
&lt;div class="math"&gt;$$
(w, x, y, z) \mapsto (w, x, y, 2(w+x+y)-z)
$$&lt;/div&gt;
&lt;p&gt;
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.&lt;/p&gt;
&lt;p&gt;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 &lt;span class="math"&gt;\(w=0\)&lt;/span&gt;, and take the other bends to be in a geometric progression &lt;span class="math"&gt;\((x, y, z) = (x, \alpha x, \alpha^2 x)\)&lt;/span&gt;. By Descartes's theorem, &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; must satisfy the equation 
&lt;/p&gt;
&lt;div class="math"&gt;$$
2(1+\alpha^2+\alpha^4) = (1+\alpha+\alpha^2)^2.
$$&lt;/div&gt;
&lt;p&gt;
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.
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
\alpha^4+\alpha^2+1
&amp;amp;= (\alpha^4+2\alpha^2+1)-\alpha^2\\
&amp;amp;= (\alpha^2+1)^2-\alpha^2\\
&amp;amp;= (\alpha^2+\alpha+1)(\alpha^2-\alpha+1)
\end{align*}&lt;/div&gt;
&lt;p&gt;
So, we see a common factor in both sides of the equation. We can factor it out as 
&lt;/p&gt;
&lt;div class="math"&gt;$$
(\alpha^2+\alpha+1)(\alpha^2-3\alpha+1)=0.
$$&lt;/div&gt;
&lt;p&gt;
This has two real roots, the solutions to &lt;span class="math"&gt;\(\alpha^2-3\alpha+1=0\)&lt;/span&gt;. For our purposes I prefer to write this as &lt;span class="math"&gt;\(\alpha+\frac{1}{\alpha} = 3\)&lt;/span&gt;, which emphasizes that the two solutions are reciprocal to one another. Taking &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; to be the larger of these two roots (&lt;span class="math"&gt;\(\alpha=(3+\sqrt{5})/2\)&lt;/span&gt; if you are curious) puts our original bends in increasing order.&lt;/p&gt;
&lt;p&gt;If we apply our mirroring strategy to &lt;span class="math"&gt;\((0, x, \alpha x, \alpha^2 x)\)&lt;/span&gt; we get rid of &lt;span class="math"&gt;\(\alpha^2 x\)&lt;/span&gt; and replace it with 
&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
2(x+\alpha x)-\alpha^2 x 
&amp;amp;= 2x+2\alpha x -(3\alpha-1) x\\
&amp;amp;= (3-\alpha) x\\
&amp;amp;= \frac{1}{\alpha} x.
\end{align*}&lt;/div&gt;
&lt;p&gt;
Sorting the bends gives us &lt;span class="math"&gt;\((0, \frac{1}{\alpha} x, x, \alpha x)\)&lt;/span&gt;. We see that for this configuration of bends the mirroring lets the bends decrease indefinitely toward zero, dividing their magnitudes by &lt;span class="math"&gt;\(\alpha\)&lt;/span&gt; at each stage.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;A list of good bends&lt;/h2&gt;
&lt;p&gt;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?&lt;/p&gt;
&lt;p&gt;In &lt;a href="https://arxiv.org/abs/math/0009113"&gt;this paper from 2003&lt;/a&gt; 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 &lt;span class="math"&gt;\((a, b, c, d)\)&lt;/span&gt; and integer solutions to the equation &lt;span class="math"&gt;\(x^2+m^2=d_1d_2\)&lt;/span&gt; satisfying &lt;span class="math"&gt;\(x&amp;lt;0\leq 2m\leq d_1 \leq d_2\)&lt;/span&gt; and &lt;span class="math"&gt;\(\gcd(x, d_1, d_2)=1\)&lt;/span&gt;.&lt;/p&gt;
&lt;div class="math"&gt;\begin{align*}
a&amp;amp;=x\\
b&amp;amp;=d_1-x\\
c&amp;amp;=d_2-x\\
d&amp;amp;=-2m+d_1+d_2-x
\end{align*}&lt;/div&gt;
&lt;p&gt;I want these grouped by their outer bend, so I will fix &lt;span class="math"&gt;\(a=x=-n\)&lt;/span&gt;. As it turns out, for each fixed &lt;span class="math"&gt;\(n\)&lt;/span&gt; there are not too many candidates for &lt;span class="math"&gt;\(m\)&lt;/span&gt;, and we can check them all provided &lt;span class="math"&gt;\(n\)&lt;/span&gt; isn't too big.&lt;/p&gt;
&lt;p&gt;Since we want &lt;span class="math"&gt;\(2m\leq d_1\)&lt;/span&gt; and &lt;span class="math"&gt;\(2m\leq d_2\)&lt;/span&gt; it follows that &lt;span class="math"&gt;\(4m^2\leq d_1d_2\)&lt;/span&gt; and so &lt;span class="math"&gt;\(3m^2\leq d_1d_2-m^2=n^2\)&lt;/span&gt;. Thus, we only need to check &lt;span class="math"&gt;\(m&amp;lt;n/\sqrt{3}\)&lt;/span&gt;. For each &lt;span class="math"&gt;\(m\)&lt;/span&gt;, we can just try all the numbers &lt;span class="math"&gt;\(d_1\)&lt;/span&gt; between &lt;span class="math"&gt;\(2m\)&lt;/span&gt; and &lt;span class="math"&gt;\(\sqrt{n^2+m^2}\)&lt;/span&gt; to see if they divide &lt;span class="math"&gt;\(n^2+m^2\)&lt;/span&gt;. If so that gives us &lt;span class="math"&gt;\(d_2\)&lt;/span&gt;, so we can just check if &lt;span class="math"&gt;\(\gcd(n, d1, d2)=1\)&lt;/span&gt;. Here's some Python code to carry out this procedure.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;math&lt;/span&gt;


&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;get_primitive_bends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ceil&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;))):&lt;/span&gt;
        &lt;span class="n"&gt;s&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;floor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;sqrt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
            &lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;divmod&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;s&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;remainder&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;math&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gcd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;d1&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;d2&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;m&lt;/span&gt;


&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;n&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;range&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bend&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;get_primitive_bends&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bend&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;This gives us exactly &lt;a href="https://en.wikipedia.org/wiki/Apollonian_gasket#Integral_Apollonian_circle_packings"&gt;the list on Wikipedia&lt;/a&gt;.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;31&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;43&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;56&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;57&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;32&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;72&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;73&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;13&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;24&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;25&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;91&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;14&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;26&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;27&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;18&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;19&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;22&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;We could certainly get more clever about how we find &lt;span class="math"&gt;\(d_1\)&lt;/span&gt; and &lt;span class="math"&gt;\(d_2\)&lt;/span&gt;, though trial division works well for small &lt;span class="math"&gt;\(n\)&lt;/span&gt;. An example speedup is to notice &lt;span class="math"&gt;\(d_1d_2=m^2+n^2\)&lt;/span&gt; is the sum of two squares. By the aptly-named &lt;a href="https://en.wikipedia.org/wiki/Sum_of_two_squares_theorem"&gt;sum of two squares theorem&lt;/a&gt;, we know any prime factors of &lt;span class="math"&gt;\(m^2+n^2\)&lt;/span&gt; which are congruent to &lt;span class="math"&gt;\(3\)&lt;/span&gt; mod &lt;span class="math"&gt;\(4\)&lt;/span&gt; must have an even exponent -- for example, if &lt;span class="math"&gt;\(m^2+n^2\)&lt;/span&gt; is divisible by &lt;span class="math"&gt;\(7\)&lt;/span&gt; then it must also be divisible by &lt;span class="math"&gt;\(49\)&lt;/span&gt;. We can use that to find the factorization quicker, possibly requiring many fewer trial divisions.&lt;/p&gt;
&lt;h1&gt;Using integral bend-centers&lt;/h1&gt;
&lt;p&gt;In &lt;a href="https://arxiv.org/abs/math/0010302v5"&gt;this other article&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Their theorem 4.2 is proved by creating a superapollonian group action which takes a given Descartes configuration to &lt;span class="math"&gt;\((0, 0, g, g)\)&lt;/span&gt;. If we start with a primitive configuration, then we can take &lt;span class="math"&gt;\(g=1\)&lt;/span&gt;. 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.&lt;/p&gt;
&lt;p&gt;We can then apply that action to a strongly integral packing of &lt;span class="math"&gt;\((0, 0, 1, 1)\)&lt;/span&gt;. For example, if we apply it to 
&lt;/p&gt;
&lt;div class="math"&gt;$$
\begin{bmatrix}
0 &amp;amp; 0 &amp;amp; 1\\
0 &amp;amp; 0 &amp;amp;-1\\
1 &amp;amp; 1 &amp;amp; 0\\
1 &amp;amp;-1 &amp;amp; 0
\end{bmatrix}
$$&lt;/div&gt;
&lt;p&gt;
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.&lt;/p&gt;
&lt;h1&gt;Where this will go&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;script type="text/javascript"&gt;if (!document.getElementById('mathjaxscript_pelican_#%@#$@#')) {
    var align = "center",
        indent = "0em",
        linebreak = "false";

    if (false) {
        align = (screen.width &lt; 768) ? "left" : align;
        indent = (screen.width &lt; 768) ? "0em" : indent;
        linebreak = (screen.width &lt; 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')[0]).appendChild(configscript);
    (document.body || document.getElementsByTagName('head')[0]).appendChild(mathjaxscript);
}
&lt;/script&gt;</content><category term="Blog"></category><category term="number theory"></category><category term="Apollo"></category><category term="Apollonian circle packings"></category></entry><entry><title>Introducing Xanthippe</title><link href="https://aldenbradford.com/introducing-xanthippe.html" rel="alternate"></link><published>2022-08-04T00:00:00-04:00</published><updated>2022-08-04T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-08-04:/introducing-xanthippe.html</id><summary type="html">&lt;p&gt;A formal introduction to my calculator validator&lt;/p&gt;</summary><content type="html">&lt;p&gt;I have been going on about my project to &lt;a href="/calculator_emulator"&gt;emulate a calculator in the browser&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;What is Xanthippe?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;I have already &lt;a href="reflections-on-wiring-a-calculator"&gt;soldered test points to a calculator&lt;/a&gt; and figured out the electrical characteristics of &lt;a href="reverse-engineering-a-matrix-keypad"&gt;the buttons&lt;/a&gt; and &lt;a href="reading-a-multiplexed-lcd-signal"&gt;the display&lt;/a&gt;. I have designed &lt;a href="https://github.com/AldenMB/Xanthippe/tree/main/calculator_interface"&gt;a circuit board&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Why the name?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;What cases to test?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;How to save the answers?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre&gt;&lt;span&gt;&lt;/span&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;display&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;&amp;quot;dQw4w9WgXcQ&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;time&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;148&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;a&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;X&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{}&lt;/span&gt;
&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="nt"&gt;&amp;quot;7&amp;quot;&lt;/span&gt;&lt;span class="p"&gt;:{}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;

&lt;p&gt;Here &lt;code&gt;dQw4w9WgXcQ&lt;/code&gt; 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.&lt;/p&gt;
&lt;h1&gt;How to encode the keys?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;You may recall this table from &lt;a href="reverse-engineering-a-matrix-keypad"&gt;a previous post&lt;/a&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;A0&lt;/th&gt;
&lt;th&gt;A1&lt;/th&gt;
&lt;th&gt;A2&lt;/th&gt;
&lt;th&gt;A3&lt;/th&gt;
&lt;th&gt;A4&lt;/th&gt;
&lt;th&gt;A5&lt;/th&gt;
&lt;th&gt;A6&lt;/th&gt;
&lt;th&gt;A7&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;+/-&lt;/td&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;SIN&lt;/td&gt;
&lt;td&gt;1/x&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B1&lt;/td&gt;
&lt;td&gt;y^x&lt;/td&gt;
&lt;td&gt;HYP&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;COS&lt;/td&gt;
&lt;td&gt;x^2&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;pi&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;TAN&lt;/td&gt;
&lt;td&gt;sqrt&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Sigma+&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;DRG&lt;/td&gt;
&lt;td&gt;EE&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;STO&lt;/td&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;LOG&lt;/td&gt;
&lt;td&gt;(&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;RCL&lt;/td&gt;
&lt;td&gt;ab/c&lt;/td&gt;
&lt;td&gt;&amp;lt;-&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;LN&lt;/td&gt;
&lt;td&gt;)&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B6&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;(reset all)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RESET&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;The two numbers &lt;code&gt;0b111110&lt;/code&gt; and &lt;code&gt;0b111111&lt;/code&gt; have ambiguous encodings in base 64, since their original versions use the characters &lt;code&gt;+&lt;/code&gt; and &lt;code&gt;/&lt;/code&gt; 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 &lt;code&gt;0b111xxx&lt;/code&gt; will give a RESET, so I never need to use the two offending characters at all.&lt;/p&gt;
&lt;p&gt;The resulting encoding is as follows.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;A&lt;/th&gt;
&lt;th&gt;B&lt;/th&gt;
&lt;th&gt;Binary&lt;/th&gt;
&lt;th&gt;Base64&lt;/th&gt;
&lt;th&gt;label&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000000&lt;/td&gt;
&lt;td&gt;A&lt;/td&gt;
&lt;td&gt;+/-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000001&lt;/td&gt;
&lt;td&gt;B&lt;/td&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000010&lt;/td&gt;
&lt;td&gt;C&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000011&lt;/td&gt;
&lt;td&gt;D&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000100&lt;/td&gt;
&lt;td&gt;E&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000101&lt;/td&gt;
&lt;td&gt;F&lt;/td&gt;
&lt;td&gt;SIN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;000110&lt;/td&gt;
&lt;td&gt;G&lt;/td&gt;
&lt;td&gt;1/x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001000&lt;/td&gt;
&lt;td&gt;I&lt;/td&gt;
&lt;td&gt;y^x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001001&lt;/td&gt;
&lt;td&gt;J&lt;/td&gt;
&lt;td&gt;HYP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001010&lt;/td&gt;
&lt;td&gt;K&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001011&lt;/td&gt;
&lt;td&gt;L&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001100&lt;/td&gt;
&lt;td&gt;M&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001101&lt;/td&gt;
&lt;td&gt;N&lt;/td&gt;
&lt;td&gt;COS&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;001110&lt;/td&gt;
&lt;td&gt;O&lt;/td&gt;
&lt;td&gt;x^2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010000&lt;/td&gt;
&lt;td&gt;Q&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010001&lt;/td&gt;
&lt;td&gt;R&lt;/td&gt;
&lt;td&gt;pi&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010010&lt;/td&gt;
&lt;td&gt;S&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010011&lt;/td&gt;
&lt;td&gt;T&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010100&lt;/td&gt;
&lt;td&gt;U&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010101&lt;/td&gt;
&lt;td&gt;V&lt;/td&gt;
&lt;td&gt;TAN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;010110&lt;/td&gt;
&lt;td&gt;W&lt;/td&gt;
&lt;td&gt;sqrt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;011001&lt;/td&gt;
&lt;td&gt;Z&lt;/td&gt;
&lt;td&gt;Sigma+&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;011010&lt;/td&gt;
&lt;td&gt;a&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;011011&lt;/td&gt;
&lt;td&gt;b&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;011100&lt;/td&gt;
&lt;td&gt;c&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;011101&lt;/td&gt;
&lt;td&gt;d&lt;/td&gt;
&lt;td&gt;DRG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;011110&lt;/td&gt;
&lt;td&gt;e&lt;/td&gt;
&lt;td&gt;EE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;100001&lt;/td&gt;
&lt;td&gt;h&lt;/td&gt;
&lt;td&gt;STO&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;100010&lt;/td&gt;
&lt;td&gt;i&lt;/td&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;100011&lt;/td&gt;
&lt;td&gt;j&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;100100&lt;/td&gt;
&lt;td&gt;k&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;100101&lt;/td&gt;
&lt;td&gt;l&lt;/td&gt;
&lt;td&gt;LOG&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;100110&lt;/td&gt;
&lt;td&gt;m&lt;/td&gt;
&lt;td&gt;(&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101001&lt;/td&gt;
&lt;td&gt;p&lt;/td&gt;
&lt;td&gt;RCL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101010&lt;/td&gt;
&lt;td&gt;q&lt;/td&gt;
&lt;td&gt;ab/c&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101011&lt;/td&gt;
&lt;td&gt;r&lt;/td&gt;
&lt;td&gt;&amp;lt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101100&lt;/td&gt;
&lt;td&gt;s&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101101&lt;/td&gt;
&lt;td&gt;t&lt;/td&gt;
&lt;td&gt;LN&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;101110&lt;/td&gt;
&lt;td&gt;u&lt;/td&gt;
&lt;td&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;110111&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;(reset all)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;x&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;111xxx&lt;/td&gt;
&lt;td&gt;4,5,...&lt;/td&gt;
&lt;td&gt;ON/C&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;!---
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:&lt;6} | {symbol}')
--&gt;

&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;How to store the tree?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href="https://github.com/sql-js/sql.js"&gt;sql.js&lt;/a&gt;) 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.&lt;/p&gt;
&lt;h1&gt;Next steps&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="Xanthippe"></category><category term="data structures"></category></entry><entry><title>A 3D-printed Planimeter For Use In Classrooms</title><link href="https://aldenbradford.com/a-3d-printed-planimeter-for-use-in-classrooms.html" rel="alternate"></link><published>2022-07-24T00:00:00-04:00</published><updated>2022-07-24T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-24:/a-3d-printed-planimeter-for-use-in-classrooms.html</id><summary type="html">&lt;p&gt;A simple 3D-printable design for a planimeter, suitable for making a class set.&lt;/p&gt;</summary><content type="html">&lt;p&gt;Over the past week I have been developing &lt;a href="https://github.com/AldenMB/Planimeter"&gt;a design for a 3D printed planimeter.&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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 &lt;a href="https://www.amazon.com/bayite-Voltmeter-Motorcycle-Polarity-Protection/dp/B00YALV0NG/"&gt;panel mount volt meter&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="3D printing"></category><category term="CadQuery"></category><category term="teaching"></category></entry><entry><title>Reading a Multiplexed LCD Signal</title><link href="https://aldenbradford.com/reading-a-multiplexed-lcd-signal.html" rel="alternate"></link><published>2022-07-21T00:00:00-04:00</published><updated>2022-07-21T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-21:/reading-a-multiplexed-lcd-signal.html</id><summary type="html">&lt;p&gt;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?&lt;/p&gt;</summary><content type="html">&lt;p&gt;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?&lt;/p&gt;
&lt;h1&gt;backplanes&lt;/h1&gt;
&lt;p&gt;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 &lt;a href="/reflections-on-wiring-a-calculator"&gt;I chose previously&lt;/a&gt;, 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.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pin&lt;/th&gt;
&lt;th&gt;0ms&lt;/th&gt;
&lt;th&gt;3ms&lt;/th&gt;
&lt;th&gt;6ms&lt;/th&gt;
&lt;th&gt;9ms&lt;/th&gt;
&lt;th&gt;12ms&lt;/th&gt;
&lt;th&gt;15ms&lt;/th&gt;
&lt;th&gt;18ms&lt;/th&gt;
&lt;th&gt;21ms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;We only need to measure the pins four times per cycle, during the first 12 milliseconds.&lt;/strong&gt;&lt;/p&gt;
&lt;h1&gt;data&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;pin&lt;/th&gt;
&lt;th&gt;0ms&lt;/th&gt;
&lt;th&gt;3ms&lt;/th&gt;
&lt;th&gt;6ms&lt;/th&gt;
&lt;th&gt;9ms&lt;/th&gt;
&lt;th&gt;12ms&lt;/th&gt;
&lt;th&gt;15ms&lt;/th&gt;
&lt;th&gt;18ms&lt;/th&gt;
&lt;th&gt;21ms&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;32(no g)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;32(g)&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Assuming the others segments are driven in the same way, this tells us how to tell which are activated. &lt;strong&gt;For each pin, its corresponding cells will be active when it reaches 4.5 volts during each of the four measurement windows.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;from measurement, to circuit&lt;/h1&gt;
&lt;p&gt;To begin, let's take all 32 pins and put them into comparators. &lt;a href="https://aldenbradford.com/KA339A-D.pdf"&gt;The comparators I have&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;luxuries&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="Xanthippe"></category></entry><entry><title>Reverse-Engineering a Matrix Keypad</title><link href="https://aldenbradford.com/reverse-engineering-a-matrix-keypad.html" rel="alternate"></link><published>2022-07-21T00:00:00-04:00</published><updated>2022-07-21T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-21:/reverse-engineering-a-matrix-keypad.html</id><summary type="html">&lt;p&gt;The process of figuring out the layout of a keypad without looking at it directly.&lt;/p&gt;</summary><content type="html">&lt;p&gt;In order to improve &lt;a href="/calculator_emulator"&gt;my calculator emulator&lt;/a&gt;, 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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a picture of the inside of the calculator before it was modified" src="/images/calculator/inside_before.jpg" width="600"&gt;&lt;/p&gt;
&lt;p&gt;There are a couple ways I could still get access to the other side of the circuit board.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Here is the result, after measuring every pair.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;A0&lt;/th&gt;
&lt;th&gt;A1&lt;/th&gt;
&lt;th&gt;A2&lt;/th&gt;
&lt;th&gt;A3&lt;/th&gt;
&lt;th&gt;A4&lt;/th&gt;
&lt;th&gt;A5&lt;/th&gt;
&lt;th&gt;A6&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;+/-&lt;/td&gt;
&lt;td&gt;2nd&lt;/td&gt;
&lt;td&gt;+&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;.&lt;/td&gt;
&lt;td&gt;SIN&lt;/td&gt;
&lt;td&gt;1/x&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B1&lt;/td&gt;
&lt;td&gt;y^x&lt;/td&gt;
&lt;td&gt;HYP&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;COS&lt;/td&gt;
&lt;td&gt;x^2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;OFF&lt;/td&gt;
&lt;td&gt;pi&lt;/td&gt;
&lt;td&gt;X&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;TAN&lt;/td&gt;
&lt;td&gt;sqrt&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B3&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;Sigma+&lt;/td&gt;
&lt;td&gt;/&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;DRG&lt;/td&gt;
&lt;td&gt;EE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B4&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;STO&lt;/td&gt;
&lt;td&gt;=&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;LOG&lt;/td&gt;
&lt;td&gt;(&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;B5&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;RCL&lt;/td&gt;
&lt;td&gt;ab/c&lt;/td&gt;
&lt;td&gt;&amp;lt;-&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;LN&lt;/td&gt;
&lt;td&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;There are a few things about this layout that surprised me.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a picture of the inside of an older revision of the calculator" src="/images/calculator/older_inside.jpg" width="600"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="Xanthippe"></category></entry><entry><title>Reflections on wiring a calculator</title><link href="https://aldenbradford.com/reflections-on-wiring-a-calculator.html" rel="alternate"></link><published>2022-07-19T00:00:00-04:00</published><updated>2022-07-19T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-19:/reflections-on-wiring-a-calculator.html</id><summary type="html">&lt;p&gt;Some thoughts on soldering, connectors, circuit repair, and test setups.&lt;/p&gt;</summary><content type="html">&lt;p&gt;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 &lt;a href="/calculator_emulator"&gt;the emulator I have written&lt;/a&gt;. 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.&lt;/p&gt;
&lt;h1&gt;Why use connectors?&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a picture of the inside of the calculator before it was modified" src="/images/calculator/inside_before.jpg" width="600"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Separation of concerns&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Modularity&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Reuse&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Choice of connector&lt;/h1&gt;
&lt;p&gt;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?&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;While the cables are readily available since they are used for 8-bit SCSI drives, the headers are not so easy to come by.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a picture of the inside of the calculator after it was modified" src="https://aldenbradford.com/inside_after.jpg" width="600"&gt;
&lt;img alt="a picture of the outside of the calculator after it was modified" src="https://aldenbradford.com/outside_after.jpg" width="600"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;RESET&lt;/th&gt;
&lt;th&gt;B6&lt;/th&gt;
&lt;th&gt;B5&lt;/th&gt;
&lt;th&gt;B4&lt;/th&gt;
&lt;th&gt;B3&lt;/th&gt;
&lt;th&gt;A7&lt;/th&gt;
&lt;th&gt;A6&lt;/th&gt;
&lt;th&gt;A5&lt;/th&gt;
&lt;th&gt;A4&lt;/th&gt;
&lt;th&gt;A3&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;th&gt;-&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;B+&lt;/td&gt;
&lt;td&gt;B2&lt;/td&gt;
&lt;td&gt;B1&lt;/td&gt;
&lt;td&gt;B0&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;11&lt;/td&gt;
&lt;td&gt;13&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;17&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;td&gt;27&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;31&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GND&lt;/td&gt;
&lt;td&gt;A2&lt;/td&gt;
&lt;td&gt;A1&lt;/td&gt;
&lt;td&gt;A0&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;22&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;28&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;32&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Soldering thoughts&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Flux is mandatory&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Match the size of the wire to the trace&lt;/h2&gt;
&lt;p&gt;It became much easier to solder onto the traces when i started using &lt;a href="https://www.adafruit.com/product/3522"&gt;magnet wire&lt;/a&gt; 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.&lt;/p&gt;
&lt;h2&gt;Mechanical contact precedes thermal contact&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;My test setup&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="soldering"></category><category term="Xanthippe"></category></entry><entry><title>Review: How to Do Nothing by Jenny Odell</title><link href="https://aldenbradford.com/how_to_do_nothing.html" rel="alternate"></link><published>2022-07-17T00:00:00-04:00</published><updated>2022-07-17T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-17:/how_to_do_nothing.html</id><summary type="html">&lt;p&gt;I liked this book, it makes good points in interesting ways&lt;/p&gt;</summary><content type="html">&lt;p&gt;I know I'm late to the boat on this one. This book came out in 2019. It was one of &lt;a href="https://www.instagram.com/p/B6oYKxAgCn7/"&gt;Barack Obama's favorite books that year&lt;/a&gt;. 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.&lt;/p&gt;
&lt;p&gt;She wisely opens the book with &lt;a href="https://terebess.hu/english/chuangtzu.html#4"&gt;an old Daoist story&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;The student asks "why do we use this notation?", "why is this ambiguous case defined in this manner?". The teacher replies, "for historical reasons".&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="book reviews"></category></entry><entry><title>A Two Bit Analog-Digital Converter</title><link href="https://aldenbradford.com/two_bit_adc.html" rel="alternate"></link><published>2022-07-12T00:00:00-04:00</published><updated>2022-07-12T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-12:/two_bit_adc.html</id><summary type="html">&lt;p&gt;A surprisingly simple circuit gives a reliable two bit analog-digital converter using only one integrated circuit chip.&lt;/p&gt;</summary><content type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;An off-the-shelf ADC chip&lt;/h1&gt;
&lt;p&gt;We only need two bits of information to distinguish between the four voltage levels. Even the cheapest ADCs give a whole eight bits -- &lt;a href="https://www.digikey.com/en/products/detail/touchstone-semiconductor/TS7003ITD833T/3645626"&gt;the cheapest ADC on Digikey&lt;/a&gt; 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.&lt;/p&gt;
&lt;h2&gt;Advantages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;It's dead simple to implement, fairly easy to understand.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Disadvantages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It's noisy, since it puts a time-varying load on the signal wires.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It's expensive!&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;A handfull of microcontrollers&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Advantages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Very few additional components, since it has most of the circuitry we need included.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Disadvantages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Still noisy and power-hungry&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;We have to write code for the microcontrollers&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;A resistance ladder (flash) ADC&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a picture of a traditional flash ADC" src="https://aldenbradford.com/traditional_flash_adc.svg" width="300"&gt;&lt;/p&gt;
&lt;h2&gt;Advantages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Independent of component values. We can use any resistors we like, and any comparator chip. This makes it potentially very cheap.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Disadvantages&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;Uses an odd number of components. Comparators are usually available in packages of two or four, and this uses three.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h1&gt;A creative solution&lt;/h1&gt;
&lt;p&gt;Here is the design I have settled on.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a compact 2-bit ADC" src="https://aldenbradford.com/compact_adc.svg" width="300"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Advantages&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Disadvantages&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a version which does not need a 2.5 volt rail" height="250" src="https://aldenbradford.com/no_reference_adc.svg"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Generalizing&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="how to form bit n of an ADC of this topology" height="200" src="https://aldenbradford.com/r_2r_adc.svg"&gt;&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;Conclusion&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="Electronics"></category></entry><entry><title>Measures of Bimodality</title><link href="https://aldenbradford.com/bimodality.html" rel="alternate"></link><published>2022-07-10T00:00:00-04:00</published><updated>2022-07-10T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-07-10:/bimodality.html</id><summary type="html">&lt;p&gt;What are the most popular ways to measure the bimodality of a sample from a random variable?&lt;/p&gt;</summary><content type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h1&gt;A survey of tests&lt;/h1&gt;
&lt;h2&gt;Statistics based on labeled data&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ashman's D&lt;/li&gt;
&lt;li&gt;Bimodal separation&lt;/li&gt;
&lt;li&gt;Bimodality amplitude&lt;/li&gt;
&lt;li&gt;Bimodal ratio&lt;/li&gt;
&lt;li&gt;Wilcock's bimodality parameter&lt;/li&gt;
&lt;li&gt;Wang's bimodality index&lt;/li&gt;
&lt;li&gt;Sambrook Smith's index&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Linear discriminant analysis would also fall into this category, though it is less concerned with detecting bimodality than with fitting bimodal data.&lt;/p&gt;
&lt;h2&gt;Kurtosis&lt;/h2&gt;
&lt;p&gt;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 &lt;a href="https://www.tandfonline.com/doi/pdf/10.1080/00031305.1971.10477241"&gt;"Kurtosis Measures Bimodality?" by David Hildebrand.&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Bimodality coefficient&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Sturrock's index&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Hartigan's dip test&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;Silverman's kernel density test&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://purdue.primo.exlibrisgroup.com/permalink/01PURDUE_PUWL/5imsd2/cdi_proquest_journals_1302943262"&gt;Silverman&lt;/a&gt; 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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;h2&gt;information-theoretic approaches&lt;/h2&gt;
&lt;p&gt;&lt;a href="https://link.springer.com/content/pdf/10.3758%2Fs13428-012-0225-x.pdf"&gt;Some people&lt;/a&gt; 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.&lt;/p&gt;
&lt;h1&gt;Conclusions&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="Statistics"></category><category term="nTARP"></category><category term="Clustering"></category></entry><entry><title>Pinball Science</title><link href="https://aldenbradford.com/pinball_science.html" rel="alternate"></link><published>2022-02-22T00:00:00-05:00</published><updated>2022-04-19T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2022-02-22:/pinball_science.html</id><summary type="html">&lt;p&gt;an old game I enjoyed as a kid, which is now hard to install.&lt;/p&gt;</summary><content type="html">&lt;h1&gt;the game&lt;/h1&gt;
&lt;p&gt;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 &lt;a href="https://www.youtube.com/watch?v=4VeQDGTrruA"&gt;this youtube video&lt;/a&gt;, 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.&lt;/p&gt;
&lt;h1&gt;installation woes&lt;/h1&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;Do not despair: they published a patch which I have used before with success. It used to be available for download &lt;a href="support.selectsoft.com/products/A/LDAMAFAMEJ.htm#Downloads"&gt;from their website&lt;/a&gt;, but their website went offline many years ago. Archive.org does &lt;a href="https://web.archive.org/web/20061127031515/support.selectsoft.com/products/A/LDAMAFAMEJ.htm"&gt;have a backup of that website&lt;/a&gt; including &lt;a href="https://web.archive.org/web/20061127031515/http://support.selectsoft.com/download/SP1fix.exe"&gt;a backup of that patch file&lt;/a&gt;. Just in case, I have also &lt;a href="https://aldenbradford.com/SP1fix.exe"&gt;hosted that file here&lt;/a&gt;. For what it's worth, here is a link to &lt;a href="https://web.archive.org/web/20050209013234/http://www.learnatglobal.com/html/xp_sound.html"&gt;another patch from that publisher&lt;/a&gt;, 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 &lt;a href="https://aldenbradford.com/SPupdate.exe"&gt;rehosted that patch here&lt;/a&gt;, just in case.&lt;/p&gt;
&lt;h2&gt;multiple versions&lt;/h2&gt;
&lt;p&gt;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 &lt;a href="https://github.com/AldenMB/AldenMB.github.io/releases/tag/Pinball_Science"&gt;on my Github&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;Scholastic&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Case: &lt;img alt="case photo" src="https://aldenbradford.com/scholastic_case.jpg" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Disk: &lt;img alt="disk photo" src="https://aldenbradford.com/scholastic_disk.jpg" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Front: &lt;img alt="front scan" src="https://aldenbradford.com/scholastic_front.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Back: &lt;img alt="back scan" src="https://aldenbradford.com/scholastic_back.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Inside: &lt;img alt="inside scan" src="https://aldenbradford.com/scholastic_inside.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Inside back: &lt;img alt="inside back scan" src="https://aldenbradford.com/scholastic_back_inside.png" width="300px"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Global Software Publishing&lt;/h2&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Case: &lt;img alt="case photo" src="https://aldenbradford.com/global_case.jpg" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Disk: &lt;img alt="disk photo" src="https://aldenbradford.com/global_disk.jpg" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Front: &lt;img alt="front scan" src="https://aldenbradford.com/global_front.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Back: &lt;img alt="back scan" src="https://aldenbradford.com/global_back.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Inside: &lt;img alt="inside scan" src="https://aldenbradford.com/global_inside.png" width="300px"&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;The New Way Things Work&lt;/h2&gt;
&lt;p&gt;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:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;There is a book &lt;a href="https://en.wikipedia.org/wiki/The_Way_Things_Work"&gt;The Way Things Work&lt;/a&gt; released in 1988&lt;/li&gt;
&lt;li&gt;There is &lt;a href="https://archive.org/details/the-way-things-work-cdrom"&gt;a CD-ROM version&lt;/a&gt; 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.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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 &lt;a href="https://github.com/AldenMB/AldenMB.github.io/releases/tag/Pinball_Science"&gt;an ISO of this disk&lt;/a&gt; along with the scans below. When loaded in Windows, the name of the disk reads as DKTNWTW.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Case: &lt;img alt="case photo" src="https://aldenbradford.com/way_things_work_case.jpg" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Disk: &lt;img alt="disk photo" src="https://aldenbradford.com/way_things_work_disk.jpg" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Front: &lt;img alt="front scan" src="https://aldenbradford.com/way_things_work_front.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Back: &lt;img alt="back scan" src="https://aldenbradford.com/way_things_work_back.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Inside: &lt;img alt="inside scan" src="https://aldenbradford.com/way_things_work_inside.png" width="300px"&gt;&lt;/li&gt;
&lt;li&gt;Inside back: &lt;img alt="inside back scan" src="https://aldenbradford.com/way_things_work_back_inside.png" width="300px"&gt;&lt;/li&gt;
&lt;/ul&gt;</content><category term="Blog"></category><category term="Abandonware"></category><category term="Preservation"></category></entry><entry><title>What's Inside "The REAL Cotton Candy Maker"?</title><link href="https://aldenbradford.com/whats-inside-the-real-cotton-candy-maker.html" rel="alternate"></link><published>2019-10-01T00:00:00-04:00</published><updated>2019-10-01T00:00:00-04:00</updated><author><name>Alden Bradford</name></author><id>tag:aldenbradford.com,2019-10-01:/whats-inside-the-real-cotton-candy-maker.html</id><summary type="html">&lt;p&gt;Tracing out the circuit of an old, constantly-breaking toy.&lt;/p&gt;</summary><content type="html">&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;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.&lt;/p&gt;
&lt;p&gt;&lt;img alt="a hand-drawn circuit diagram" src="https://aldenbradford.com/cotton_candy_circuit.png" width="700px"&gt;&lt;/p&gt;
&lt;p&gt;Above is the circuit diagram I reconstructed. Here are a few observations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;li&gt;JE2 is connected to a hall effect sensor, which detects a magnet on the machine's rotating shaft.&lt;/li&gt;
&lt;li&gt;JE3 is just connected to a couple LEDs.&lt;/li&gt;
&lt;li&gt;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.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;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.&lt;/p&gt;</content><category term="Blog"></category><category term="electronics"></category><category term="reverse engineering"></category><category term="preservation"></category></entry></feed>