<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="text/xsl" href="assets/xml/rss.xsl" media="all"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Fotonix &amp; Gizmos</title><link>https://www.fotonixx.com/</link><description>Ramblings of an Optical Engineer</description><atom:link href="https://www.fotonixx.com/rss.xml" rel="self" type="application/rss+xml"></atom:link><language>en</language><copyright>Contents © 2022 &lt;a href="mailto:ryan@fotonixx.com"&gt;Ryan Frazier&lt;/a&gt; </copyright><lastBuildDate>Thu, 17 Mar 2022 17:37:44 GMT</lastBuildDate><generator>Nikola (getnikola.com)</generator><docs>http://blogs.law.harvard.edu/tech/rss</docs><item><title>Taking Jeff Patton's Product Leadership Course</title><link>https://www.fotonixx.com/posts/jpat-product-leadership-review/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/product_leadership/preview_image.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;Around 2010 I remember watching an &lt;a class="reference external" href="https://www.youtube.com/watch?v=0QYcgwjUlWo"&gt;Intel Commercial featuring Ajay Bhatt&lt;/a&gt;, co-inventor of the USB. As he walks through a crowd of fawning coworkers to get his morning coffee, the tagline pops up: “Our Rockstars Aren’t Like Your Rockstars.” Despite working in the semiconductor industry, my list of rockstars has never included Ajay. Instead, it’s filled with the likes of John Carmack and Michael Abrash, pioneers in computer graphics and virtual reality, one of my core passions.&lt;/p&gt;
&lt;p&gt;As I discovered a new passion in Product Management, my list of rockstars grew to include product leaders who created the concepts and processes I use daily. In February, I was lucky enough to &lt;a class="reference external" href="https://www.jpattonassociates.com/services/ppl-online-course/"&gt;take a Product Leadership Course&lt;/a&gt; from one of these Leaders, Jeff Patton, author of &lt;a class="reference external" href="https://www.amazon.com/User-Story-Mapping-Discover-Product/dp/1491904909"&gt;User Story Mapping&lt;/a&gt;. While labeled as a Certified Scrum Product Owner (CSPO) course, the class introduced many areas of product thinking, not just agile delivery, and is one of the few such classes &lt;a class="reference external" href="https://svpg.com/the-cspo-pathology/"&gt;recommended by Marty Cagan of SVPG&lt;/a&gt;. Below are my thoughts on the training and personal takeaways.&lt;/p&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#the-course" id="id1"&gt;The Course&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#pre-work" id="id2"&gt;Pre-Work&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#class-summary" id="id3"&gt;Class Summary&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#is-the-class-worth-taking" id="id4"&gt;Is the Class Worth Taking?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#learnings-and-takeaways" id="id5"&gt;Learnings and Takeaways&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#focusing-on-outcomes" id="id6"&gt;Focusing On Outcomes&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#reviewing-shipped-features" id="id7"&gt;Reviewing Shipped Features&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#users-vs-choosers" id="id8"&gt;Users vs. Choosers&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#the-continuous-tech-product-improvement-cycle" id="id9"&gt;The Continuous Tech-Product Improvement Cycle&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#products-composed-of-products" id="id10"&gt;Products Composed of Products&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#personas-and-interviews" id="id11"&gt;Personas and Interviews&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#a-simple-product-interview-script" id="id12"&gt;A Simple Product Interview Script&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#personas-and-protopersonas" id="id13"&gt;Personas and Protopersonas&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#personifying-a-service" id="id14"&gt;Personifying a Service&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#strategy" id="id15"&gt;Strategy&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#where-you-will-play-and-how-you-will-win" id="id16"&gt;Where You will Play and How you Will Win&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#customers-solve-business-problems-product-teams-solve-customer-opportunities" id="id17"&gt;Customers Solve Business Problems; Product Teams Solve Customer Opportunities.&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#user-focused-okrs" id="id18"&gt;User-Focused OKRs&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#discovery-and-delivery" id="id19"&gt;Discovery and Delivery&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#opportunity-canvas" id="id20"&gt;Opportunity Canvas&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#the-next-best-test" id="id21"&gt;The Next Best Test&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#clarifying-prototype-fidelity" id="id22"&gt;Clarifying Prototype Fidelity&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#shrink-your-releases-by-prioritizing-people-not-features" id="id23"&gt;Shrink Your Releases by prioritizing people, not features&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#release-strategy-vs-development-strategy" id="id24"&gt;Release Strategy vs. Development Strategy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#release-to-earn-vs-release-to-learn" id="id25"&gt;Release to Earn vs. Release to Learn&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="quick-links"&gt;
&lt;p class="topic-title"&gt;Quick Links&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#the-course" id="id26"&gt;The Course&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#pre-work" id="id27"&gt;Pre-Work&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#class-summary" id="id28"&gt;Class Summary&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#is-the-class-worth-taking" id="id29"&gt;Is the Class Worth Taking?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#learnings-and-takeaways" id="id30"&gt;Learnings and Takeaways&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#focusing-on-outcomes" id="id31"&gt;Focusing On Outcomes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#personas-and-interviews" id="id32"&gt;Personas and Interviews&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#strategy" id="id33"&gt;Strategy&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#discovery-and-delivery" id="id34"&gt;Discovery and Delivery&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-course"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id26"&gt;The Course&lt;/a&gt;&lt;/h2&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Course Title:&lt;/strong&gt; &lt;a class="reference external" href="https://www.jpattonassociates.com/services/ppl-online-course/"&gt;Passionate Product Leadership Live Online Course&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Format:&lt;/strong&gt; Virtual over Zoom. Leverages &lt;a class="reference external" href="https://start.mural.co"&gt;Mural&lt;/a&gt; extensively for collaboration&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Duration:&lt;/strong&gt; Four days at four hours per day (16 hours total)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Cost:&lt;/strong&gt; $1695 ($1395 If you register in advance)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="section" id="pre-work"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id27"&gt;Pre-Work&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Apart from a few videos to watch before each session, there’s no formal pre-work/advanced reading for the course. However, I strongly recommend reading &lt;a class="reference external" href="http://theleanstartup.com/book"&gt;The Lean Startup&lt;/a&gt; and &lt;a class="reference external" href="https://www.amazon.com/User-Story-Mapping-Discover-Product/dp/1491904909"&gt;User Story Mapping&lt;/a&gt; beforehand. Otherwise, all the topics covered may leave you feeling like you’ve just drunk from a product-spewing fire hose!&lt;/p&gt;
&lt;p&gt;If you want to get a feel for Jeff’s lecturing style, watch his REConf 2019 talk "&lt;a class="reference external" href="https://www.youtube.com/watch?v=mqBGO0wgNQM&amp;amp;t=249s"&gt;The Game has Changed&lt;/a&gt;." He repeated most of this talk on the first day of class.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="class-summary"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id28"&gt;Class Summary&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Jumping into the Zoom meeting on day one of the four-day course, I wasn’t sure what to expect. Since the pandemic, I’ve been in my fair share of virtual training sessions, and a majority of them are little more than the presenter going through a checklist of discussion topics while deflecting questions that disagree with their main points. However, as soon as I connected to Jeff’s meeting, I knew it would be different.&lt;/p&gt;
&lt;p&gt;Instead of a premade slide deck, His screen showed a birds-eye video feed of his desk, with sticky notes and a hand-written message welcoming everybody to the class. This format was standard throughout the entire training, with Jeff discussing topics while simultaneously drawing pictures and diagrams to explain them. It made everything more engaging than a slide deck while allowing him to hop into quick tangents as participants asked questions.&lt;/p&gt;
&lt;p&gt;The &lt;a class="reference external" href="https://www.jpattonassociates.com/services/ppl-online-course/#21st-century-tech-product-development-concepts-and-practice"&gt;outline on the course website&lt;/a&gt; is largely accurate, with our four days touching the following topics&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Day One&lt;/strong&gt; - Product Development, Structuring Product Teams&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Day Two&lt;/strong&gt; - Users and Personas, Customer Interviewing, Story Mapping, Metrics&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Day Three&lt;/strong&gt; - Vision, Product Strategy, User-focused OKRs&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Day Four&lt;/strong&gt; - Discovery, Delivery, Release Strategy&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Additionally, there are a couple of things to note about the sessions:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;dl class="simple"&gt;
&lt;dt&gt;You will Learn by Doing&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Interlaced throughout the training were sets of collaborative group activities. In fact, from day two onward, everybody was clustered into mini product teams tackling various tech products (our team focused on the Target app and the challenges shoppers face when items from their grocery orders are out of stock). With the aid of some very well-designed &lt;a class="reference external" href="https://start.mural.co"&gt;Mural&lt;/a&gt; boards, we were immediately able to practice newly learned concepts. The small group size meant open conversations and exposure to different perspectives.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;li&gt;&lt;dl class="simple"&gt;
&lt;dt&gt;Managing software Delivery is a small portion of the actual class&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Again, this is a product leadership class, not just an agile delivery class. This course will not teach you how to write intricate, verbose requirement docs that you can cleanly hand off. Instead, it will stress that the solution to many of the points of frustration a PM faces is to continually talk to customers, involve software and stakeholders early and often, and have regular, open conversations to ensure everybody is aligned.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Office hours, held at the end of each lesson, provided a great time to discuss in detail any of the day’s topics, especially if you have specific questions on how to apply them with your organization or product. I made a point to hang back every day, not just to ask my question but to learn about the challenges and insights other participants face when adopting a product mindset. Jeff scanned any sketches from the office hour and added them to our class Mural board.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="is-the-class-worth-taking"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id29"&gt;Is the Class Worth Taking?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;This class completely exceeded my expectations, and not just because it managed to tie &lt;a class="reference external" href="https://www.youtube.com/watch?v=WpnM37A4P_8&amp;amp;t=93s"&gt;South Park into a lesson on product strategy&lt;/a&gt;! The informal dialog, paired with activities and Zoom chat for class discussions always left me feeling like an active participant instead of just an observer. Even though I had already implemented many of the ideas being taught, it was worth it to learn from Jeff first hand, ask questions related to my specific products, and clarify some vague topics from his book. I plan to take advantage of the alumni policy and audit the course in the future.&lt;/p&gt;
&lt;p&gt;I walked out of the training with roughly ten pages of notes, almost all of which I’d been able to put into practice during group activities, and ½ dozen new books on my “to read” backlog. I had also earned a CSPO certificate along the way. However, CSPO is a participation award (there’s no exam to confirm you learned anything). The learnings from the class were far more valuable than a certificate to pad my resume. My most significant regret is that none of my product team took the class alongside me. Alumni can retake the course for free, and I plan to pull the rest of my trio into a future session.&lt;/p&gt;
&lt;p&gt;If you adopt product thinking in your company, bring your entire team, so everybody gets direct experience (including SWE, UX, and Leadership)! As great as Jeff’s drawings are, they are very context heavy. If you take one to your team later and go, “See, this is what we need to do!” You’ll likely get blank stares and confusion.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="learnings-and-takeaways"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id30"&gt;Learnings and Takeaways&lt;/a&gt;&lt;/h2&gt;
&lt;div class="class alert alert-success pb-0 docutils container"&gt;
&lt;div class="alert-header"&gt;
&lt;i class="fas fa-info-circle"&gt;&lt;/i&gt; Note
&lt;/div&gt;
&lt;hr class="mt-0 mb-1"&gt;&lt;p&gt;These are my own notes and takeaways from the course, grouped roughly by concepts. &lt;strong&gt;They only cover a small fraction of everything that was covered during the training.&lt;/strong&gt; You’ll notice I omitted things like “build-measure-learn” or “what is a story map” because I was already familiar with them. I strongly recommend coming into the class having read up on certain topics so you can focus on subtle details.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="focusing-on-outcomes"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id31"&gt;Focusing On Outcomes&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="reviewing-shipped-features"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id7"&gt;Reviewing Shipped Features&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;One of the first activities was reviewing past features your team has shipped on a 2-axis plot of Outcome vs. Actual Effort. I plan to adopt this method and slowly build a cluster plot of everything we ship. It will be a great way to visualize any weak spots on our product development cycle, e.g., “Our releases are regularly larger than we’d like” or “ involving X part of the system in a release always bottlenecks us.”&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;For Additional Reading&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.jpattonassociates.com/keep-actual-effort-and-outcome-visible/"&gt;Keep Actual Effort and Outcome Visible&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="users-vs-choosers"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id8"&gt;Users vs. Choosers&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If you’re in B2B, it’s important to understand that the people who choose to purchase your product (customers) are not the same as the users themselves. More importantly, when the choosers are not the users, you should prioritize different metrics:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;B2C SaaS&lt;/strong&gt; - Focus on &lt;a class="reference external" href="https://www.productplan.com/glossary/aarrr-framework/"&gt;Pirate&lt;/a&gt; or &lt;a class="reference external" href="https://www.interaction-design.org/literature/article/google-s-heart-framework-for-measuring-ux"&gt;Heart&lt;/a&gt; metrics.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;B2B&lt;/strong&gt; - Focus on user efficiency and effectiveness metrics. How long does it take them to do their job with your product, and how well can they do it?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="the-continuous-tech-product-improvement-cycle"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id9"&gt;The Continuous Tech-Product Improvement Cycle&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;This four-quadrant graph came up regularly throughout the course to describe the cycle of iterating and improving a tech product. A lot goes on in the image, with the right half focusing on dual-track development and the left half emphasizing vision and strategy.&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://www.fotonixx.com/images/product_leadership/continuous_tech_improvement_cycle.jpeg"&gt;&lt;img alt="/images/product_leadership/continuous_tech_improvement_cycle.jpeg" class="align-center" src="https://www.fotonixx.com/images/product_leadership/continuous_tech_improvement_cycle.jpeg" style="width: 400px;"&gt;&lt;/a&gt;
&lt;p&gt;The bottom-left quadrant stood out to me: Sense, Listen, and Learn. Effectively, you have three main sensing channels that result in new product opportunities:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Tactical&lt;/strong&gt; - Composed of metrics, observations, and customer interactions, this is the most obvious way to improve your project.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Strategic&lt;/strong&gt; - Your product has a vision, and it (hopefully) has a strategy to achieve that vision. This channel involves opportunities that help you hit that strategic goal.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Technological&lt;/strong&gt; - It’s essential to be aware of new technologies that can improve your product as well as the technical debt you currently carry.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;You need to maintain all three channels for a healthy product. For an analogy, imagine the human body and the following sensing channels: hunger, tiredness, and thirst. You can sleep as much as you want, but if you don't sleep or eat you'll eventually crash!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="products-composed-of-products"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id10"&gt;Products Composed of Products&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;One of the last topics of day one was “The Product Onion,” which tiered different product categories.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;End Products&lt;/strong&gt; - These product teams create solutions that address end customers and users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Customer Enabling Products&lt;/strong&gt; - These product teams create product websites and modules. They need to service both end customers and other product teams.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Employee Enabling/Product Team Enabling products&lt;/strong&gt; - These teams typically develop APIs that other product teams use to accomplish their jobs. They do not typically provide solutions to end customers.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;a class="reference external image-reference" href="https://www.fotonixx.com/images/product_leadership/product_onion.jpeg"&gt;&lt;img alt="/images/product_leadership/product_onion.jpeg" class="align-center" src="https://www.fotonixx.com/images/product_leadership/product_onion.jpeg" style="width: 400px;"&gt;&lt;/a&gt;
&lt;p&gt;It’s not a perfect analogy. There are exceptions (e.g., if an API becomes public and now an Employee Enabling Product Team also supports customers), but I appreciate the separation. My team needed to navigate this when separating our end product from the back-end services that enabled it.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="personas-and-interviews"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id32"&gt;Personas and Interviews&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="a-simple-product-interview-script"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id12"&gt;A Simple Product Interview Script&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;While a fan of &lt;a class="reference external" href="https://www.producttalk.org/2016/03/customer-interview-questions/"&gt;Teresa Torres'&lt;/a&gt; story-based Interview method to avoid cognitive bias, I understand that some people may be more comfortable interviewing with a pre-defined set of questions. This quick script is an excellent introduction to customer interviewing and lets the team hear what users think without biasing them.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Can you tell me a little bit about &amp;lt;Product&amp;gt;?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What do you use &amp;lt;Product&amp;gt; for?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What do you love about it? Can you give me an example?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;What do you hate about it? Can you give me an example?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you could wave a magic want and change anything about this product to make it better, what would it be?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Imagine you had that change, how would you use it?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;How do you do things today without this capability?&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;For Additional Reading&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.producttalk.org/"&gt;Continuous Discovery Habits&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.talkingtohumans.com/"&gt;Talking to Humans&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.dropbox.com/s/1mpek4fzs2qvq5r/Discovery%20Immersion%20QR%20Cards.pdf?dl=0"&gt;Product Discovery Recipes&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="personas-and-protopersonas"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id13"&gt;Personas and Protopersonas&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;A persona is an extensive (and time-consuming) distillation of customer research into a fictional person. Proto personas speed things up by leveraging assumptions about your users (which you must verify). There are loads of persona templates available, but the one we used had a photo and three categories:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;About (Descriptors)&lt;/strong&gt; - This can cover demographics, education, skills, and job roles&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Behavior (Verbs)&lt;/strong&gt; - What are some things this persona does? In our persona, “Susie” was responsible for sourcing the weekly snacks for her kid’s soccer team.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Motivations&lt;/strong&gt; - What are the pains and problems this persona experiences, what are some rewards?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;a class="reference external image-reference" href="https://www.fotonixx.com/images/product_leadership/user_personas.jpeg"&gt;&lt;img alt="/images/product_leadership/user_personas.jpeg" class="align-center" src="https://www.fotonixx.com/images/product_leadership/user_personas.jpeg" style="width: 400px;"&gt;&lt;/a&gt;
&lt;p&gt;I liked the mantra &lt;em&gt;Differences that make a difference&lt;/em&gt;. aka differences in personas should affect how that persona would use your product.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="personifying-a-service"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id14"&gt;Personifying a Service&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The classic user story template goes: “As XXX, I want to YYY so that ZZZ,” (this should really a guideline more than than a rule). During office hours, somebody asked: “Bots consume our APIs; how do we write user stories around them?” The answer: anthropomorphic personification (something I’ve only ever heard of in the context of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Discworld"&gt;Discworld&lt;/a&gt;).&lt;/p&gt;
&lt;p&gt;Effectively, the bot takes the user's role, and you personify it as though it was a living thing. It may work for you, it may not, but I thought it was a clever observation regardless.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="strategy"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id33"&gt;Strategy&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="where-you-will-play-and-how-you-will-win"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id16"&gt;Where You will Play and How you Will Win&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;There’s a slew of books on product strategy; one of the more famous is Richard Rumelt’s &lt;a class="reference external" href="http://goodbadstrategy.com/"&gt;Good Strategy/Bad Strategy&lt;/a&gt;. While I like the book, especially the concept of a strategic kernel, I’ll be the first to admit that it is &lt;strong&gt;dense&lt;/strong&gt; and can be a slog to get through.&lt;/p&gt;
&lt;p&gt;Here is a “cheat sheet” phrase for thinking of strategy: Where you’ll play and how you’ll win.&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Where You’ll Play&lt;/strong&gt; - Your strategy should identify and focus on a specific market, user persona, and problems you’re addressing.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;How You’ll Win&lt;/strong&gt; - You need to understand how your solution is better than alternatives, including desired outcomes and metrics.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;For Additional Reading&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="http://goodbadstrategy.com/"&gt;Good Strategy/Bad Strategy&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.lennysnewsletter.com/p/getting-better-at-product-strategy?utm_source=url"&gt;Getting Better at Product Strategy&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="customers-solve-business-problems-product-teams-solve-customer-opportunities"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id17"&gt;Customers Solve Business Problems; Product Teams Solve Customer Opportunities.&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Your organization probably has goals: earn $XXX in revenue, increase the sign-up rate by Y%, etc. The thing is, product teams cannot directly solve those problems. I’m not going to open my wallet and hand money to the CEO. Likewise, unless I plan to go the route of &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Wells_Fargo_account_fraud_scandal"&gt;Wells Fargo&lt;/a&gt;, I don’t have direct control over how many people sign up for a service.&lt;/p&gt;
&lt;p&gt;Instead, a Product team’s job is to solve customer opportunities. When we solve a customer opportunity, we hypothesize that doing so will have a business impact, but we’re still addressing business goals through the customers. &lt;a class="reference external" href="https://www.producttalk.org/2016/08/opportunity-solution-tree/"&gt;Opportunity Solution Trees&lt;/a&gt; are a great way to connect customer opportunities to business outcomes visually.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="user-focused-okrs"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id18"&gt;User-Focused OKRs&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Since a product team should focus on addressing user opportunities, it makes sense that any product team using an OKR (Objective and Key Results) goal framework should have user-focused objectives, not business impact objectives. Jeff Outlined a 3-step process for making user-focused OKRs, and I’ve added two steps that help bridge the gap from business impacts to user outcomes.&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;If your team uses &lt;a class="reference external" href="https://svpg.com/team-objectives-overview"&gt;OKRs as defined by Marty Cagan&lt;/a&gt;, your product team is responsible for one or more business-oriented key results in a given quarter, e.g., “reduce churn by X%.”&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Assuming you perform continuous discovery/regular user interviews, build an &lt;a class="reference external" href="https://www.producttalk.org/2016/08/opportunity-solution-tree/"&gt;Opportunity-Solutions Tree (OST)&lt;/a&gt; around that metric. Prioritize this opportunity space (see chapter 7 of Continuous Discovery Habits) and hone in on one target opportunity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Rewrite the opportunity as a quote from your target person. Humanizing the challenge helps to give it more context.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Write an ambitious statement that describes solving the opportunity. Make sure you capture the desired outcome and not a specific feature.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Identify 2-4 metrics that would show you are making progress towards solving the problem and their values that would indicate success. These are your Key Results.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="discovery-and-delivery"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id34"&gt;Discovery and Delivery&lt;/a&gt;&lt;/h3&gt;
&lt;div class="section" id="opportunity-canvas"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id20"&gt;Opportunity Canvas&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Opportunity Canvases are a structured format to vet opportunities and see assumptions. I haven’t adopted it personally but may give it a test-run in the future.&lt;/p&gt;
&lt;a class="reference external image-reference" href="https://www.fotonixx.com/images/product_leadership/opportunity_canvas.jpeg"&gt;&lt;img alt="/images/product_leadership/opportunity_canvas.jpeg" class="align-center" src="https://www.fotonixx.com/images/product_leadership/opportunity_canvas.jpeg" style="width: 400px;"&gt;&lt;/a&gt;
&lt;/div&gt;
&lt;div class="section" id="the-next-best-test"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id21"&gt;The Next Best Test&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Tests and experiments build confidence that your features provide actual user value, but they rarely need high-fidelity prototypes or shippable code. Experiments aim to test assumptions and progress you around the build-measure-learn loop as rapidly as possible. Disqualifying multiple ideas with short experiments is better than creating an expensive prototype of your first solution.&lt;/p&gt;
&lt;table class="table table-striped table-sm w-85 mx-auto"&gt;
&lt;colgroup&gt;
&lt;col style="width: 60%"&gt;
&lt;col style="width: 40%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;What are we trying to Learn?&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;What is the fastest way to learn it?&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Are we solving a meaningful customer problem?&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Talk to users&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Surveys, metrics, observations&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;When a user sees the solution will they want to try it?&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Smoke Test/ Landing Page Test&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Interview + Prototype&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Can we build our solution predictably?&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Talk to Developers&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Spike&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Can users easily learn to use it?&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Usability Test&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;Will users Keep using it and really get value?&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Built it, &lt;strong&gt;Not&lt;/strong&gt; to scale&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A/B Testing&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The above table outlines the recommended order of experiments to help rapidly build the confidence or kill a solution. Notice that the first question you should always answer is “Are we solving a meaningful customer problem.”&lt;/p&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;For Additional Reading&lt;/dt&gt;
&lt;dd&gt;&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://testingwithhumans.com/"&gt;Testing with Humans&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://giffconstable.com/2021/04/the-truth-curve-and-the-build-curve/"&gt;The Truth Curve and the Build Curve&lt;/a&gt;.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.amazon.com/Thinking-Bets-Making-Smarter-Decisions/dp/0735216355"&gt;Thinking In Bets&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="clarifying-prototype-fidelity"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id22"&gt;Clarifying Prototype Fidelity&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Inspired covers prototype fidelity in-depth: the higher a prototype’s fidelity, the less distinguishable it should be from your final software. The book also drives home to run usability tests with only high-fidelity prototypes. However, Jeff’s definition of fidelity argues that the fidelity of a prototype lies on 3-axes: visual, data, and functional. Depending on the test, a prototype should skew along one or more axes:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/product_leadership/prototype_axes.png" src="https://www.fotonixx.com/images/product_leadership/prototype_axes.png" style="width: 400px;"&gt;
&lt;p class="caption"&gt;My own attempt at mimicking Jeff's Sharpie + Highlighter drawing style&lt;/p&gt;
&lt;/div&gt;
&lt;dl class="simple"&gt;
&lt;dt&gt;Testing Desirability&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;The prototype should have high visual fidelity. We’re seeing if users are even interested in our solution. For that, you don’t need full functionality or accurate data.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;Testing Feasibility&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;To answer “can we build it,” your prototype should be highly functional but doesn’t need to have accurate data or be visually appealing.&lt;/p&gt;
&lt;/dd&gt;
&lt;dt&gt;Testing Usability&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Usability needs to have high functional and data fidelity. Notice that it does not &lt;em&gt;need&lt;/em&gt; to be visual. Over-prioritization of visual fidelity is a common hurdle we run into relying on tools like Figma: they look like the final product, but if the product has something like drag and drop interfaces or a drawing engine, Figma falls short or becomes expensive to prototype. A paper prototype can be just as valuable for usability testing!&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;/div&gt;
&lt;div class="section" id="shrink-your-releases-by-prioritizing-people-not-features"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id23"&gt;Shrink Your Releases by prioritizing people, not features&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;We almost ran out of time for the activity that spawned this takeaway! We revisited the famous “getting ready for work” story maps we’d created earlier in the week on the last day. As a team, Jeff challenged us to update the map, assuming you had slept late and only had 15 minutes to get ready. He challenged each group to remove unnecessary stories or add alternatives to hit the deadline.&lt;/p&gt;
&lt;p&gt;The assignment isn’t difficult when you fly solo, but since our entire team was working on one story map, there were tasks (get kids ready for school, feed the dog) that made it in even though I would not need to do them.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/product_leadership/condensed_story_map.png" src="https://www.fotonixx.com/images/product_leadership/condensed_story_map.png"&gt;
&lt;p class="caption"&gt;Despite my having neither children nor pets, they both made it into my teams "critical" story map&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Jeff asked after we regrouped if anybody had irrelevant stories on their map. He then suggested, “Wouldn’t it be easy just to ignore people who have dogs or kids and focus on solving the problem well for one persona?” This point ties through to planning a product release strategy! The goal of your roadmap should be to release early, often, and provide value for a specific persona. &lt;strong&gt;If a release roadmap is bloated or top-heavy, the best way to clean it up is to focus on one persona and delay implementing steps/features that are irrelevant to that persona.&lt;/strong&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="release-strategy-vs-development-strategy"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id24"&gt;Release Strategy vs. Development Strategy&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;&lt;a class="reference external" href="https://www.amazon.com/User-Story-Mapping-Discover-Product/dp/1491904909"&gt;User Story Mapping&lt;/a&gt; discusses breaking up a release into multiple stages: your tracer-bullet (prove functionality), mid-game, and late game. However, I missed that &lt;strong&gt;this is the team's development strategy, not part of the release strategy.&lt;/strong&gt;&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Release Strategy identifies the smallest successful release that addresses a customer opportunity.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Development Strategy prioritizes the order in which the delivery team tackles stories within a release to address feasibility risks and ensure on-time delivery.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Development strategy lies in the domain of software engineers and agile delivery. It even came up that at Atlassian (creators of Jira), the Product Managers are not involved with the development strategy for that reason. While I might not go that far, I agree that once the release strategy is fleshed out, the software team knows how to build things and should have a level of autonomy.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="release-to-earn-vs-release-to-learn"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/jpat-product-leadership-review/#id25"&gt;Release to Earn vs. Release to Learn&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Release to Earn is what it sounds like, production-ready that earns your company revenue. However, release to learn is software that is “Just enough to fulfill the narrative but far from feature complete.” Release to learn often involves A/B testing or releasing to a subset of users.&lt;/p&gt;
&lt;p&gt;The concept is covered extensively in &lt;a class="reference external" href="http://theleanstartup.com/book"&gt;The Lean Startup&lt;/a&gt;. However, it’s worth noting to emphasize that delivery can be part of your discovery work while still providing incremental value to (a subset) users! It also ties back to release strategy: every release should be identified as a “release to learn” or “release to earn” release with hypothesized outcomes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;/div&gt;</description><category>agile</category><category>product management</category><category>product owner</category><category>scrum</category><category>story mapping</category><guid>https://www.fotonixx.com/posts/jpat-product-leadership-review/</guid><pubDate>Fri, 25 Feb 2022 00:38:17 GMT</pubDate></item><item><title>Design a Camera with Python and PyRayT: Part One</title><link>https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/camera_design_with_pyrayt/preview_image.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;Have you ever cut open a camera lens to look at what's inside? I don't blame you if you haven't, lenses are ridiculously expensive and it's a one way operation. However, if you look at a &lt;a class="reference external" href="https://www.ephotozine.com/article/this-cutaway-diagram-shows-the-inside-of-a-dslr-30546"&gt;picture of a cross-section&lt;/a&gt;, you'd see that the "lens" is actually made of upwards of a dozen individual lenses, each one doing its part to create a clear, error free image. Typically, understanding what's going on in the camera lens requires exhaustive calculations or expensive lens design software costing upwards of $10,000 for a single user. My annoyance at this price barrier (as somebody who uses those tools professionally) is what sparked me to create &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;PyRayT&lt;/a&gt;, a free and open source generic ray tracer that pairs with the &lt;a class="reference external" href="https://scipy.org/install.html"&gt;Scientific Python stack&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To celebrate &lt;a class="reference external" href="https://pyrayt.readthedocs.io/en/latest/release.html#v0-3-0"&gt;PyRayT's v0.3.0 release&lt;/a&gt;, I'm going to walk through how it can be used to design and optimize a multi-lens camera. This first part of the series will cover why lenses need to be so intricate, and what happens if you tried to make a camera out of a single lens instead.&lt;/p&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#why-are-camera-lenses-so-complex" id="id2"&gt;Why Are Camera Lenses so Complex?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#getting-up-and-running-with-pyrayt" id="id3"&gt;Getting Up and Running with PyRayT&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#creating-a-simple-camera" id="id4"&gt;Creating a Simple Camera&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#thin-lenses" id="id5"&gt;Thin Lenses&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#apertures-and-baffles" id="id6"&gt;Apertures and Baffles&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#our-first-ray-trace" id="id7"&gt;Our First Ray Trace&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#characterizing-lens-performance" id="id8"&gt;Characterizing Lens Performance&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#spherical-aberrations" id="id9"&gt;Spherical Aberrations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#chromatic-aberrations" id="id10"&gt;Chromatic Aberrations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#coma-aberrations" id="id11"&gt;Coma Aberrations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#next-steps" id="id12"&gt;Next Steps&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="quick-links"&gt;
&lt;p class="topic-title"&gt;Quick Links&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#why-are-camera-lenses-so-complex" id="id13"&gt;Why Are Camera Lenses so Complex?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#getting-up-and-running-with-pyrayt" id="id14"&gt;Getting Up and Running with PyRayT&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#creating-a-simple-camera" id="id15"&gt;Creating a Simple Camera&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#thin-lenses" id="id16"&gt;Thin Lenses&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#apertures-and-baffles" id="id17"&gt;Apertures and Baffles&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#our-first-ray-trace" id="id18"&gt;Our First Ray Trace&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#characterizing-lens-performance" id="id19"&gt;Characterizing Lens Performance&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#spherical-aberrations" id="id20"&gt;Spherical Aberrations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#chromatic-aberrations" id="id21"&gt;Chromatic Aberrations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#coma-aberrations" id="id22"&gt;Coma Aberrations&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#next-steps" id="id23"&gt;Next Steps&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="why-are-camera-lenses-so-complex"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id13"&gt;Why Are Camera Lenses so Complex?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;If you rack your brain to recall highschool physics there's probably two things you remember about lenses:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;A ray parallel to the optical axis converges onto the focal point.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A ray going through the center of the lens is unmodified.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;With these rules you can trace simple diagrams showing how object images are formed by lenses.&lt;/p&gt;
&lt;div class="figure mb-4"&gt;
    &lt;div class="figure-image"&gt;
        &lt;img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/71/Lens3.svg/1920px-Lens3.svg.png" alt="Thin lens ray trace"&gt;
    &lt;/div&gt;
    &lt;div class="figure-caption text-center"&gt;
        Source: &lt;a href="https://en.wikipedia.org/wiki/Lens#Imaging_properties"&gt;Wikipedia&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Notice how in the above image every point in the object corresponds to exactly one point in the image, so in theory a single lens camera should be able to capture perfect reproductions of objects we want to image. Why then, does a picture from that camera end up looking like the one below?&lt;/p&gt;
&lt;div class="figure mb-4 container"&gt;
    &lt;div class="figure-image row mb-2"&gt;
        &lt;div class="col-md-6 col-sm-12 mb-2"&gt;
            &lt;img src="https://www.opticsthewebsite.com/Content/images/aber/USAF512.png" alt="Ideal Image"&gt;
        &lt;/div&gt;
        &lt;div class="col-md-6 col-sm-12"&gt;
            &lt;img src="https://www.opticsthewebsite.com/Content/images/aber/USAF512_largeap4.png" alt="Distorted Image"&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="figure-caption text-center"&gt;
        An ideal image (left) and the resulting image as captured by a simple camera (right)
        &lt;br&gt;
        Source: &lt;a href="https://www.opticsthewebsite.com/SeidelSimulation"&gt;www.opticsthewebsite.com&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;These distortions between the object and image, called &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Optical_aberration"&gt;optical aberrations&lt;/a&gt;, are caused by  &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Taylor_series"&gt;higher order terms&lt;/a&gt; that are typically ignored when calculating lenses by hand using the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Paraxial_approximation"&gt;paraxial approximation&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The goal of any camera design is to minimize these aberrations to the extent  that they are not noticed by the end user. A single lens only has so many variables about it you can change, but adding multiple lenses to a system gives optical engineers additional degrees of freedom for the minimization (often by minimizing &lt;a class="reference external" href="https://www.opticsthewebsite.com/Aberrations"&gt;Siedel Aberration Coefficients&lt;/a&gt;, which are beyond the scope of the article).&lt;/p&gt;
&lt;p&gt;So how bad could a single lens imager really be? And how do you improve the quality with additional lenses? Let's answer the first question by quantifing common aberrations for a single lens system.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-up-and-running-with-pyrayt"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id14"&gt;Getting Up and Running with PyRayT&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As mentioned above, hand calculations for lenses rely on approximations that don't capture aberrations. Fortunately, numeric ray tracers don't suffer from the same limitations which is why they're so valuable for accurate lens design. Since this article is about showcasing features of &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;PyRayT&lt;/a&gt;, that's the ray tracer we'll be using. You can install the latest stable package from pip.&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a name="rest_code_a80acf19a1bb435caa5174843d526cda-1"&gt;&lt;/a&gt;py -m pip install pyrayt
&lt;/pre&gt;&lt;p&gt;You can also check out the &lt;a class="reference external" href="https://pyrayt.readthedocs.io/en/latest/tutorial.html"&gt;Getting Started Guide&lt;/a&gt; and list of &lt;a class="reference external" href="https://pyrayt.readthedocs.io/en/latest/reference/components.html"&gt;built-in optical components&lt;/a&gt; for a general overview.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-a-simple-camera"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id15"&gt;Creating a Simple Camera&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;To design our single-lens camera we only need two specs: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Optical_power"&gt;system power&lt;/a&gt; and f/# (&lt;a class="reference external" href="https://en.wikipedia.org/wiki/F-number"&gt;F-number&lt;/a&gt;). We want the system to have a focal length of 50mm, and since a lens' power is the inverse of focal length, our system power is 0.02. Our f/# will be 2.4, meaning the focal length should be 2.4x larger than the entrance aperture.&lt;/p&gt;
&lt;div class="section" id="thin-lenses"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id16"&gt;Thin Lenses&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Our lens is the only component that contributes to system power, so the power of the lens has to equal the desired power of our system. To calculate lens power we'll use the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Lens#Lensmaker's_equation"&gt;lensmaker's equation&lt;/a&gt;.&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
P_{lens} = (n_{lens} -1)[\frac{1}{R_1}-\frac{1}{R_2}+\frac{(n_{lens}-1)d}{n R_1 R_2}]
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Since we're doing the calculation by hand we'll make a couple approximations to simplify things: (1) the radii of curvature are equal and opposite (resulting in a biconvex lens), and (2) the thickness is small enough that we can discard the final term. Later we'll numerically optimize the entire system to correct for focus, but these approximations give us a good starting point.&lt;/p&gt;
&lt;p&gt;The simplified equation then becomes:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
R_{lens} =\frac{2(n_{lens} -1)}{P_{lens}}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Since the refractive index of most glasses is ~1.5, this means that our radius of curvature for a biconvex lens is equal to the &lt;em&gt;inverse of the lenses power&lt;/em&gt;, which is also the focal length of the system!&lt;/p&gt;
&lt;p&gt;Now we can construct our lens and visualize it with the &lt;code&gt;draw&lt;/code&gt; function in the &lt;code&gt;tinygfx&lt;/code&gt; package (installed as part of the PyRayT distribution).&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# import the Ray Tracer Package&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyrayt&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-3"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;pyrayt.materials&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;matl&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-4"&gt;&lt;/a&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;tinygfx.g3d.renderers&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;draw&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-5"&gt;&lt;/a&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-6"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# All spatial units are mm&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;lens_diameter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;30&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-8"&gt;&lt;/a&gt;&lt;span class="n"&gt;lens_thickness&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-9"&gt;&lt;/a&gt;&lt;span class="n"&gt;system_focus&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt; &lt;span class="c1"&gt;# The focus of the system&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-10"&gt;&lt;/a&gt;&lt;span class="n"&gt;f_num&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;2.4&lt;/span&gt; &lt;span class="c1"&gt;# f-number of system&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-12"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Creating a simple Lens&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-13"&gt;&lt;/a&gt;&lt;span class="n"&gt;lens_material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glass&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"ideal"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-14"&gt;&lt;/a&gt;&lt;span class="n"&gt;lens_radius&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="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lens_material&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;index_at&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.633&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="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;system_focus&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-15"&gt;&lt;/a&gt;&lt;span class="n"&gt;lens&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;thick_lens&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;r1&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lens_radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-17"&gt;&lt;/a&gt;    &lt;span class="n"&gt;r2&lt;/span&gt;&lt;span class="o"&gt;=-&lt;/span&gt;&lt;span class="n"&gt;lens_radius&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-18"&gt;&lt;/a&gt;    &lt;span class="n"&gt;thickness&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lens_thickness&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-19"&gt;&lt;/a&gt;    &lt;span class="n"&gt;aperture&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-20"&gt;&lt;/a&gt;    &lt;span class="n"&gt;material&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;lens_material&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-21"&gt;&lt;/a&gt;
&lt;a name="rest_code_c44c6328333d4e949b1bf3a389b3c841-22"&gt;&lt;/a&gt;&lt;span class="n"&gt;draw&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lens&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;img alt="/images/camera_design_with_pyrayt/biconvex_lens.png" class="align-center" src="https://www.fotonixx.com/images/camera_design_with_pyrayt/biconvex_lens.png"&gt;
&lt;p&gt;A lens is not too impressive by itself. Let's add the remaining parts of our system so we can start running ray traces!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="apertures-and-baffles"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id17"&gt;Apertures and Baffles&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;There's two more pieces we need in order to make the camera: an aperture to block rays that exceed our f/# and an imager placed on our camera's focal plane. From a modeling perspective both will be accomplished with variations of PyRayT's &lt;code&gt;baffle&lt;/code&gt;. Baffle's are 2D planes that absorb all light incident on them, perfect for modeling sensors as well as ideal beam-stops. For the imager we create a square baffle the same size as our lens and move it to the focal plane.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_66922e03cfeb40189dd8b5a4f2d509c2-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;imager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;baffle&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system_focus&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Our aperture can be thought of as a "baffle with a hole", where the hole is large enough to only let in rays with a cone angle specified by our f/#. &lt;code&gt;pyrayt&lt;/code&gt; and &lt;code&gt;tinygfx&lt;/code&gt; create arbitrary shapes via &lt;a class="reference external" href="https://www.fotonixx.com/posts/efficient-csg/"&gt;constructive solids&lt;/a&gt; meaning an aperture is a baffle with the center shape subtracted from it. The convenience function &lt;code&gt;aperture&lt;/code&gt; does just this, creating a baffle with an arbitrarily shaped hole in the middle.&lt;/p&gt;
&lt;p&gt;The diameter of the aperture that gives us the desired f/# depends on where in the system the aperture is located. If we place it half-way between the lens and the focal plane, the diameter of the opening has to be:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
d_{ap}=\frac{1}{2*P_{sys}}*\frac{1}{f_\#}
\end{equation*}
&lt;/div&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;aperture_position&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;system_focus&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;
&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;aperture_diameter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;aperture_position&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="n"&gt;f_num&lt;/span&gt;
&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;aperture&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aperture&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="c1"&gt;# make a square baffle&lt;/span&gt;
&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;aperture_size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;aperture_diameter&lt;/span&gt; &lt;span class="c1"&gt;# put a circular opening in the center&lt;/span&gt;
&lt;a name="rest_code_7cfb9da886b140adb2131ca232e3d073-7"&gt;&lt;/a&gt;    &lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;aperture_position&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;div class="section" id="our-first-ray-trace"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id18"&gt;Our First Ray Trace&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;With our components defined we're ready to simulate. The only thing we need is a test source that generates rays to trace through the system. For that we'll use PyRayT's &lt;code&gt;LineOfRays&lt;/code&gt; which generates a set of linearly spaced rays projected towards the +x axis. The last step is to load all the components into a &lt;code&gt;RayTracer&lt;/code&gt; object and run the &lt;code&gt;trace&lt;/code&gt; function.&lt;/p&gt;
&lt;div class="class alert alert-success pb-0 docutils container"&gt;
&lt;div class="alert-header"&gt;
&lt;i class="fas fa-info-circle"&gt;&lt;/i&gt; Note
&lt;/div&gt;
&lt;hr class="mt-0 mb-1"&gt;&lt;p&gt;Almost all of the sources used to characterize our system will be parallel bundles of rays at various angles. This is because we're assuming the camera is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Infinity_focus"&gt;focused at infinity&lt;/a&gt;, where any angular deviation between sets of rays originating from the same point are effectively zero.&lt;/p&gt;
&lt;/div&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_51e4386d19b84d00bc157c52f95c158a-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Create a Parallel ray set&lt;/span&gt;
&lt;a name="rest_code_51e4386d19b84d00bc157c52f95c158a-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LineOfRays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wavelength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mf"&gt;0.633&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_51e4386d19b84d00bc157c52f95c158a-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_51e4386d19b84d00bc157c52f95c158a-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RayTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;aperture&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imager&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;a name="rest_code_51e4386d19b84d00bc157c52f95c158a-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_rays_per_source&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;a name="rest_code_51e4386d19b84d00bc157c52f95c158a-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The results of a trace is a &lt;a class="reference external" href="https://pandas.pydata.org/"&gt;Pandas&lt;/a&gt; dataframe which stores information about the ray at every intersection of the simulation. However, for now we'd rather just visualize the ray trace, which is done with the &lt;code&gt;show&lt;/code&gt; function.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# import matplotlib so we can manipulate the axis&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-2"&gt;&lt;/a&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nn"&gt;matplotlib.pyplot&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nn"&gt;plt&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;init_figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Figure&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Axes&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-5"&gt;&lt;/a&gt;    &lt;span class="sd"&gt;"""&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-6"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    Convenience function to generate an axis with a set size&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-7"&gt;&lt;/a&gt;&lt;span class="sd"&gt;    """&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;figsize&lt;/span&gt; &lt;span class="o"&gt;=&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;8&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-9"&gt;&lt;/a&gt;    &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gca&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-10"&gt;&lt;/a&gt;    &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;grid&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-11"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-12"&gt;&lt;/a&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-13"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# set up the figure and axis&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-14"&gt;&lt;/a&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;init_figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-15"&gt;&lt;/a&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"distance (mm)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-16"&gt;&lt;/a&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"distance (mm)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-17"&gt;&lt;/a&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-19"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# display the ray trace&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-20"&gt;&lt;/a&gt;&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ray_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-22"&gt;&lt;/a&gt;    &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-23"&gt;&lt;/a&gt;    &lt;span class="n"&gt;view&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'xy'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_937cbb50c80b4b839d60895b0456ff33-24"&gt;&lt;/a&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;img alt="/images/camera_design_with_pyrayt/single_lens_raytrace.png" class="align-center" src="https://www.fotonixx.com/images/camera_design_with_pyrayt/single_lens_raytrace.png"&gt;
&lt;p&gt;Looks like our lens is doing its job! All rays that transmit through the aperture are focused to an approximate point at the focal distance, and any ray angle that exceeds our f/# is blocked. Unfortunately since the aperture and imager are 2D objects, they don't show up in the ray trace, but we know that they are there because rays terminate on their surfaces.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="characterizing-lens-performance"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id19"&gt;Characterizing Lens Performance&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A picture may be worth 1000 words, but when it comes to analyzing our lens' performance data is key. Using the results dataframe we can explore the &lt;a class="reference external" href="https://pyrayt.readthedocs.io/en/latest/generated/pyrayt.html?highlight=RaySet#pyrayt._pyrayt.RaySet"&gt;RaySet&lt;/a&gt; metadata of each ray as they travel through the system, and we'll use that information to see how our single lens design holds up against common imaging aberrations: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Spherical_aberration"&gt;spherical&lt;/a&gt;, &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Chromatic_aberration"&gt;chromatic&lt;/a&gt;, and &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Coma_(optics)"&gt;coma&lt;/a&gt;. As mentioned above, characterizing the system with Seidel coefficients is beyond the scope of the article, instead we'll use &lt;a class="reference external" href="https://matplotlib.org/"&gt;Matplotlib&lt;/a&gt; to generate plots of the system focus across different parameters.&lt;/p&gt;
&lt;div class="section" id="spherical-aberrations"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id20"&gt;Spherical Aberrations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Spherical lenses don't actually focus light to a perfect point. In fact, the focal point is a function of the radius where the light enters the lens (in our case the position on the y-axis where the ray originates). We can easily visualize the spherical aberrations by creating a helper function that generates a set of rays along the y-axis, and calculates where each ray intercepts the x-axis.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;spherical_aberration&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ray_origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;max_radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_points&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-3"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# the souce is a line of rays only on the +y axis. It's slightly shifted so zero is not a point&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-4"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# as it would focus at infinity&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-5"&gt;&lt;/a&gt;    &lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LineOfRays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;max_radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ray_origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;max_radius&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;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-8"&gt;&lt;/a&gt;    &lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RayTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-9"&gt;&lt;/a&gt;    &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_rays_per_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_points&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-10"&gt;&lt;/a&gt;    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-12"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Since we don't have the actual imager as a variable in the function&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-13"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# assume it is the last thing a ray intersect with, meaning the rays that hit it have the&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-14"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# highest generation&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;imager_rays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-16"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-17"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Intercept is calculated using the tilt for each ray, with is a normalized vector representing&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-18"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# the direction the ray is travelling&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-19"&gt;&lt;/a&gt;    &lt;span class="n"&gt;intercept&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'x_tilt'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'y0'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'y_tilt'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'x0'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-20"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-21"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# the original radii&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-22"&gt;&lt;/a&gt;    &lt;span class="n"&gt;radii&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;]&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="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'id'&lt;/span&gt;&lt;span class="p"&gt;]))][&lt;/span&gt;&lt;span class="s1"&gt;'y0'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-23"&gt;&lt;/a&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-24"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# create a new dataframe with the aberration metrics&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-25"&gt;&lt;/a&gt;    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'radius'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;radii&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="s1"&gt;'focus'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;asarray&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;)})&lt;/span&gt;
&lt;a name="rest_code_f255715bdd3b4f60a3b0897fd64658ba-26"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/pre&gt;&lt;img alt="/images/camera_design_with_pyrayt/spherical_aberration_chart_single_lens.png" class="align-center" src="https://www.fotonixx.com/images/camera_design_with_pyrayt/spherical_aberration_chart_single_lens.png" style="width: 600px;"&gt;
&lt;p&gt;Using the function on our single-lens system yields the above plot, showing that the focal length of the lens is changing by almost 10% based on the radius alone! This aberration would cause our single-lens images to come out "blurry" even when the imager is aligned to the focal plane.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="https://www.opticsthewebsite.com/Content/images/aber/USAF512_largeap3.png" src="https://www.opticsthewebsite.com/Content/images/aber/USAF512_largeap3.png" style="width: 300px;"&gt;
&lt;p class="caption"&gt;An image suffering from spherical aberrations. Source: &lt;a class="reference external" href="https://www.opticsthewebsite.com/SeidelSimulation"&gt;www.opticsthewebsite.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Speaking of the focal plane, we also see that the focus of our lens is ~52mm instead of the 50 we calculated, due the the thick lens portion of the lensmaker's equation we chose to ignore.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="chromatic-aberrations"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id21"&gt;Chromatic Aberrations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unlike spherical aberrations, chromatic aberrations &lt;em&gt;are&lt;/em&gt; explained by the lensmaker's equation: the focal point of the lens depends on the refractive index of the lens' material. Real materials don't have a constant refractive index; instead, the refractive index is a function of wavelength. This effect, called &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Dispersion_(optics)"&gt;dispersion&lt;/a&gt;, is more often associated with the reason prisms split white light into a rainbow of colors. In our case it means our lens will have a wavelength dependent focus.&lt;/p&gt;
&lt;p&gt;The same way we wrote a function to characterize spherical aberrations, we can write one to quantify chromatic aberration:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;chromatic_abberation&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ray_origin&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;test_radius&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="nb"&gt;float&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;wavelengths&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-2"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# create a set of sources for every wavelength of light&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;sources&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-4"&gt;&lt;/a&gt;        &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LineOfRays&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="n"&gt;wavelength&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-5"&gt;&lt;/a&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_y&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;test_radius&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-6"&gt;&lt;/a&gt;        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ray_origin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-7"&gt;&lt;/a&gt;        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;wavelengths&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-9"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# Create the ray tracer and propagate&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-10"&gt;&lt;/a&gt;    &lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RayTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sources&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;system&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_rays_per_source&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;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-14"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;#filter the rays that intersect the imager&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;imager_rays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-16"&gt;&lt;/a&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-17"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# calculate intercept of the imager rays with the x-axis and form into a dataframe&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-18"&gt;&lt;/a&gt;    &lt;span class="n"&gt;intercept&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'x_tilt'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'y0'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'y_tilt'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'x0'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-19"&gt;&lt;/a&gt;    &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pd&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DataFrame&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="s1"&gt;'wavelength'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'wavelength'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="s1"&gt;'focus'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;intercept&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-20"&gt;&lt;/a&gt;
&lt;a name="rest_code_65de1f71c00b44238b1a4f1bb44b6dbf-21"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;If we run this function on our current system the results will say that every wavelength has the exact same focus! This is because we made the lens out of an "ideal" glass with a refractive index (n) of 1.5. Let's replace our lens with one made of a popular &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Crown_glass_(optics)"&gt;crown glass&lt;/a&gt; instead:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_533ef0308f5e4d12952ac11101c08f9e-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;lens_material&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;matl&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;glass&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"BK7"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="c1"&gt;# Update this line of the lens definition&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Running the function on our newly dispersive system shows a focal length shift of ~1mm (2%) across the visible spectrum.&lt;/p&gt;
&lt;img alt="/images/camera_design_with_pyrayt/chromatic_aberration_chart_single_lens.png" class="align-center" src="https://www.fotonixx.com/images/camera_design_with_pyrayt/chromatic_aberration_chart_single_lens.png" style="width: 600px;"&gt;
&lt;p&gt;An image taken with this lens would result in sharp edges in our photos having a 'rainbow' effect. Interestingly, this aberration is sometimes sought after for artistic effect, going as far as being including as a graphics setting in id's &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Doom_(2016_video_game)"&gt;2016 Doom reboot&lt;/a&gt;.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="https://upload.wikimedia.org/wikipedia/commons/6/66/Chromatic_aberration_%28comparison%29.jpg" src="https://upload.wikimedia.org/wikipedia/commons/6/66/Chromatic_aberration_%28comparison%29.jpg" style="width: 300px;"&gt;
&lt;p class="caption"&gt;An image with and without chromatic aberration. Source: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Chromatic_aberration"&gt;wikipedia.org&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="coma-aberrations"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id22"&gt;Coma Aberrations&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The last aberration we'll quantify is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Coma_(optics)"&gt;coma&lt;/a&gt;. Instead of being a change in focal length, coma is a change mangification vs. angle of incidence on the system. The name comes from the fact that a point imaged with a system suffering from coma looks like the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Coma_(cometary)"&gt;coma of a comet&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The easiest way to visualize coma is to plot a &lt;code&gt;LineOfRays&lt;/code&gt; hitting the lens at non-normal incidence. In a coma free imager, the distribution of ray-imager intersections should be symmetrically distributed about the central ray.&lt;/p&gt;
&lt;p&gt;While we &lt;em&gt;could&lt;/em&gt; generate three sources at three distinct angles and run a single ray trace, we're instead going to leverage PyRayT's &lt;a class="reference external" href="https://pyrayt.readthedocs.io/en/latest/generated/pyrayt.html?highlight=pin#pyrayt._pyrayt.pin"&gt;pin&lt;/a&gt; function to manipulate the angle of the lens. Since the position of the lens is a property of the lens itself, updating the position without remembering to undo it later will effect calculations down the line. the &lt;code&gt;pin&lt;/code&gt; &lt;a class="reference external" href="https://realpython.com/python-with-statement/"&gt;context manager&lt;/a&gt; takes care of this for us by undoing any transformations of pinned objects when the context manager exits.&lt;/p&gt;
&lt;p&gt;The main advantage of rotating the lens instead of the source is that the incoming rays stay perpendicular to the imager, and nominally centered about y=0.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-1"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Create the system&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;source&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;components&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;LineOfRays&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mf"&gt;0.9&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;lens_diameter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;move_x&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;tracer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;RayTracer&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;source&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;lens&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;imager&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_rays_per_source&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1001&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;angles&lt;/span&gt; &lt;span class="o"&gt;=&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;6&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;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;fig&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;init_figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;for&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;angle&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;enumerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angles&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-9"&gt;&lt;/a&gt;    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;pyrayt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pin&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;lens&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-10"&gt;&lt;/a&gt;        &lt;span class="c1"&gt;# pin the lens in place so its rotation resets between iterations&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-11"&gt;&lt;/a&gt;        &lt;span class="n"&gt;lens&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;rotate_z&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-12"&gt;&lt;/a&gt;        &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tracer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;trace&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-13"&gt;&lt;/a&gt;    &lt;span class="n"&gt;imager_rays&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loc&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'generation'&lt;/span&gt;&lt;span class="p"&gt;])]&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-14"&gt;&lt;/a&gt;    &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hist&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;imager_rays&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s1"&gt;'y1'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;bins&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;21&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;label&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;angle&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;-degree incidence"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;density&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;alpha&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mf"&gt;0.8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-15"&gt;&lt;/a&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-16"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# plot labels&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-17"&gt;&lt;/a&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_xlabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"position along Y-axis (mm)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-18"&gt;&lt;/a&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_ylabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"density (AU)"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-19"&gt;&lt;/a&gt;&lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_label&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Focal Spot Distribution vs. Angle of Incidence"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-20"&gt;&lt;/a&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-21"&gt;&lt;/a&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;legend&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_b3a7cd2038024c969d1e0038754605e5-22"&gt;&lt;/a&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;img alt="/images/camera_design_with_pyrayt/coma_raytrace.png" class="align-center" src="https://www.fotonixx.com/images/camera_design_with_pyrayt/coma_raytrace.png" style="width: 600px;"&gt;
&lt;img alt="/images/camera_design_with_pyrayt/coma_histogram.png" class="align-center" src="https://www.fotonixx.com/images/camera_design_with_pyrayt/coma_histogram.png" style="width: 600px;"&gt;
&lt;p&gt;Just looking at the ray trace we can see the angled rays don't come to any central focus, and the histogram has a strong skew towards the -y axis. Like spherical aberrations, coma will cause our images to blur; however, the distortion is worse the further you are from the center of the imager, with the middle of the image remaining sharp.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="https://www.opticsthewebsite.com/Content/images/aber/USAF512_largeap4.png" src="https://www.opticsthewebsite.com/Content/images/aber/USAF512_largeap4.png" style="width: 300px;"&gt;
&lt;p class="caption"&gt;An image suffering from coma aberrations. Source: &lt;a class="reference external" href="https://www.opticsthewebsite.com/SeidelSimulation"&gt;www.opticsthewebsite.com&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="next-steps"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/#id23"&gt;Next Steps&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This wraps up the first part of our camera design. It might not seem like much, but understanding the limitations of a simple system helps justify why we go through the process of desiging a complex one. The full &lt;a class="reference external" href="https://gist.github.com/rfrazier716/211516bfba7a3795c3f5d00b5e5fe53b"&gt;Jupyter notebook for the design is on GitHub&lt;/a&gt;, and next post I'll show how with just one additional lens, we can drastically minimize all three of the above aberrations to help our camera generate a cleaner final image! In the mean time feel free to explore the design and see how much optimization you can do with just one lens:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;What about the design changes if you pick a different material?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;If you break the symmetry between the front and back surface can you reduce aberrations?&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Is there a way to reduce chromatic aberration only by changing the physical dimensions (not material) of the lens?&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;&lt;/div&gt;</description><guid>https://www.fotonixx.com/posts/design-a-camera-with-python-and-pyrayt/</guid><pubDate>Fri, 06 Aug 2021 01:04:29 GMT</pubDate></item><item><title>Writing an (Overly) Idiomatic Fizzbuzz with Rust</title><link>https://www.fotonixx.com/posts/rust-fizzbuzz/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/rust_fizzbuzz/preview_image.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;The last few months have been a whirlwind exposure to &lt;a class="reference external" href="https://www.rust-lang.org/"&gt;Rust&lt;/a&gt;. It started when I was looking for a systems language to speed up pieces of &lt;a class="reference external" href="https://pyrayt.readthedocs.io"&gt;PyRayT&lt;/a&gt; that was more general than &lt;a class="reference external" href="https://cython.org/"&gt;Cython&lt;/a&gt;, but not C/C++ (which I have my own love/hate relationship with). After reading the excellently written &lt;a class="reference external" href="https://doc.rust-lang.org/book/"&gt;Rust Book&lt;/a&gt; I was hooked on the language, using it for a couple CLIs, a &lt;a class="reference external" href="https://github.com/rfrazier716/mongo_warp"&gt;webserver&lt;/a&gt;, and even going through old &lt;a class="reference external" href="https://adventofcode.com/"&gt;Advent of Code&lt;/a&gt; puzzles to get more practice.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="https://i.imgflip.com/5fy2ea.jpg" src="https://i.imgflip.com/5fy2ea.jpg" style="width: 500px;"&gt;
&lt;p class="caption"&gt;This is slowly becoming my reply to all things software related&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;This post isn't going to be a gushing review of Rust (though as &lt;a class="reference external" href="https://insights.stackoverflow.com/survey/2020#technology-most-loved-dreaded-and-wanted-languages-loved"&gt;2020's most loved language&lt;/a&gt; you won't be hard pressed to find one of those either). Instead, it's sparked from an article I saw on &lt;a class="reference external" href="https://this-week-in-rust.org/"&gt;This Week in Rust&lt;/a&gt; back in June about writing an &lt;a class="reference external" href="https://shane-o.dev/blog/binary-search-rust"&gt;idiomatic binary search&lt;/a&gt;. The binary search is a well known algorithm, which got me thinking: what's another well known program I could use to practice writing idiomatic code? The answer: &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Fizz_buzz"&gt;Fizzbuzz&lt;/a&gt;, the programming puzzle commonly used in interviews to make sure the candidate actually knows what a for loop is.&lt;/p&gt;
&lt;p&gt;In this post I'll be starting with a standard Fizzbuzz solution, and polishing it up to take full advantage of all the features and programming style Rust offers.&lt;/p&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#what-makes-code-idiomatic" id="id1"&gt;What Makes Code Idiomatic?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#the-basic-fizzbuzz" id="id2"&gt;The Basic Fizzbuzz&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#make-me-a-match" id="id3"&gt;Make Me a Match&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#getting-idiomatic" id="id4"&gt;Getting Idiomatic&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#idiomatic-testing" id="id5"&gt;Idiomatic Testing???&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#generic-traits-and-monomorphization" id="id6"&gt;Generic Traits and Monomorphization&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#going-off-the-deep-end" id="id7"&gt;Going off the Deep End&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#update-why-no-love-for-enum" id="id8"&gt;Update - Why no Love for Enum?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="quick-links"&gt;
&lt;p class="topic-title"&gt;Quick Links&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#what-makes-code-idiomatic" id="id9"&gt;What Makes Code Idiomatic?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#the-basic-fizzbuzz" id="id10"&gt;The Basic Fizzbuzz&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#make-me-a-match" id="id11"&gt;Make Me a Match&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#getting-idiomatic" id="id12"&gt;Getting Idiomatic&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#idiomatic-testing" id="id13"&gt;Idiomatic Testing???&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#generic-traits-and-monomorphization" id="id14"&gt;Generic Traits and Monomorphization&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#going-off-the-deep-end" id="id15"&gt;Going off the Deep End&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#update-why-no-love-for-enum" id="id16"&gt;Update - Why no Love for Enum?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="what-makes-code-idiomatic"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id9"&gt;What Makes Code Idiomatic?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Before diving into the &lt;em&gt;how&lt;/em&gt;, it's worth covering &lt;em&gt;what&lt;/em&gt; idiomatic code actually is. Outside of coding context, idiomatic means "using, containing, or denoting expressions that are natural to a native speaker." When discussing idiomatic programming, it means the program leverages features unique to the language to accomplish the task. Coming from &lt;a class="reference external" href="https://www.python.org/"&gt;Python&lt;/a&gt;, I would hear this as writing "Pythonic" code (list comprehension, generators, etc.).&lt;/p&gt;
&lt;p&gt;Idiomatic Rust should leverage Rust's unique features such as match, traits, iterators, and ownership. Since I'm still learning Rust every day, I use the linter &lt;a class="reference external" href="https://github.com/rust-lang/rust-clippy"&gt;Clippy&lt;/a&gt;, to catch common mistakes and recommend idiomatic alternatives!&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="the-basic-fizzbuzz"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id10"&gt;The Basic Fizzbuzz&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The goal of Fizzbuzz is simple:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Write a short program that prints each number from 1 to 100 on a new line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each multiple of 3, print "Fizz" instead of the number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For each multiple of 5, print "Buzz" instead of the number.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;For numbers which are multiples of both 3 and 5, print "FizzBuzz" instead of the number.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The first bullet screams "for loop" while the next three are conditional (if) statements. With that in mind we'll write our simplest solution relying on a series of if-else statements and the modulo operator.&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=4ab45aafc8a95c02010f84f66aabdaaf"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..=&lt;/span&gt;&lt;span class="mi"&gt;100&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-3"&gt;&lt;/a&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="n"&gt;x&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="mi"&gt;3&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="mi"&gt;0&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&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;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&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="mi"&gt;0&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-4"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-5"&gt;&lt;/a&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="k"&gt;if&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;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&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="mi"&gt;0&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-7"&gt;&lt;/a&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="k"&gt;if&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;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&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="mi"&gt;0&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-9"&gt;&lt;/a&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-10"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-11"&gt;&lt;/a&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-12"&gt;&lt;/a&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;a name="rest_code_513e683395094ee4a1b96c1fe42977a8-13"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;This gets us the desired output, but there's nothing idiomatic about it. With the exception of &lt;code&gt;..=&lt;/code&gt; (specifies a range "up to and including"), none of Rust's unique features are being used. In fact, it looks almost identical to a solution written in Python! Clearly we can do better.&lt;/p&gt;
&lt;div class="section" id="make-me-a-match"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id11"&gt;Make Me a Match&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;If you haven't read &lt;a class="reference external" href="https://doc.rust-lang.org/book/"&gt;Rust Book&lt;/a&gt;, bookmark it right away! It's one of the best introductions to a language I've ever read, and explains not just the core language, but the toolchains surrounding it that make Rust so accessible. One thing the book wastes no time introducing is Rust's &lt;code&gt;match&lt;/code&gt; operator:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"Rust has an extremely powerful control flow operator called match that allows you to compare a value against a series of patterns and then execute code based on which pattern matches. Patterns can be made up of literal values, variable names, wildcards, and many other things"&lt;/p&gt;
&lt;p class="attribution"&gt;—&lt;a class="reference external" href="https://doc.rust-lang.org/book/ch06-02-match.html"&gt;The Rust Book Ch. 6-2&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Let's update our basic function to use &lt;code&gt;match&lt;/code&gt; instead of &lt;code&gt;if-else&lt;/code&gt;. We want to match the output of two modulo operators, if they're both zero we'll output &lt;code&gt;Fizzbuzz&lt;/code&gt;, if only one is zero we'll output &lt;code&gt;Fizz&lt;/code&gt; or &lt;code&gt;Buzz&lt;/code&gt; depending on the zero. and if neither are zero we'll simply output the number.&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=49150dcded25e25489d34dae9cfad0a3"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_e83860405ce04e86a1379cebada6323c-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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;a name="rest_code_e83860405ce04e86a1379cebada6323c-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..=&lt;/span&gt;&lt;span class="mi"&gt;100&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;a name="rest_code_e83860405ce04e86a1379cebada6323c-3"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&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;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&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;%&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;5&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;a name="rest_code_e83860405ce04e86a1379cebada6323c-4"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_e83860405ce04e86a1379cebada6323c-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&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="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_e83860405ce04e86a1379cebada6323c-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_e83860405ce04e86a1379cebada6323c-7"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_&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="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;a name="rest_code_e83860405ce04e86a1379cebada6323c-8"&gt;&lt;/a&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;a name="rest_code_e83860405ce04e86a1379cebada6323c-9"&gt;&lt;/a&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;a name="rest_code_e83860405ce04e86a1379cebada6323c-10"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Now this is starting to look more like Rust! By using &lt;code&gt;match&lt;/code&gt; we're able to eliminate a lot of unnecessary brackets and only have to calculate the modulo once, instead of at every if statement. Since the &lt;code&gt;match&lt;/code&gt; control flow operates from top to bottom, we need the "FizzBuzz" case to be listed first, as both "Fizz" and "Buzz" also satisfy the &lt;code&gt;(0,0)&lt;/code&gt; case.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="getting-idiomatic"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id12"&gt;Getting Idiomatic&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The above code would be more than enough to show an interviewer you passed CS 100, but we want to squeeze every possible idiomatic opportunity out of this function, so our next step will be pulling our logic out of the main function and into a trait. Again referencing the Rust Book:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"A trait tells the Rust compiler about functionality a particular type has and can share with other types. We can use traits to define shared behavior in an abstract way. We can use trait bounds to specify that a generic can be any type that has certain behavior."&lt;/p&gt;
&lt;p class="attribution"&gt;—&lt;a class="reference external" href="https://doc.rust-lang.org/book/ch10-02-traits.html"&gt;The Rust Book Ch. 10-2&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Right now we're only going to focus one one small feature of traits: defining sets of methods that can be called on a type (in our case &lt;code&gt;i32&lt;/code&gt;). Our trait &lt;code&gt;Fizzy&lt;/code&gt; will be simple in that it only has one function (also named &lt;code&gt;fizzy&lt;/code&gt;) that accepts a reference to the number and returns a String based on our Fizzbuzz rules.&lt;/p&gt;
&lt;pre class="code rust"&gt;&lt;a name="rest_code_dca94c5b8d0246fa8b8f328407725a53-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;trait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_dca94c5b8d0246fa8b8f328407725a53-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_dca94c5b8d0246fa8b8f328407725a53-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Trait definitions only declare the methods, they do not define the actual logic. For that we need to &lt;em&gt;implement&lt;/em&gt; the trait for our selected type. This is as easy as making an &lt;code&gt;impl&lt;/code&gt; for &lt;code&gt;i32&lt;/code&gt; and moving the match statement out of our main function into the &lt;code&gt;fizzy&lt;/code&gt; method. Our new program is shown below with the logic separated out into its own trait.&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=b2f1e2702441ebb90ededd28ae91959d"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;trait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-2"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-3"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;i32&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-7"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&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="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&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="mi"&gt;5&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;::&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-9"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;)&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="nb"&gt;String&lt;/span&gt;::&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-10"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&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;=&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;String&lt;/span&gt;::&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-11"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_&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="fm"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-12"&gt;&lt;/a&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-13"&gt;&lt;/a&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-14"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-15"&gt;&lt;/a&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-16"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-17"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..=&lt;/span&gt;&lt;span class="mi"&gt;100&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-18"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-19"&gt;&lt;/a&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;a name="rest_code_7dce36a69a734577a9b2bd46b06609fb-20"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;It may look like all we did was shuffle around where the code was (and for this simple of a program traits are already overkill) but structuring our logic into a trait allows for flexibility down the road, especially if we have to add more methods to &lt;code&gt;Fizzy&lt;/code&gt; or define it for different types (imagine a new Fizzbuzz with letters instead of numbers). The separation also allows us to write unit tests to validate &lt;code&gt;fizzy&lt;/code&gt; since it can be called separately from the main function.&lt;/p&gt;
&lt;div class="section" id="idiomatic-testing"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id13"&gt;Idiomatic Testing???&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Unit tests themselves are not unique/idiomatic to Rust. In fact, you'd be hard pressed to find a modern language that does not have an extensive unit test framework to tap into. What &lt;em&gt;is&lt;/em&gt; idiomatic, however, is how testing is built into the core language and Rust's solution to testing private interfaces.&lt;/p&gt;
&lt;p&gt;When writing a class/interface, I'll split complex methods into multiple small methods that can be easily tested, but I don't want those interim methods exposed to the end user. Python makes this easy enough with private methods, prefixing a function with an underscore (_) marks it as private, and most documentation and linters will treat it as such. However, it's actually as public as any other function, so while the IDE might flag a warning when I call the method to test it, there's nothing illegal about doing so (see below).&lt;/p&gt;
&lt;pre class="code Python"&gt;&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;object&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="fm"&gt;__init__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-4"&gt;&lt;/a&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-5"&gt;&lt;/a&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-6"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# putting an _ before a method marks it as private&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-7"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;preamble&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-8"&gt;&lt;/a&gt;        &lt;span class="nb"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;preamble&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-9"&gt;&lt;/a&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-10"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-11"&gt;&lt;/a&gt;        &lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Hello"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# a public interface can call a private method&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-12"&gt;&lt;/a&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-14"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="vm"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s1"&gt;'__main__'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;greeter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Greeter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Fotonix"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;greeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hello&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;# this instance method is public&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-17"&gt;&lt;/a&gt;    &lt;span class="n"&gt;greeter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_address&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"Buongiorno"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# this method is private, but can still be called&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-19"&gt;&lt;/a&gt;&lt;span class="c1"&gt;#-- Output --&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-20"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Hello Fotonix&lt;/span&gt;
&lt;a name="rest_code_cad148e87c2b4fe5a0a21104abbeed70-21"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# Buongiorno Fotonix&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;On the opposite side of the accessability spectrum we have C++, which uses its &lt;a class="reference external" href="https://stackoverflow.com/questions/860339/difference-between-private-public-and-protected-inheritance"&gt;public, private, and protected&lt;/a&gt; keywords to strictly enforce what objects and classes have access to those methods. While this is great from a security standpoint, it makes testing non-public interfaces difficult because you either have to (1) accept that you can only write "blackbox tests" that test the interfaces end users have, or (2) create &lt;a class="reference external" href="https://www.geeksforgeeks.org/friend-class-function-cpp/"&gt;friend classes&lt;/a&gt; that wrap the private functions in a public interface, and test that new interface.&lt;/p&gt;
&lt;p&gt;Rust strikes a happy medium between the two. You can still declare traits as public or private, and that privacy is not only respected, but enforced at compile-time. However, using the &lt;a class="reference external" href="https://doc.rust-lang.org/book/ch07-02-defining-modules-to-control-scope-and-privacy.html"&gt;modules&lt;/a&gt; system, you can put your tests in a path that has access to the private traits (i.e. they're within the trait's scope).&lt;/p&gt;
&lt;p&gt;The most common way to do this is to &lt;em&gt;inline unit tests in the same file as the methods you're testing&lt;/em&gt; and wrapping them in a module called &lt;code&gt;test&lt;/code&gt;. Apart from this unique layout, writing the tests themselves is similar to most unit-test frameworks. Rust has built-in macros for assertions and tests can be separated into functions to run concurrently. We'll add unit-tests to the bottom of our Fizzbuzz program to validate the &lt;code&gt;Fizzy&lt;/code&gt; trait. Tests can by run by running &lt;code&gt;cargo test&lt;/code&gt; from the terminal, or "test" from the pull-down menu in the playground.&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=0903c09a16ab46e0fbc66beb3129280e"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-1"&gt;&lt;/a&gt;&lt;span class="cp"&gt;#[cfg(test)]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;mod&lt;/span&gt; &lt;span class="nn"&gt;test&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-3"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;super&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[test]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_fizz&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-7"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;27&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;assert_eq!&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="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-9"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-10"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-12"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[test]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-13"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_buzz&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-14"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;20&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-15"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;assert_eq!&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="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-16"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-17"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-19"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[test]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-20"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_fizzbuzz&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-21"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;30&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-22"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;assert_eq!&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="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-23"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-24"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-25"&gt;&lt;/a&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-26"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="cp"&gt;#[test]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-27"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;test_num&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-28"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&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="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;29&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;98&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-29"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="fm"&gt;assert_eq!&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="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="fm"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-30"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-31"&gt;&lt;/a&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;a name="rest_code_a857398725f848bca2bb29f52e4a59a0-32"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="generic-traits-and-monomorphization"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id14"&gt;Generic Traits and Monomorphization&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;At this point pulling out the above Fizzbuzz will knock any interviewer's socks clean off... or they'll be annoyed that you've spend so much time on such an easy question, could go either way. But we're not here to please an imaginary interviewer! We're writing the most idiomatic Fizzbuzz in the history of Rust, so let's add one more "&lt;em&gt;totally unnecessary in this context but useful in general&lt;/em&gt;" feature: Generic Types.&lt;/p&gt;
&lt;p&gt;Up until now we've used &lt;code&gt;i32&lt;/code&gt; as the base type for all things Fizzbuzz. It's a safe bet for general integers, having a range of &amp;gt;4 billion, but will it always be the &lt;em&gt;right&lt;/em&gt; choice for our program? If Fizzbuzz will only ever use positive numbers, you may as well use an unsigned int. If you only ever need to calculate up to 100, 32-bits is overkill and you're better off with &lt;code&gt;u8&lt;/code&gt;. Instead of trying to predict the end use-case, we want to write our trait implementation such that the main function can call it with &lt;em&gt;any&lt;/em&gt; integer type, and an appropriate trait method is called.&lt;/p&gt;
&lt;p&gt;Rust solves this issue with &lt;a class="reference external" href="https://doc.rust-lang.org/book/ch10-01-syntax.html"&gt;generics&lt;/a&gt;. Instead of defining a function for a specific type, the programmer defines a set of traits that the type &lt;strong&gt;must&lt;/strong&gt; implement. Generics are one of Rust's &lt;em&gt;zero-cost abstractions&lt;/em&gt;, and provide flexibility while incurring &lt;a class="reference external" href="https://doc.rust-lang.org/book/ch10-01-syntax.html#performance-of-code-using-generics"&gt;no overhead at runtime&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;To make &lt;code&gt;Fizzy&lt;/code&gt; generic to all int types, we'll use the &lt;a class="reference external" href="https://crates.io/crates/num"&gt;num&lt;/a&gt; crate. The trait we want is &lt;code&gt;PrimInt&lt;/code&gt; which is a general abstraction for integer types, and &lt;code&gt;Zero&lt;/code&gt; which will generate the zero value we compare to. We also need the &lt;code&gt;Display&lt;/code&gt; trait from the standard library, which enforces that the type can be formatted into a string.&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=8305e2bdd08c0da94542fc3a8d670a7c"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_traits&lt;/span&gt;::&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identities&lt;/span&gt;::&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PrimInt&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0.2.14&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-3"&gt;&lt;/a&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;trait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-4"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;String&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-5"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-6"&gt;&lt;/a&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-7"&gt;&lt;/a&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-9"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nc"&gt;PrimInt&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="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-10"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nc"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;fmt&lt;/span&gt;::&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-11"&gt;&lt;/a&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-12"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;String&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-13"&gt;&lt;/a&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="n"&gt;zero&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-14"&gt;&lt;/a&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="n"&gt;three&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;from&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;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// These will never fail&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-15"&gt;&lt;/a&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="n"&gt;five&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;from&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="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-16"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&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="bp"&gt;self&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="n"&gt;three&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="bp"&gt;self&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="n"&gt;five&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-17"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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;y&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="n"&gt;x&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="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&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="n"&gt;zero&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="nb"&gt;String&lt;/span&gt;::&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-18"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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;_&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="n"&gt;x&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="n"&gt;zero&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="nb"&gt;String&lt;/span&gt;::&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-19"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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="k"&gt;if&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;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&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="nb"&gt;String&lt;/span&gt;::&lt;span class="n"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-20"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_&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="fm"&gt;format!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-21"&gt;&lt;/a&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-22"&gt;&lt;/a&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-23"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-24"&gt;&lt;/a&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-25"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-26"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..=&lt;/span&gt;&lt;span class="mi"&gt;100&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-27"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-28"&gt;&lt;/a&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;a name="rest_code_5da11029f13e4cc19b970dc6629cf82c-29"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Notice how we can no longer use integers in &lt;code&gt;fizzy&lt;/code&gt;, but instead have to convert them to our generic type within the function. Fortunately the compiler optimizes this out and replaces them with constants in the final code. This is also a case where its acceptable to use &lt;code&gt;unwrap&lt;/code&gt; without fear of causing a panic at runtime. Since T implements &lt;code&gt;PrimInt&lt;/code&gt; we know a conversion from integers to T will never fail.&lt;/p&gt;
&lt;div class="class alert alert-info docutils container"&gt;
&lt;div class="update-header"&gt;
    &lt;i class="fas fa-info-circle"&gt;&lt;/i&gt; Update
&lt;/div&gt;
&lt;hr&gt;&lt;p&gt;&lt;a class="reference external" href="https://www.reddit.com/r/rust/comments/ogw7gi/learning_idiomatic_rust_by_going_overkill_with/h4si3pt/?utm_source=reddit&amp;amp;utm_medium=web2x&amp;amp;context=3"&gt;/u/TinBryn pointed out&lt;/a&gt; that that type bounds I picked were unnecessarily complex due to Rust's &lt;a class="reference external" href="https://doc.rust-lang.org/book/ch19-03-advanced-traits.html#using-supertraits-to-require-one-traits-functionality-within-another-trait"&gt;super traits&lt;/a&gt; system. A type implementing &lt;code&gt;PrimInt&lt;/code&gt; implies it also implements &lt;code&gt;Zero&lt;/code&gt; and &lt;code&gt;Copy&lt;/code&gt;, and &lt;code&gt;Copy&lt;/code&gt; implies &lt;code&gt;Clone&lt;/code&gt;. Ultimately all we need to include is &lt;code&gt;where T: PrimInt + std::fmt::Display&lt;/code&gt; to get the same functionality.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="going-off-the-deep-end"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id15"&gt;Going off the Deep End&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We did it, we wrote an amazing Fizzbuzz leveraging a slew of Rust's unique features! But we also cheated slightly... The rules of the game asked us to print the result of the fizzbuzz check, but to enable testing we return a &lt;code&gt;String&lt;/code&gt; that's printed in the main loop. We can trim down this waste of a &lt;em&gt;whopping 72 bytes&lt;/em&gt; of memory by having &lt;code&gt;fizzy&lt;/code&gt; write directly to an IO stream! The easiest solution would be to have our function call the &lt;code&gt;println!&lt;/code&gt; macro directly, but then we can no longer test our function. Instead, We'll borrow a tip from the &lt;a class="reference external" href="https://rust-cli.github.io/book/tutorial/testing.html#making-your-code-testable"&gt;Rust CLI Book&lt;/a&gt; (different than &lt;em&gt;The Rust Book&lt;/em&gt;, but equally as good) where we pass a mutable reference to a &lt;code&gt;Writer&lt;/code&gt; handle. In the main loop that handle will point to stdout, but for testing it will be a &lt;code&gt;vector&lt;/code&gt; that we can compare to the expected output.&lt;/p&gt;
&lt;p&gt;This requires a couple modifications to our &lt;code&gt;fizzy&lt;/code&gt; function:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;We need to replace all the match statement arms with &lt;code&gt;writeln!&lt;/code&gt; macro calls.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Since &lt;code&gt;writeln!&lt;/code&gt; can fail we need to modify the signature of &lt;code&gt;fizzy&lt;/code&gt; to return a &lt;code&gt;std::io::Result&lt;/code&gt; enum, allowing us to squeeze in yet another idiomatic feature: Error Types!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We also want to be able to catch the error in the main function. so we'll replace the for loop with an iterator, and consume it with a &lt;code&gt;try_for_each&lt;/code&gt; method.&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=df1f2f10f63bc1eed011574e4ce5ba31"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_traits&lt;/span&gt;::&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identities&lt;/span&gt;::&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PrimInt&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0.2.14&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;io&lt;/span&gt;::&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-4"&gt;&lt;/a&gt;&lt;span class="k"&gt;pub&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;trait&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-5"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-7"&gt;&lt;/a&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-8"&gt;&lt;/a&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Fizzy&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-9"&gt;&lt;/a&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-10"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nc"&gt;PrimInt&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="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-11"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nc"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;fmt&lt;/span&gt;::&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-12"&gt;&lt;/a&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-13"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;Write&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nb"&gt;Result&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-14"&gt;&lt;/a&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="n"&gt;zero&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-15"&gt;&lt;/a&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="n"&gt;three&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;from&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;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// These will never fail&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-16"&gt;&lt;/a&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="n"&gt;five&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;from&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="n"&gt;unwrap&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-17"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&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="bp"&gt;self&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="n"&gt;three&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="bp"&gt;self&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="n"&gt;five&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-18"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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;y&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="n"&gt;x&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="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&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="n"&gt;zero&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="fm"&gt;writeln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-19"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &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;_&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="n"&gt;x&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="n"&gt;zero&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="fm"&gt;writeln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-20"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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="k"&gt;if&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;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&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="fm"&gt;writeln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-21"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;_&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="fm"&gt;writeln!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;writer&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-22"&gt;&lt;/a&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-23"&gt;&lt;/a&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-24"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-25"&gt;&lt;/a&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-27"&gt;&lt;/a&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="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&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="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;io&lt;/span&gt;::&lt;span class="n"&gt;stdout&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-28"&gt;&lt;/a&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="kd"&gt;let&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Err&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;error&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="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..=&lt;/span&gt;&lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;try_for_each&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="o"&gt;|&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;x&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fizzy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="k"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;out&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-29"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"IO Error Writing to Stream: {}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-30"&gt;&lt;/a&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;a name="rest_code_1956ac9e19cf4141bc924cae49f8dbed-31"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;With those small changes we've added mutable references, iterators, and error handling to the list of features this little program can demonstrate. Was any of it necessary? Not at all! Our final output is no different than the first program composed of if-else statements. But it's always fun to start with a trivial program and think up ways to transform it into something that makes me feel like I'll one day earn the title of "Rustacean".&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="update-why-no-love-for-enum"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/rust-fizzbuzz/#id16"&gt;Update - Why no Love for Enum?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;After posting this code onto the &lt;a class="reference external" href="https://www.reddit.com/r/rust/"&gt;r/rust&lt;/a&gt; subreddit, the most common feedback I got was along the lines of "why are you passing strings around/writing directly to stdout, make an enum and use that instead." This somewhat surprised me because &lt;a class="reference external" href="https://github.com/rfrazier716/rust_101/blob/d0028ed3072b4d7ce34b845fe6044266cdcaa123/fizzbuzz/src/main.rs"&gt;my first pass&lt;/a&gt; at writing this code &lt;em&gt;did&lt;/em&gt; use an enum with an associated value, and the feedback for that code was "the enum is unnecessary if all you'll ever do is print the output, just print it directly to stdout." These conflicting feedbacks have helped me spawn my own definition for truly idiomatic Rust:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;"The most idiomatic Rust is whatever code you did not write, but somebody else has decided you should."&lt;/p&gt;
&lt;p class="attribution"&gt;—Fotonix&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Not one to disappoint, however, lets write a final Fizzbuzz that forgoes our custom trait in favor of an enum that implements &lt;code&gt;std::fmt::Display&lt;/code&gt;!&lt;/p&gt;
&lt;div class="rust-playground"&gt;
    &lt;a href="https://play.rust-lang.org/?version=stable&amp;amp;mode=debug&amp;amp;edition=2018&amp;amp;gist=8d9fc8300bf73040735c770a9e3ecf1d"&gt;
        &lt;i class="fas fa-play"&gt;&lt;/i&gt; Run on the Rust Playground
    &lt;/a&gt;
&lt;/div&gt;&lt;pre class="code rust"&gt;&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num_traits&lt;/span&gt;::&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;identities&lt;/span&gt;::&lt;span class="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;PrimInt&lt;/span&gt;&lt;span class="p"&gt;};&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// 0.2.14&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;use&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;std&lt;/span&gt;::&lt;span class="n"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-4"&gt;&lt;/a&gt;&lt;span class="cp"&gt;#[derive(Debug, PartialEq)]&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-5"&gt;&lt;/a&gt;&lt;span class="k"&gt;enum&lt;/span&gt; &lt;span class="nc"&gt;FizzbuzzResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;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;a name="rest_code_87eda1d815054acda52daceae9a506e0-6"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Fizz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-7"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Buzz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-8"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;FizzBuzz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-9"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-10"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-12"&gt;&lt;/a&gt;&lt;span class="k"&gt;impl&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;::&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="k"&gt;for&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;FizzbuzzResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-13"&gt;&lt;/a&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-14"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nc"&gt;fmt&lt;/span&gt;::&lt;span class="n"&gt;Display&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-15"&gt;&lt;/a&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-16"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fmt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="bp"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;: &lt;span class="kp"&gt;&amp;amp;&lt;/span&gt;&lt;span class="nc"&gt;mut&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fmt&lt;/span&gt;::&lt;span class="n"&gt;Formatter&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;'&lt;/span&gt;&lt;span class="nb"&gt;_&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nc"&gt;fmt&lt;/span&gt;::&lt;span class="nb"&gt;Result&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-17"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="bp"&gt;self&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-18"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;Fizz&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="fm"&gt;write!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Fizz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-19"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;Buzz&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="fm"&gt;write!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"Buzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-20"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;FizzBuzz&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="fm"&gt;write!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"FizzBuzz"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-21"&gt;&lt;/a&gt;&lt;span class="w"&gt;            &lt;/span&gt;&lt;span class="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;ref&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;)&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="fm"&gt;write!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;val&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-22"&gt;&lt;/a&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-23"&gt;&lt;/a&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-24"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-25"&gt;&lt;/a&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-26"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;fizzbuzz&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;: &lt;span class="nc"&gt;T&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;-&amp;gt; &lt;span class="nc"&gt;FizzbuzzResult&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-27"&gt;&lt;/a&gt;&lt;span class="k"&gt;where&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-28"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nc"&gt;PrimInt&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="n"&gt;Zero&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-29"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;: &lt;span class="nb"&gt;Copy&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nb"&gt;Clone&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-30"&gt;&lt;/a&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-31"&gt;&lt;/a&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="n"&gt;zero&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;zero&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-32"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="c1"&gt;// These will never fail&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-33"&gt;&lt;/a&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="n"&gt;three&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;from&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not convert '3' to generic type"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-34"&gt;&lt;/a&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="n"&gt;five&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="n"&gt;T&lt;/span&gt;::&lt;span class="n"&gt;from&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="n"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Could not convert '5' to generic type"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-35"&gt;&lt;/a&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-36"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;match&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&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="n"&gt;three&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;num&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="n"&gt;five&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-37"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &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;y&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="n"&gt;x&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="n"&gt;zero&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;y&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="n"&gt;zero&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="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;FizzBuzz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-38"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &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;_&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="n"&gt;x&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="n"&gt;zero&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="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;Fizz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-39"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&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="k"&gt;if&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;==&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;zero&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="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;Buzz&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-40"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="n"&gt;_&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="n"&gt;FizzbuzzResult&lt;/span&gt;::&lt;span class="n"&gt;Num&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;num&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-41"&gt;&lt;/a&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-42"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-43"&gt;&lt;/a&gt;
&lt;a name="rest_code_87eda1d815054acda52daceae9a506e0-44"&gt;&lt;/a&gt;&lt;span class="k"&gt;fn&lt;/span&gt; &lt;span class="nf"&gt;main&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-45"&gt;&lt;/a&gt;&lt;span class="w"&gt;    &lt;/span&gt;&lt;span class="k"&gt;for&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="k"&gt;in&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="o"&gt;..=&lt;/span&gt;&lt;span class="mi"&gt;100&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-46"&gt;&lt;/a&gt;&lt;span class="w"&gt;        &lt;/span&gt;&lt;span class="fm"&gt;println!&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="n"&gt;fizzbuzz&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-47"&gt;&lt;/a&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;a name="rest_code_87eda1d815054acda52daceae9a506e0-48"&gt;&lt;/a&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;This code has a few distinct advantages, but the main ones are you only ever return an enum that lives on the stack, and testing no longer involves string comparison, but instead compares the returned enum to the expected type (This is why &lt;code&gt;PartialEq&lt;/code&gt; is derived for &lt;code&gt;FizzbuzzResult&lt;/code&gt;). On the flip side, we now have two match comparisons: one to generate the enum and one to display it, whereas our first attempt has only one.&lt;/p&gt;
&lt;p&gt;At this point I don't know which of these options is &lt;em&gt;more&lt;/em&gt; idiomatic, but I do know now that I've written them down, somebody is going to come in with a third option claiming it's superior to both 😄.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</description><category>rust</category><guid>https://www.fotonixx.com/posts/rust-fizzbuzz/</guid><pubDate>Fri, 09 Jul 2021 00:27:33 GMT</pubDate></item><item><title>Version Controlling Your Data Science Projects</title><link>https://www.fotonixx.com/posts/data-science-vcs/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/data_science_vcs/preview_image.jpg"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;Historically I've been a huge advocate of &lt;a class="reference external" href="https://www.wolfram.com/mathematica/"&gt;Mathematica&lt;/a&gt; for scientific computing. The symbolic analysis made physics modeling a breeze, and I could quickly create attractive summary plots to be shared with my team. As I started looking at large datasets and collaborating with more engineers, however, Mathematica became more hinderance than help. As Code could not be easily version controlled, it would often end up emailed back and forth with the receiver having to manually check that they had all dependent files, as well as update file-paths to load data without errors.&lt;/p&gt;
&lt;p&gt;Most of my coworkers were not familiar with Mathematica, and there wasn't justification in purchasing license seats just for them to open and run my files. All of this culminated in an environment where most data analysis bottlenecked through me and caused unnecessary slowdowns.&lt;/p&gt;
&lt;p&gt;These issues sparked my conversion to &lt;a class="reference external" href="https://www.python.org/"&gt;Python&lt;/a&gt; for almost all analysis. As free, open-source software, anybody on my team can install it in minutes, and its popularity as a scripting language means nearly everybody we onboard has some Python experience. Today I'll be covering the repository structure I've adopted to make a data analysis workflow that anybody can easily use and contribute to. It assumes you have familiarity with the Python ecosystem and &lt;a class="reference external" href="https://jupyter.org/"&gt;Jupyter&lt;/a&gt; Notebook environment, as well as a light understanding of Git.&lt;/p&gt;
&lt;div class="contents alert alert-primary topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#what-makes-this-structure-special" id="id2"&gt;What Makes this Structure Special?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#required-software" id="id3"&gt;Required Software&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#setting-up-our-python-environment" id="id4"&gt;Setting Up Our Python Environment&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#easy-virtual-environments-with-poetry" id="id5"&gt;Easy Virtual Environments with Poetry&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#adding-common-dependencies" id="id6"&gt;Adding Common Dependencies&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#registering-our-environment-as-an-ipython-kernel" id="id7"&gt;Registering Our Environment as an IPython Kernel&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#adding-version-control" id="id8"&gt;Adding Version Control&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#making-a-new-repository" id="id9"&gt;Making a New Repository&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#choosing-which-files-to-ignore" id="id10"&gt;Choosing which Files to Ignore&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#creating-a-git-hook-to-prevent-messy-commits" id="id11"&gt;Creating a Git Hook to Prevent Messy Commits&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#version-controlling-data" id="id12"&gt;Version Controlling Data&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#why-don-t-we-store-everything-in-the-git-repository" id="id13"&gt;Why Don't We Store Everything in the Git Repository?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#setting-up-dvc" id="id14"&gt;Setting up DVC&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#adding-a-remote" id="id15"&gt;Adding a Remote&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#document-everything-with-a-readme" id="id16"&gt;Document Everything with a ReadMe&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#what-s-the-workflow-look-like" id="id17"&gt;What's the Workflow Look Like?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#how-to-save-results" id="id18"&gt;How to Save Results&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="quick-links"&gt;
&lt;p class="topic-title"&gt;Quick Links&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#what-makes-this-structure-special" id="id19"&gt;What Makes this Structure Special?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#required-software" id="id20"&gt;Required Software&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#setting-up-our-python-environment" id="id21"&gt;Setting Up Our Python Environment&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#adding-version-control" id="id22"&gt;Adding Version Control&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#version-controlling-data" id="id23"&gt;Version Controlling Data&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#document-everything-with-a-readme" id="id24"&gt;Document Everything with a ReadMe&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#what-s-the-workflow-look-like" id="id25"&gt;What's the Workflow Look Like?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/data-science-vcs/#how-to-save-results" id="id26"&gt;How to Save Results&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="what-makes-this-structure-special"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id19"&gt;What Makes this Structure Special?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Our new analysis flow is going to borrow extensively from the great open source tools that have come about for data science combined with best practices for software maintenance. In the end we will have a repository that can be extended for any data analysis project featuring:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A version controlled &lt;a class="reference external" href="https://git-scm.com/"&gt;Git&lt;/a&gt; repository with hooks in place to only store code.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://jupyter.org/"&gt;Jupyter&lt;/a&gt; notebook integration to perform the actual analysis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A managed Python environment to keep track of dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://dvc.org/"&gt;Version controlled data&lt;/a&gt; which is stored separately from your code, but tracked in the main repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;A clean, informative ReadMe enabling other users to download and run the same analysis.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;div class="class alert alert-info docutils container"&gt;
&lt;i class="fas fa-info-circle"&gt;&lt;/i&gt; If you want to skip the explanation, you can fork a &lt;a href="https://github.com/rfrazier716/data_analysis_template"&gt;template of the final repository&lt;/a&gt; from GitHub.&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="required-software"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id20"&gt;Required Software&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While most of the tools we use will be installed through &lt;a class="reference external" href="https://pip.pypa.io/en/stable/"&gt;Python's own package manager&lt;/a&gt;, there's a few pieces that need to be downloaded separately:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;A &lt;a class="reference external" href="https://www.python.org/"&gt;Python&lt;/a&gt; Distribution - The base Python Interpreter. At the time of writing I recommend &lt;a class="reference external" href="https://www.python.org/downloads/release/python-3810/"&gt;version 3.8&lt;/a&gt;, which is well supported. It's worth noting that if you are on Windows, the &lt;code&gt;pywinpty&lt;/code&gt; package (required by Jupyter) only supports 64-bit Python, so make sure to download a 64-bit interpreter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://python-poetry.org/"&gt;Poetry&lt;/a&gt; - We'll use Poetry to manage virtual environments as well as make sure others can install the same package versions we're using (via the &lt;code&gt;poetry.lock&lt;/code&gt; file).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://code.visualstudio.com/"&gt;Visual Studio Code&lt;/a&gt; - While any text editor will work, VSCode's extensive Python support (including a Jupyter extension that runs a server in the editor) makes it a one-stop shop for data analysis.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference external" href="https://git-scm.com/"&gt;Git&lt;/a&gt; - At the end of the day Jupyter notebooks are still code, and we want to exercise best software practices when writing them, version control included. Git can be daunting at first, but &lt;a class="reference external" href="https://code.visualstudio.com/docs/editor/versioncontrol"&gt;VSCode's Git integration&lt;/a&gt; means your commits can be done through the UI instead of command line.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Once everything is installed we're ready to set up the environment. Create a new folder that will hold the repository and open it in VSCode (&lt;code&gt;Ctrl+K&lt;/code&gt; if you're already in VSCode).&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="setting-up-our-python-environment"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id21"&gt;Setting Up Our Python Environment&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;We want the packages we depend on to be isolated from the rest of the Python installation. Since Python dynamically links libraries at runtime, updating a package down the line (e.g. to install a different library with a newer dependency) might break your old code, or worse, introduce a subtle bug that you don't catch until much later. While we could go to an extreme and containerize all of our code, running it in a Docker Environment or similar, Python instead offers a simpler solution through &lt;a class="reference external" href="https://docs.python.org/3/tutorial/venv.html"&gt;virtual environments&lt;/a&gt;. If virtual environments are a new concept, I recommend reading the &lt;a class="reference external" href="https://docs.python.org/3/tutorial/venv.html"&gt;tutorial on python.org&lt;/a&gt;.&lt;/p&gt;
&lt;div class="section" id="easy-virtual-environments-with-poetry"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id5"&gt;Easy Virtual Environments with Poetry&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Poetry makes creating new environments easy with its &lt;code&gt;poetry init&lt;/code&gt; command. This will ask a series of questions about your project which the tool will use to generate a &lt;a class="reference external" href="https://snarky.ca/what-the-heck-is-pyproject-toml/"&gt;pyproject.toml&lt;/a&gt; file. You can also choose to add any package dependencies when creating the environment. It's simple and often faster to add dependencies later, so I typically skip this step.&lt;/p&gt;
&lt;p&gt;If there's not a console at the bottom of your VSCode window, open it with &lt;code&gt;Ctrl+Shift+`&lt;/code&gt;, or Terminal -&amp;gt; New Terminal and run the initialization.&lt;/p&gt;
&lt;pre class="code console"&gt;&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry init
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-3"&gt;&lt;/a&gt;&lt;span class="go"&gt;This command will guide you through creating your pyproject.toml config.&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-5"&gt;&lt;/a&gt;&lt;span class="go"&gt;Package name [data_analysis]:  my_awesome_analysis_repository&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-6"&gt;&lt;/a&gt;&lt;span class="go"&gt;Version [0.1.0]:&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-7"&gt;&lt;/a&gt;&lt;span class="go"&gt;Description []:&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-8"&gt;&lt;/a&gt;&lt;span class="go"&gt;Author [Ryan Frazier &amp;lt;ryan@fotonixx.com&amp;gt;, n to skip]:&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-9"&gt;&lt;/a&gt;&lt;span class="go"&gt;License []:&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-10"&gt;&lt;/a&gt;&lt;span class="go"&gt;Compatible Python versions [^3.9]:  &amp;gt;3.8,&amp;lt;3.9&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-11"&gt;&lt;/a&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-12"&gt;&lt;/a&gt;&lt;span class="go"&gt;Would you like to define your main dependencies interactively? (yes/no) [yes] no&lt;/span&gt;
&lt;a name="rest_code_7d385ec27396493486f6f8ce654d1227-13"&gt;&lt;/a&gt;&lt;span class="go"&gt;Would you like to define your development dependencies interactively? (yes/no) [yes] no&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Once it's done, &lt;code&gt;pyproject.toml&lt;/code&gt; will be generated in the root directory with the information you entered. By default Poetry will create the environment in a separate directory, but you can access it with &lt;code&gt;poetry run&lt;/code&gt;. Test that your environment is running by executing &lt;code&gt;poetry run py --version&lt;/code&gt;, and making sure it matches the version you specified.&lt;/p&gt;
&lt;div class="section" id="adding-common-dependencies"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id6"&gt;Adding Common Dependencies&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;The specific packages you need will vary project-to-project, you'll almost always be using the &lt;a class="reference external" href="https://www.scipy.org/stackspec.html"&gt;Scientific Python Stack&lt;/a&gt;, so let's add it to our dependencies! We also need to install the &lt;a class="reference external" href="https://ipython.org/ipython-doc/3/interactive/tutorial.html"&gt;Interactive Python (IPython)&lt;/a&gt; Kernel so we can register this environment as a Jupyter Kernel later. All of these can be installed using poetry's &lt;code&gt;add&lt;/code&gt; command.&lt;/p&gt;
&lt;pre class="code console"&gt;&lt;a name="rest_code_0ad5621c573341bc90ec73f5254c7f57-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry add jupyterlab numpy scipy matplotlib pandas ipykernel
&lt;/pre&gt;&lt;p&gt;If you open &lt;code&gt;pyproject.toml&lt;/code&gt; you'll see all those packages are now listed in the dependencies section. Additionally, Poetry created a &lt;code&gt;poetry.lock&lt;/code&gt; file which is used to store the exact versions of every package and dependency in the environment.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="registering-our-environment-as-an-ipython-kernel"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id7"&gt;Registering Our Environment as an IPython Kernel&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Jupyter uses &lt;a class="reference external" href="https://ipython.readthedocs.io/en/stable/development/how_ipython_works.html?highlight=kernel#the-ipython-kernel"&gt;IPython Kernels&lt;/a&gt; running as a separate process to evaluate cells. In order for Jupyter to use our newly created virtual environment, we need to register it with a kernel using the ipykernel package. Run the below command, replacing &lt;code&gt;name&lt;/code&gt; and &lt;code&gt;display-name&lt;/code&gt; with appropriate values for the project.&lt;/p&gt;
&lt;pre class="code console"&gt;&lt;a name="rest_code_42f9028dac5a4f75a9ca1d9a53f1d17e-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry run python -m ipykernel install --user --name project_x_env --display-name &lt;span class="s2"&gt;"My Awesome Data Science Environment"&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Now you can Launch Jupyter from &lt;strong&gt;any&lt;/strong&gt; environment, including the global environment, and still access this environment's packages. If you don't have Jupyter installed globally, you can run the installation in our environment by executing &lt;code&gt;poetry run jupyter lab&lt;/code&gt;. Alternatively, creating and opening an &lt;code&gt;*.ipynb&lt;/code&gt; file in VSCode will enable the data science view.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/data_science_vcs/kernel_addition.png" src="https://www.fotonixx.com/images/data_science_vcs/kernel_addition.png"&gt;
&lt;p class="caption"&gt;Our newly created Kernel as a selectable option&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-version-control"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id22"&gt;Adding Version Control&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;You might be wondering why a data analysis project needs version control, or what version control even is. &lt;a class="reference external" href="https://www.atlassian.com/git/tutorials/what-is-version-control"&gt;To quote Atlassian&lt;/a&gt;:&lt;/p&gt;
&lt;div class="class alert alert-info docutils container"&gt;
&lt;p&gt;&lt;em&gt;"Version control software keeps track of every modification to the code in a special kind of database. If a mistake is made, developers can turn back the clock and compare earlier versions of the code to help fix the mistake while minimizing disruption to all team members."&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Sounds pretty great, right?! If you still need convincing: imagine you're toying with a new way to look at data but you don't want to delete your current method. Instead of copying the file into a new one, you can create a &lt;a class="reference external" href="https://www.atlassian.com/git/tutorials/using-branches"&gt;branch&lt;/a&gt; and &lt;a class="reference external" href="https://www.atlassian.com/git/tutorials/using-branches/git-merge"&gt;merge&lt;/a&gt; the changes if they work, or delete them if not. Not only that but you can have multiple people simultaneously looking at the data, each person focused on their specific tasks, with all changes merged at the end.&lt;/p&gt;
&lt;div class="section" id="making-a-new-repository"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id9"&gt;Making a New Repository&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To keep things simple we'll use VSCode's Git plugins to create, commit to, and push our repository. On the left side of the VSCode window you should see the directory structure with the pyproject and poetry.lock files. Depending on your Poetry settings the virtual environment may also be in this base directory.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/data_science_vcs/empty_repository.png" src="https://www.fotonixx.com/images/data_science_vcs/empty_repository.png"&gt;
&lt;/div&gt;
&lt;p&gt;Open the Command Pallette (&lt;code&gt;Ctrl+Shift+P&lt;/code&gt; or f1 on Windows) and type "git init". There should be only one option that reads "Git: Initialize Repository". Press Enter and select the current folder to initialize the repository.&lt;/p&gt;
&lt;p&gt;Notice that &lt;code&gt;pyproject.toml&lt;/code&gt; and &lt;code&gt;poetry.lock&lt;/code&gt; have turned green in the Explorer (&lt;code&gt;Ctrl+Shift+E&lt;/code&gt;)! This is because VSCode knows you have a repository in the directory and that those files have not been staged for a commit. If you open the Source Control Panel (&lt;code&gt;Ctrl+Shift+G&lt;/code&gt;) you'll see both those files listed as changes with a "U" on the right meaning they're unstaged. Before staging and making our initial commit, however, we want to add a few more files that will help flesh out the repository. These include:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;a &lt;code&gt;readme.md&lt;/code&gt; file in the base directory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a &lt;code&gt;.gitignore&lt;/code&gt; file in the base directory&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a new directory &lt;code&gt;/notebooks/&lt;/code&gt; with a &lt;code&gt;readme.md&lt;/code&gt; inside of it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;a &lt;code&gt;/data/&lt;/code&gt; directory with an empty &lt;code&gt;.gitkeep&lt;/code&gt; file  inside of it.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;When you've added the files your project directory should look similar to below:&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/data_science_vcs/adding_readme_and_ignore.png" src="https://www.fotonixx.com/images/data_science_vcs/adding_readme_and_ignore.png"&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="choosing-which-files-to-ignore"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id10"&gt;Choosing which Files to Ignore&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Git uses the &lt;code&gt;.gitignore&lt;/code&gt; file to black-list specific files or even entire directories from being captured into version control. This helps keep the repository size small and only commit files that are necessary to reproduce the environment. As a rule of thumb the following should be excluded from your commits:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Any IDE settings (the &lt;code&gt;./.vscode/&lt;/code&gt; directory )&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Auto-generated files (&lt;code&gt;*.pyc&lt;/code&gt;, file backups etc.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;User specific environment files&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Large files that don't often change (downloaded datasets, third-party libraries)&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It might seem alarming that datasets should not be part of version control, after all you have nothing to analyze without data! Further down we'll talk about how to synchronize data with the repository, but for now we'll ignore all files in the /data/ directory except for our whitelisted &lt;code&gt;.gitkeep&lt;/code&gt; file.&lt;/p&gt;
&lt;pre class="code text"&gt;&lt;a name="rest_code_608b22497c754d73b2b15e05505f1f96-1"&gt;&lt;/a&gt;# Ignore data directories
&lt;a name="rest_code_608b22497c754d73b2b15e05505f1f96-2"&gt;&lt;/a&gt;/data/*
&lt;a name="rest_code_608b22497c754d73b2b15e05505f1f96-3"&gt;&lt;/a&gt;!/data/.gitkeep
&lt;/pre&gt;&lt;p&gt;The rest of our &lt;code&gt;.gitignore&lt;/code&gt; file is built off of GitHub's &lt;a class="reference external" href="https://github.com/github/gitignore/blob/master/Python.gitignore"&gt;python.gitignore&lt;/a&gt; with the above additions to ignore our data directory, as well as VSCode settings, and Jupyter backup files. The entire file can be found &lt;a class="reference external" href="https://github.com/rfrazier716/data_analysis_template/blob/main/.gitignore"&gt;here&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-a-git-hook-to-prevent-messy-commits"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id11"&gt;Creating a Git Hook to Prevent Messy Commits&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;While I love Jupyter for exploration and data analysis, one thing that always bothers me is how the code lives in the same file as evaluated evaluated output. When version controlling notebooks this can cause issues for a couple of reasons:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;If the data you're working on is private but the code is public, the private data could end up in an output and committed, available for anybody to see.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Git works by logging differences in your file. This includes things like cell number, cell output, and picture metadata, if you version control an evaluated notebook you'll have unstaged changes as soon as you evaluate a cell, even though none of the written code actually changed!&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;We want a way to scrub our notebooks of all evaluated output before committing them. To do so we'll use &lt;a class="reference external" href="https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks"&gt;githooks&lt;/a&gt; which are custom scripts that run when you perform a Git command. Flipping through the Githook documentation, the pre-commit hook is exactly what we need. Unfortunately, installing a Githook is a manual process that requires you to add a file to your &lt;code&gt;/.git/&lt;/code&gt; directory.&lt;/p&gt;
&lt;div class="class alert alert-warning docutils container"&gt;
&lt;i class="fas fa-exclamation-triangle"&gt;&lt;/i&gt; Githooks are a great way to keep your repository clean, but you &lt;b&gt;must&lt;/b&gt; make sure your file is saved before running the hook, otherwise the script will overwrite any unsaved changes.&lt;/div&gt;
&lt;p&gt;In order to make it as easy as possible for anybody to use this template, as well as make writing the githook simple, we'll instead use the &lt;a class="reference external" href="https://pre-commit.com/"&gt;pre-commit&lt;/a&gt; python package and write our hook with a YAML file that will live in the root of our version control. The pre-commit config we'll be using comes from &lt;a class="reference external" href="https://zhauniarovich.com/post/2020/2020-06-clearing-jupyter-output/"&gt;Yuri Zhauniarovich's blog&lt;/a&gt; and uses &lt;a class="reference external" href="https://nbconvert.readthedocs.io/en/latest/"&gt;nbconvert&lt;/a&gt; to scrub the output in-place.&lt;/p&gt;
&lt;p&gt;Let's add &lt;code&gt;pre-commit&lt;/code&gt; and &lt;code&gt;nbconvert&lt;/code&gt; to our poetry environment. Since it's not needed to actually run the notebooks, and will only be used by people contributing to the codebase we'll install them as developer packages.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_a7fc6375d7a74eb789532d89091abcee-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry add pre-commit nbconvert --dev
&lt;/pre&gt;&lt;p&gt;To define the hook, a new file in the base directory called &lt;code&gt;.pre-commit-config.yaml&lt;/code&gt; and add the following text:&lt;/p&gt;
&lt;pre class="code YAML"&gt;&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-1"&gt;&lt;/a&gt;&lt;span class="nt"&gt;repos&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-2"&gt;&lt;/a&gt;  &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;repo&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;local&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-3"&gt;&lt;/a&gt;    &lt;span class="nt"&gt;hooks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-4"&gt;&lt;/a&gt;      &lt;span class="p p-Indicator"&gt;-&lt;/span&gt; &lt;span class="nt"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyter-nb-clear-output&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-5"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;jupyter-nb-clear-output&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-6"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;\.ipynb$&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-7"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;stages&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p p-Indicator"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;commit&lt;/span&gt;&lt;span class="p p-Indicator"&gt;]&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-8"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;language&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;system&lt;/span&gt;
&lt;a name="rest_code_c6d8d899f55945359a9b97b86ef14b3a-9"&gt;&lt;/a&gt;        &lt;span class="nt"&gt;entry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="l l-Scalar l-Scalar-Plain"&gt;poetry run jupyter nbconvert --ClearOutputPreprocessor.enabled=True --inplace&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The last piece is to install the githook so that it's run before every commit. Pre-commit makes this easy for us with it's &lt;code&gt;install&lt;/code&gt; argument.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_e7f0d69da5584d4ca20394d42fa2861e-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry run pre-commit install
&lt;a name="rest_code_e7f0d69da5584d4ca20394d42fa2861e-2"&gt;&lt;/a&gt;&lt;span class="go"&gt;pre-commit installed at .git\hooks\pre-commit&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;This is a good spot for our first commit! We've fleshed out the repository, filled our &lt;code&gt;.gitignore&lt;/code&gt; and added most of our template files (even if they are empty). To commit using VSCode we'll again use the Source Control Panel (&lt;code&gt;Ctrl+Shift+G&lt;/code&gt;). You can manually stage files by pressing the "+" icon to the right of the files, or you can stage all changes by clicking the "+" to the right of the "Changes" drop-down.&lt;/p&gt;
&lt;p&gt;All commits should have a meaningful message so that you can look back and quickly understand what was changed. &lt;a class="reference external" href="https://chris.beams.io"&gt;Chris Beam's Blog&lt;/a&gt; has a great &lt;a class="reference external" href="https://chris.beams.io/posts/git-commit/"&gt;post&lt;/a&gt; on the importance of a good commit message and how to write one, but for our first commit message we'll keep it simple with "initial commit".&lt;/p&gt;
&lt;p&gt;With the files staged and commit message filled out, press the check-mark at the top of the panel to commit the changes.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="version-controlling-data"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id23"&gt;Version Controlling Data&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Next comes adding data to the repository. Copy any necessary data files over to the &lt;code&gt;/data/&lt;/code&gt; directory we made earlier. You can have a nested directory tree inside of that directory so organize it into a structure that works for you. Looking at the Explorer panel you'll notice all these files are greyed out and don't show up if you tab over to the Source Control panel. Since &lt;code&gt;/data/&lt;/code&gt; is ignored every file and folder below is is subsequently ignored as well.&lt;/p&gt;
&lt;div class="section" id="why-don-t-we-store-everything-in-the-git-repository"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id13"&gt;Why Don't We Store Everything in the Git Repository?&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;To understand why we wouldn't want to store our large data files in a Git repository, lets peel back what happens when you clone an existing repository onto your local system. From Atlassian's &lt;a class="reference external" href="https://www.atlassian.com/git/tutorials/git-lfs"&gt;Git-LFS tutorial&lt;/a&gt;:&lt;/p&gt;
&lt;div class="class alert alert-info docutils container"&gt;
&lt;p&gt;&lt;em&gt;"Git is a distributed version control system, meaning the entire history of the repository is transferred to the client during the cloning process. For projects containing large files, particularly large files that are modified regularly, this initial clone can take a huge amount of time, as every version of every file has to be downloaded by the client."&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The solution to this problem is to not version control the large files at all, but instead version control small reference files that tell you &lt;em&gt;where&lt;/em&gt; the data lives so you only check-out the version you want, instead of the entire history. &lt;a class="reference external" href="https://git-lfs.github.com/"&gt;Git-LFS&lt;/a&gt; (Large File System) is one implementation of this strategy, but we're going to instead use the &lt;a class="reference external" href="https://dvc.org/"&gt;Data Version Control (DVC)&lt;/a&gt; Python package, which is specifically designed with data-analysis in mind.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="setting-up-dvc"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id14"&gt;Setting up DVC&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Since DVC is a Python package, we can install it with Pip just like our other dependencies. The assumption is anybody using our repository will want access to the data, not just developers, so we won't both with the &lt;code&gt;--dev&lt;/code&gt; flag either.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_44e05b5acfdf406c986d5a5055d36774-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry add dvc
&lt;/pre&gt;&lt;p&gt;DVC is designed to mimic a Git workflow, so many of the commands you'd use for Git have DVC parallels. For example, to initialize DVC in our repository and add the data directory we use the &lt;code&gt;init&lt;/code&gt; and &lt;code&gt;add&lt;/code&gt; commands respectively.&lt;/p&gt;
&lt;pre class="code shell-session"&gt;&lt;a name="rest_code_ad9d1a5c13104e718cad4d769424cd3d-1"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry run dvc init
&lt;a name="rest_code_ad9d1a5c13104e718cad4d769424cd3d-2"&gt;&lt;/a&gt;&lt;span class="gp"&gt;$ &lt;/span&gt;poetry run dvc add ./data/
&lt;a name="rest_code_ad9d1a5c13104e718cad4d769424cd3d-3"&gt;&lt;/a&gt;&lt;span class="go"&gt;~~~&lt;/span&gt;
&lt;a name="rest_code_ad9d1a5c13104e718cad4d769424cd3d-4"&gt;&lt;/a&gt;&lt;span class="go"&gt;To track the changes with git, run:&lt;/span&gt;
&lt;a name="rest_code_ad9d1a5c13104e718cad4d769424cd3d-5"&gt;&lt;/a&gt;&lt;span class="go"&gt;        git add data.dvc .gitignore&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Notice the final output after adding the data directory, DVC is telling us that it's created a new file called &lt;code&gt;data.dvc&lt;/code&gt; that's tracking changes to our data directory. It's also updated &lt;code&gt;.gitignore&lt;/code&gt; automatically for us to ignore &lt;code&gt;/data/&lt;/code&gt; (Our previously committed .gitkeep will not be ignored since it's already tracked). DVC's offering a convenience in case we had forgotten to ignore the directory ourselves, but since we already had, let's revert our &lt;code&gt;.gitignore&lt;/code&gt; to its previous state. In the Source Control Panel, right-click &lt;code&gt;.gitignore&lt;/code&gt; and select "Discard Changes". This will reset the file to its state in the previous commit.&lt;/p&gt;
&lt;p&gt;If you add new data to the directory, you can update &lt;code&gt;data.dvc&lt;/code&gt; by running &lt;code&gt;dvc add ./data/&lt;/code&gt; again. Make sure to commit &lt;code&gt;data.dvc&lt;/code&gt; as soon as you add new data, otherwise committed notebooks might lose sync with the data changes.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="adding-a-remote"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id15"&gt;Adding a Remote&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;DVC allows you to back-up version-controlled data to remote servers, perfect for enabling multiple users to access the same version of a dataset. From the &lt;a class="reference external" href="https://dvc.org/doc/command-reference/remote#remote"&gt;documentation&lt;/a&gt;:&lt;/p&gt;
&lt;div class="class alert alert-info docutils container"&gt;
&lt;p&gt;&lt;em&gt;The same way as GitHub provides storage hosting for Git repositories, DVC remotes provide a location to store and share data and models. You can pull data assets created by colleagues from DVC remotes without spending time and resources to build or process them locally. Remote storage can also save space on your local environment – DVC can fetch into the cache directory only the data you need for a specific branch/commit.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Using DVC with remote storage is optional. DVC commands use the local cache (usually in dir .dvc/cache) as data storage by default. This enables the main DVC usage scenarios out of the box.&lt;/em&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;I recommend setting up a default remote even if you're the only one looking at the dataset. Mine are usually directories on an internal network drive, but DVC has &lt;a class="reference external" href="https://dvc.org/doc/command-reference/remote/add#supported-storage-types"&gt;support for multiple storage types&lt;/a&gt;, so use whichever structure works best for you. The remote can even be a separate directory on you computer.&lt;/p&gt;
&lt;p&gt;Once the remote is set-up, pushing to it is as simple as running &lt;code&gt;dvc push &amp;lt;remote&amp;gt;&lt;/code&gt;. To pull from your remote you similarly run &lt;code&gt;dvc pull &amp;lt;remote&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="document-everything-with-a-readme"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id24"&gt;Document Everything with a ReadMe&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A good ReadMe will elevate your repository from "that collection of code that only you know how to use" to "an easily understood project that anybody can contribute to." Think of the ReadMe like a lab report with instruction on how to reproduce the analysis, it should:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;Clearly summarize the goal of the repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explain how to duplicate the code on a user's machine. This includes:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Installing required software (Python, Poetry, etc.).&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Creating the Virtual Environment and installing necessary packages.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Registering the Environment with Jupyter.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Pulling data from the DVC remote.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Explain how to run the analysis, with a description of what each notebook does and what results it will generate.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Give clear instructions for how users can contribute to/extend the repository:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;Installing the additional developer dependencies.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Making sure the githook is set-up so only clean files are committed.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;I'm a big fan of &lt;a class="reference external" href="https://github.com/othneildrew/Best-README-Template"&gt;othneildrew's Best ReadMe Template&lt;/a&gt; and use it for most of my projects. A modified version can be found on the &lt;a class="reference external" href="https://github.com/rfrazier716/data_analysis_template"&gt;example repository&lt;/a&gt; which covers all of the above requirements.&lt;/p&gt;
&lt;p&gt;Remember we also had a second ReadMe in the &lt;code&gt;/notebooks/&lt;/code&gt; directory which we'll use to describe each notebook in greater detail. You can even include example plots and outputs that the notebooks should generate. GitHub will show a rendered ReadMe in every directory that has one, so you can even group all your notebooks into separate subdirectories, and have a specific ReadMe explaining the purpose of every group!&lt;/p&gt;
&lt;p&gt;With data added, committed, and ReadMe's filled in it's time for our next commit! Stage all the changed files and give it a meaningful message: e.g. highlight what data was added and where it came from. Be sure to follow the &lt;a class="reference external" href="https://www.midori-global.com/blog/2018/04/02/git-50-72-rule"&gt;50-72 rule&lt;/a&gt; so your messages stay meaningful and concise.&lt;/p&gt;
&lt;p&gt;This is a great time to push your commits up to your favorite server. If VCS is completely new to you, I'd recommend &lt;a class="reference external" href="https://docs.github.com/en/github/getting-started-with-github"&gt;GitHub&lt;/a&gt; for its sheer popularity and option to make both public and private repositories with a free account.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-the-workflow-look-like"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id25"&gt;What's the Workflow Look Like?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;With the repository set up, it's time to outline how to use it for analysis. While at the end of the day you should do whatever is natural for your team. I recommend adopting a modification of the &lt;a class="reference external" href="https://www.atlassian.com/git/tutorials/comparing-workflows/gitflow-workflow"&gt;Gitflow&lt;/a&gt; workflow:&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;No work is done in the main branch of the repository.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When you have a new task, make a branch off of &lt;code&gt;main&lt;/code&gt; and do all of your work in that new branch.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;All notebooks should live in the &lt;code&gt;/notebook/&lt;/code&gt; directory. Use subdirectories to group similar notebooks.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;When the task is finished, clean up the branch and update the ReadMe to describe any new files. Depending on how formal your team is, manually merge the branch back into &lt;code&gt;main&lt;/code&gt; or submit a pull-request.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This structure ensures that analysis tasks are done in isolated environments and only merged back when they're finalized, and you don't need to constantly worry about pulling from the main branch and resolving merge requests.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="how-to-save-results"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/data-science-vcs/#id26"&gt;How to Save Results&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;This repository structure does a great job making sure analysis is done in a clear, reproducible matter, but I haven't touched on how to actually save results. In fact, because of our commit-hook, even if your VCS server rendered Jupyter notebooks (and many do) there would be no output and only code! How do we actually save results in a format that can be looked back on? This will vary from person-to-person, but I like to create a &lt;code&gt;/results/&lt;/code&gt; directory and use &lt;a class="reference external" href="https://www.sphinx-doc.org/en/master/"&gt;Sphinx&lt;/a&gt; to create a static website with a new page for each result. Be sure to populate them with saved images and hard coded values so that the results persist even if you edit the code later on.&lt;/p&gt;
&lt;p&gt;Static site generation with Sphinx can easily be integrated into a CI/CD workflow and Github even offers free hosting of static pages. Look into &lt;a class="reference external" href="https://github.com/c-w/ghp-import"&gt;gh-pages&lt;/a&gt; for an easy way to deploy your pages into a GitHub hosted Static site.&lt;/p&gt;
&lt;p&gt;This entire template can be found on &lt;a class="reference external" href="https://github.com/rfrazier716/data_analysis_template"&gt;my GitHub&lt;/a&gt;, feel free to fork it for your own projects and submit requests for additional features!&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</description><category>analysis</category><category>data science</category><category>git</category><category>jupyter</category><category>python</category><guid>https://www.fotonixx.com/posts/data-science-vcs/</guid><pubDate>Mon, 31 May 2021 15:46:46 GMT</pubDate></item><item><title>5 Video Game Soundtracks to Jam Out to While Coding</title><link>https://www.fotonixx.com/posts/5-video-game-soundtracks-to-jam-out-to-while-coding/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/music_for_coding/preview_image.jpg"&gt;&lt;/figure&gt; &lt;div&gt;&lt;div class="embed-responsive embed-responsive-1by1 rounded mb-2" id="quick-links"&gt;
&lt;iframe src="https://open.spotify.com/embed/playlist/5nc0NhiliBT6TtGMZAT1ML" height="380" frameborder="0" allowtransparency="true" allow="encrypted-media"&gt;&lt;/iframe&gt;
&lt;/div&gt;&lt;p&gt;Looking for a great programming playlist, try a video game soundtrack! The lack of lyrics stop them from being a distraction, but they're engaging enough to help you focus and drown out background noise. Below are five soundtracks I regularly listen to when I need to shut out the world and code.&lt;/p&gt;
&lt;div class="section" id="doom-2016-mick-gordon"&gt;
&lt;h2&gt;5. Doom (2016) - Mick Gordon&lt;/h2&gt;
&lt;p&gt;For when you find yourself up too late with bloodshot eyes and an energy drink in hand, blazing through code like a wannabe hacker channeling your inner &lt;a class="reference external" href="https://www.youtube.com/watch?v=r38fEGep2yU"&gt;Cereal&lt;/a&gt;. There's nothing better to keep you in the zone like the grandfather of shooters: &lt;a class="reference external" href="https://bethesda.net/en/game/doom"&gt;Doom&lt;/a&gt;!&lt;/p&gt;
&lt;div class="container-fluid"&gt;
    &lt;div class="col-xs-12 col-sm-12 col-md-10 offset-md-1 col-lg-10 offset-lg-1 padding-zero embed-responsive embed-responsive-16by9 mt-4 mb-4"&gt;
            &lt;iframe src="https://www.youtube.com/embed/5a9E3n_VZRQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Filed under &lt;em&gt;Progressive Metal&lt;/em&gt;, &lt;a class="reference external" href="https://twitter.com/mick_gordon?lang=en"&gt;Mick Gordon's&lt;/a&gt; TGA winning soundtrack is full of high energy tracks to get your blood pumping. While a few songs feature samples of the 1993 classic (I'm looking at you "At DOOM's Gate"), most tracks set themselves apart from the original game's already great songs. If you want to make an evening of debug feel like you're pulling off a Shadowrun heist, look no further than Doom.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="celeste-lena-raine"&gt;
&lt;h2&gt;4. Celeste - Lena Raine&lt;/h2&gt;
&lt;p&gt;A game that needs no introduction, &lt;a class="reference external" href="http://www.celestegame.com/"&gt;Celeste&lt;/a&gt; is an incredibly popular platformer that still manages to regularly show up on "hidden indie gems" lists. When an entire subcomponent of the game is to collect floating casette tapes to unlock "B-Side" levels, you know music must play a big role, and &lt;a class="reference external" href="http://lena.fyi/"&gt;Lena Raine&lt;/a&gt; does not disappoint!&lt;/p&gt;
&lt;div class="container-fluid"&gt;
    &lt;div class="col-xs-12 col-sm-12 col-md-10 offset-md-1 col-lg-10 offset-lg-1 padding-zero embed-responsive embed-responsive-16by9 mt-4 mb-4"&gt;
            &lt;iframe width="560" height="315" src="https://www.youtube.com/embed/1rwAvUvvQzQ" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;I find myself regularly looping through the 100 minutes of tracks only to be left wanting more when it ends. The feel of the songs range from motivating and energetic, to downright haunting, but every one is a perfect accompaniment when focusing on a task.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="desktop-dungeons-danny-baronowsky-grant-kirkhope"&gt;
&lt;h2&gt;3. Desktop Dungeons - Danny Baronowsky &amp;amp; Grant Kirkhope&lt;/h2&gt;
&lt;p&gt;There's a solid chance you haven't heard of &lt;cite&gt;Desktop Dungeons&lt;/cite&gt;, a smaller indie game from 2013. However, you've probably heard of Ubisoft's X-Com/Mushroom Kingdom Crossover &lt;a class="reference external" href="https://rabbids.ubisoft.com/portal/en-us/games/mario-rabbids-kingdom-battle.aspx"&gt;Mario + Rabbids, Kingdom Battle&lt;/a&gt; (music by Kirkhope) or the indie hit turned Legend of Zelda spinoff &lt;a class="reference external" href="https://braceyourselfgames.com/crypt-of-the-necrodancer/"&gt;Crypt of the Necrodancer&lt;/a&gt; (music by Baronowsky). With two great composers behind it, it's no wonder the Desktop Dungeons soundtrack turned out as good as it did.&lt;/p&gt;
&lt;div class="container-fluid"&gt;
    &lt;div class="col-xs-12 col-sm-12 col-md-10 offset-md-1 col-lg-10 offset-lg-1 padding-zero embed-responsive embed-responsive-16by9 mt-4 mb-4"&gt;
            &lt;iframe width="560" height="315" src="https://www.youtube.com/embed/CS0WqQNIy5c" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Mostly orchestral with some great brass sections, these tracks are great to put on when you need a bit of energy but don't want something so intense that it distracts your focus. The game itself is well worth attention too. Branded as a "coffee break" turn-based dungeon crawler, the games 15 minute rounds are incredibly satisfying, and the alpha is available for free on the &lt;a class="reference external" href="http://www.desktopdungeons.net/media/"&gt;developer's website&lt;/a&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="payday-2-simon-viklund"&gt;
&lt;h2&gt;2. Payday 2 - Simon Viklund&lt;/h2&gt;
&lt;p&gt;Similar to Doom, &lt;a class="reference external" href="https://www.overkillsoftware.com/games/payday-2/"&gt;Payday 2's&lt;/a&gt; soundtrack is full of blood pumping tracks to keep you amped while you fly through for loops (or rob a bank, you do you). Unlike Doom's metal approach, however, Payday's songs are more suited for an early 2000's rave, with lots of industrial sound effects and synthesizers.&lt;/p&gt;
&lt;div class="container-fluid"&gt;
    &lt;div class="col-xs-12 col-sm-12 col-md-10 offset-md-1 col-lg-10 offset-lg-1 padding-zero embed-responsive embed-responsive-16by9 mt-4 mb-4"&gt;
            &lt;iframe width="560" height="315" src="https://www.youtube.com/embed/uaP54HhcS8Q" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Only recently going up on &lt;a class="reference external" href="https://open.spotify.com/album/3vRxcJ43dTDDx5a77ccgxW?si=QkmTOcGlSuuy80MW2AvQUQ"&gt;Spotify&lt;/a&gt;, these tracks are the creations of &lt;a class="reference external" href="https://twitter.com/SimonViklund"&gt;Simon Viklund&lt;/a&gt;, who has since left &lt;a class="reference external" href="https://www.overkillsoftware.com/"&gt;Overkill Software&lt;/a&gt; to make another well received Co-Op shooter, &lt;a class="reference external" href="https://store.steampowered.com/app/493520/GTFO/"&gt;GTFO&lt;/a&gt;. Supposedly Payday 3 is in the works for a &lt;a class="reference external" href="https://www.gameinformer.com/2021/03/19/payday-3-release-set-for-2023-with-new-publishing-deal-18-month-post-launch-support"&gt;2023 release&lt;/a&gt;, but with both Viklund and original game designer Ulf Anderson out of the picture, I'm hard pressed to believe it will capture the charm of the originals.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="a-hat-in-time-pascal-michael-stiefel"&gt;
&lt;h2&gt;1. A Hat in Time - Pascal Michael Stiefel&lt;/h2&gt;
&lt;p&gt;If you need to dedicate a long period of time to focus, look no further than the soundtrack to &lt;a class="reference external" href="https://gearsforbreakfast.com/games/a-hat-in-time/"&gt;A Hat In Time&lt;/a&gt;. Clocking in at over five hours, this is the perfect soundtrack to put on when you want a constant stream of background music without jumping between albums.&lt;/p&gt;
&lt;div class="container-fluid"&gt;
    &lt;div class="col-xs-12 col-sm-12 col-md-10 offset-md-1 col-lg-10 offset-lg-1 padding-zero embed-responsive embed-responsive-16by9 mt-4 mb-4"&gt;
            &lt;iframe src="https://www.youtube.com/embed/2c1iSpk3u1A" title="YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen&gt;&lt;/iframe&gt;
    &lt;/div&gt;
&lt;/div&gt;&lt;p&gt;Originally released in 2017, this is the only game on the list that I hadn't played before hearing the soundtrack. But after listening to it multiple times, I've picked it up to see just what crazy world all these upbeat tracks exist in.  Standout songs are &lt;a class="reference external" href="https://www.youtube.com/watch?v=wwGnXPpbu4A"&gt;Train Rush&lt;/a&gt; and &lt;a class="reference external" href="https://youtu.be/2c1iSpk3u1A"&gt;Trainwreck of Electro Swing&lt;/a&gt; but you really can't to wrong with any of the tracks that vary from slow and atmospheric, to upbeat and jazzy. Best of all, each song is full-length and fleshed out, instead of being a collection of looping sound bytes.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</description><category>music</category><category>video games</category><guid>https://www.fotonixx.com/posts/5-video-game-soundtracks-to-jam-out-to-while-coding/</guid><pubDate>Sat, 24 Apr 2021 20:35:11 GMT</pubDate></item><item><title>2D Binary Spacing Partitioning with Python and NetworkX</title><link>https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/bsp_2d/opengraph.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;I'm a sucker for tricks programmers use to get games to run efficiently, from the &lt;a class="reference external" href="https://www.pcgamer.com/heres-whats-happening-inside-fallout-3s-metro-train/"&gt;metro train in Fall Out 3&lt;/a&gt; that's actually a piece of armor, to the &lt;a class="reference external" href="https://kotaku.com/the-invisible-bunnies-that-power-world-of-warcraft-1791576630"&gt;spell casting bunnies&lt;/a&gt; in World of Warcraft that triggered world events. In a slightly different camp, however, are tricks that enabled game makers to do the impossible with the hardware they're given, and one name that's constantly tied to these feats is &lt;a class="reference external" href="https://en.wikipedia.org/wiki/John_Carmack"&gt;John Carmack&lt;/a&gt;, cofounder of id software and pioneer of 3D computer games. His use of ray casting for Wolfenstein 3D and the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Fast_inverse_square_root"&gt;fast inverse square root algorithm&lt;/a&gt; alone are topics worthy of their own posts, but today I'm going to focus on the algorithm he used to enable real-time 3D level rendering in the original Doom: binary space partitioning (BSP).&lt;/p&gt;
&lt;p&gt;Carmack's vision for Doom was to have maps with varying heights, zone-specific lighting, and non-orthogonal walls. Unfortunately, the ray casting algorithm used in id's previous game, Wolfenstein 3D, couldn't render such complex scenes, so he was left looking for a new solution. David Kushner's &lt;a class="reference external" href="http://www.davidkushner.com/book/masters-of-doom/"&gt;Masters of Doom&lt;/a&gt;, as well as this post on &lt;a class="reference external" href="https://twobithistory.org/2019/11/06/doom-bsp.html"&gt;twobithistory&lt;/a&gt; have already covered this story wonderfully, so I won't go into excessive detail but instead focus on the solution. By representing levels as binary trees of line segments, the rendering engine could quickly decide which surfaces to draw and in what order. Despite being incredibly curious when I first read Masters of Doom, I wasn't working on anything related to computer graphics at the time, and filed BSP trees off for something to look at another day.&lt;/p&gt;
&lt;p&gt;Fast forward to now: I'm trying to incorporate OpenGL rendering into my &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;ray tracer&lt;/a&gt;, which requires &lt;a class="reference external" href="https://www.fotonixx.com/posts/efficient-csg/"&gt;constructive solid geometry&lt;/a&gt; performed on polygonal meshes. Turns out binary space partitioning is a core part of this algorithm too, so it's finally time to dive in and learn how to create and use BSP trees!&lt;/p&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="contents"&gt;
&lt;p class="topic-title"&gt;Contents&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#what-s-so-special-about-binary-space" id="id1"&gt;What's So Special About Binary Space?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#creating-a-bsp-tree" id="id2"&gt;Creating a BSP Tree&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#the-bsp-algorithm" id="id3"&gt;The BSP algorithm&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#bisecting-a-line" id="id4"&gt;Bisecting a Line&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#intersecting-two-lines" id="id5"&gt;Intersecting Two Lines&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#ahead-behind-bisected-or-colinear" id="id6"&gt;Ahead, Behind, Bisected or Colinear&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#putting-it-into-a-function" id="id7"&gt;Putting it Into a Function&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#handling-bisected-lines" id="id8"&gt;Handling Bisected Lines&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#recursively-building-the-tree" id="id9"&gt;Recursively Building the Tree&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#taking-it-for-a-test-drive" id="id10"&gt;Taking it for a Test Drive&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="contents alert alert-primary ml-0 topic" id="quick-links"&gt;
&lt;p class="topic-title"&gt;Quick Links&lt;/p&gt;
&lt;ul class="simple"&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#what-s-so-special-about-binary-space" id="id11"&gt;What's So Special About Binary Space?&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#creating-a-bsp-tree" id="id12"&gt;Creating a BSP Tree&lt;/a&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#the-bsp-algorithm" id="id13"&gt;The BSP algorithm&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#bisecting-a-line" id="id14"&gt;Bisecting a Line&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#recursively-building-the-tree" id="id15"&gt;Recursively Building the Tree&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#taking-it-for-a-test-drive" id="id16"&gt;Taking it for a Test Drive&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;div class="section" id="what-s-so-special-about-binary-space"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id11"&gt;What's So Special About Binary Space?&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Binary space partitioning divides an n-dimensional space into segments that are entirely in front or behind a dividing &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Hyperplane"&gt;hyperplane&lt;/a&gt;. For now we'll focus on the 2D case, which means the partition splits a group of line segments into sets that are either in front, behind, or colinear to a dividing line. Repeatedly performing this partitioning on the resulting sets creates a binary tree, where every node contains a dividing line and all colinear segments, and each edge connects to a child node whose segments are completely in front or behind the current node.&lt;/p&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/tree_construction.gif" src="https://www.fotonixx.com/images/bsp_2d/tree_construction.gif" style="width: 600px;"&gt;
&lt;p class="caption"&gt;Construction of a BSP tree by repeatedly subdividing a collection of surfaces. Edges labeled + are in front of the previous node, and - are behind.&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;The most popular use for BSP trees is for &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Hidden-surface_determination"&gt;visible surface determination&lt;/a&gt; in computer graphics, notably for the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Painter%27s_algorithm"&gt;painter's algorithm&lt;/a&gt;. As the painter's algorithm draws the entire set of surfaces from back to front, it needs to be able to quickly sort the surfaces from any viewpoint. BSP trees allow for the computationally expensive sorting to happen just once. When the scene needs to be rendered the tree is traversed in linear time from the current viewpoint and a sorted list of surfaces is generated. It's worth noting this only works for static surfaces, which made it ideal for rendering Doom's level maps. As soon as an individual surface is transformed, however, the entire tree needs to be regenerated.&lt;/p&gt;
&lt;p&gt;A less famous use of BSP trees is performing constructive solid geometry (CSG) operations on discrete polygonal meshes. This application uses two separate BSP trees, one for each object the operations are being applied to. The trees are then 'projected' onto each other, resulting in a set of surfaces from one object that are entirely in the other. Depending on the operation (union, intersection, or difference), certain surfaces are discarded, and the remaining ones make up the new shape. The actual CSG algorithm is beyond the scope of this post, but the documentation and code for the &lt;a class="reference external" href="http://evanw.github.io/csg.js/docs/"&gt;csg.js&lt;/a&gt; library provides a basic explanation.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="creating-a-bsp-tree"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id12"&gt;Creating a BSP Tree&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;In addition to the standard library, we'll use &lt;a class="reference external" href="https://numpy.org"&gt;Numpy&lt;/a&gt; to calculate the line-segment intersections, and &lt;a class="reference external" href="https://networkx.org"&gt;NetworkX&lt;/a&gt; to create the tree. NetworkX is a general purpose graph library that prevents us from having to define our own nodes and edges, and includes extensive traversal algorithms and visualization features. Both packages are installable via pip.&lt;/p&gt;
&lt;pre class="code shell"&gt;&lt;a name="rest_code_e00307786cce40c48ed3c2fb374ec514-1"&gt;&lt;/a&gt;py -m pip install numpy, networkx
&lt;/pre&gt;&lt;div class="section" id="the-bsp-algorithm"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id13"&gt;The BSP algorithm&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Below are the steps we'll be implementing to create our tree. Given a set of 2D line segments and a dividing line:&lt;/p&gt;
&lt;ol class="arabic simple"&gt;
&lt;li&gt;&lt;p&gt;Calculate the intersection of the dividing line with all segments.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Split any segments that intersect the dividing line into two segments at the intersection.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Add colinear line segments to the current node of the tree.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new child node and repeat steps 1-6 for all segments in front of the dividing line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Create a new child node and repeat steps 1-6 for all segments behind the dividing line.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Connect the new nodes to the current node with an edge.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;Since tree generation is a recursive process, steps 4 and 5 repeat every step up to and &lt;em&gt;including&lt;/em&gt; themselves until every line segment can be described as colinear to a node. Also, the number of segments in the returned tree may drastically exceed the number of segments in the original set, depending on how many are bisected at each iteration. We'll break the algorithm down into two pieces: the code used to intersect and split a set of segments with a dividing line, and the recursive function that builds the tree.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="bisecting-a-line"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id14"&gt;Bisecting a Line&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;The bulk of the processing is determining where each line segment in a set intersects the dividing line. We'll make a function called &lt;code&gt;bisect&lt;/code&gt; that accepts an array of line segments and a dividing line, and returns three arrays, one each for segments ahead, behind, and colinear to the dividing line.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_b765b8eb07f34dbd885f911aaa8df24e-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;Tuple&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
&lt;a name="rest_code_b765b8eb07f34dbd885f911aaa8df24e-2"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# separates a set of line segments based on where they intercept a dividing line&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The segments array is an Nx2x2 array, where N is the number of segments being sorted. Each row of the array is a set of two xy-pairs representing the start and endpoints of the segment. For example, a square centered at the origin is composed of four segments:&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;square&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-2"&gt;&lt;/a&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="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="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="o"&gt;-&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-3"&gt;&lt;/a&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="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="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;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-4"&gt;&lt;/a&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;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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]],&lt;/span&gt;
&lt;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-5"&gt;&lt;/a&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="mi"&gt;1&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;a name="rest_code_1e254df3e60949d08a264a6abd34fdc2-6"&gt;&lt;/a&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Similarly, a line is represented by a single 2x2 array of two XY-pairs. The advantage of using Numpy data structures instead of Python lists and tuples is that we can apply the intersection algorithm to the entire segment array at once, instead of iterating over each segment with a for loop. Since Numpy calls compiled C-code, there is almost always a performance gain when a loop can be replaced with array operations.&lt;/p&gt;
&lt;p&gt;The last decision we need to make before writing the bisect function is coming up with a consistent definition for what "in front" and "behind" mean in the context of our lines. An easy enough way to visualize it is this: If you take your right hand and orient it so that the pad of your hand is on top of the first point of the line, and your fingers point towards the second, everything on the left side of your hand (closest to the palm) is in front of the line, and everything on the right side (closes to the back of your hand) is behind the line. Later we'll define this more formally with projections onto a normal vector of the line. If the concept of a vector projection is new, I recommend reading up on dot and cross-products, or skipping straight to the &lt;a class="reference internal" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#implementation"&gt;implementation&lt;/a&gt;.&lt;/p&gt;
&lt;img alt="/images/bsp_2d/line_ahead_behind.png" class="blog-figure align-center" src="https://www.fotonixx.com/images/bsp_2d/line_ahead_behind.png"&gt;
&lt;div class="section" id="intersecting-two-lines"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id5"&gt;Intersecting Two Lines&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;To calculate the intercepts we'll describe the lines and segments as vectors with one independent variable.&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
\begin{aligned}
\vec{l}(t) &amp;amp;= \vec{p}_0+(\vec{p}_1-\vec{p}_0)*t \\
&amp;amp;= \vec{p}_0 + \vec{v}*t
\end{aligned}
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Since segments have a defined start and end, t is valid over a closed range &lt;span class="math"&gt;\(t \in [0,1]\)&lt;/span&gt;. Lines on the other hand extend infinitely in all directions, so t can be any real number. For convenience I'm going to refer to our line segments as &lt;span class="math"&gt;\(\vec{g}(t) = \vec{p_0} + \vec{v}_0t\)&lt;/span&gt;, and the dividing line as &lt;span class="math"&gt;\(l(s) = \vec{p}_1 + \vec{v}_1 s\)&lt;/span&gt;. We're interested in the value t where the two lines meet.&lt;/p&gt;
&lt;div class="row docutils container"&gt;
&lt;div class="col-lg-5 col-md-12 my-auto docutils container"&gt;
&lt;div class="math"&gt;
\begin{align*}
\begin{aligned}
\vec{g}(t) &amp;amp;= \vec{l}(s) \\
\vec{p}_0 + \vec{v}_0 t &amp;amp;= \vec{p}_1 + \vec{v}_1 s \\
&amp;amp;...
\end{aligned}\\
t = \frac{(\vec{p}_1-\vec{p}_0) \times \vec{v}_1}{\vec{v}_0 \times \vec{v}_1}
\end{align*}
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="col-lg-7 col-md-12 my-auto docutils container"&gt;
&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/line_intersection.png" src="https://www.fotonixx.com/images/bsp_2d/line_intersection.png"&gt;
&lt;p class="caption"&gt;intersection point shown in red&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p&gt;I've skipped a few steps in the above equation because the &lt;a class="reference external" href="https://stackoverflow.com/questions/563198/how-do-you-detect-where-two-line-segments-intersect"&gt;intercept of two vector equations&lt;/a&gt; is well understood. What we're more interested in is using the results to determine where the segments lie with respect to our line.&lt;/p&gt;
&lt;p&gt;Red flags might be going off because this equation is dividing two vectors, which is (1) an invalid operation and (2) can't result in a scaler. Since our lines and segments are all 2D, however, we're going to use the 2D definition of cross product: &lt;span class="math"&gt;\(\vec{A} \times \vec{B} = A_x*B_y-A_y*B_x\)&lt;/span&gt;. This scaler is the z-component of the cross product vector we &lt;em&gt;would&lt;/em&gt; have gotten had we taken a traditional 3D cross-product and set the z-values to zero for &lt;span class="math"&gt;\(\vec{A}\)&lt;/span&gt; &amp;amp; &lt;span class="math"&gt;\(\vec{B}\)&lt;/span&gt;.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="ahead-behind-bisected-or-colinear"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id6"&gt;Ahead, Behind, Bisected or Colinear&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;It's easy to tell that the line bisects a given segment when the t is between 0 and 1. What about the other cases though: how do we determine if the segment is in front or behind the line, and what about cases where t is undefined? To get more insight into what's happening with the intersection equation, we'll look at the numerator and denominator separately.&lt;/p&gt;
&lt;dl&gt;
&lt;dt&gt;Numerator&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;We already defined the two-dimensional vector cross product: &lt;span class="math"&gt;\(\vec{A} \times \vec{B} = A_x*B_y-A_y*B_x\)&lt;/span&gt;. Taking a closer look, it's identical to &lt;span class="math"&gt;\((A_x, A_y) \cdot (B_y, -B_x)\)&lt;/span&gt;, and &lt;span class="math"&gt;\((B_y, -B_x)\)&lt;/span&gt; is the same as rotating &lt;span class="math"&gt;\((B_x,B_y)\)&lt;/span&gt; clockwise 90 degrees, aka it's &lt;em&gt;proportional to the unit normal vector of the dividing line&lt;/em&gt;. The numerator can be rewritten as:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
((\vec{p}_0-\vec{p}_1) \cdot \hat{n})|\vec{v}_1|
\end{equation*}
&lt;/div&gt;
&lt;p&gt;Meaning the numerator is taking the distance between a point on the segment and a point on the line, and projecting that vector onto the normal vector of the line (this is identical to calculating the &lt;a class="reference external" href="https://en.wikipedia.org/wiki/Distance_from_a_point_to_a_line"&gt;shortest distance between a point and a line&lt;/a&gt;). From our definition of "in front" and "behind", we can claim that a line segment is &lt;em&gt;partially&lt;/em&gt; in front of the line if the numerator is &amp;gt; 0, and &lt;em&gt;partially&lt;/em&gt; behind if the numerator is &amp;lt; 0. If the numerator is equal to zero, it means the first point of the segment is on the line, and we need to look at the denominator to define whether it's in front or behind.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;dl&gt;
&lt;dt&gt;Denominator&lt;/dt&gt;
&lt;dd&gt;&lt;p&gt;Using the same logic as above, the denominator can be rewritten as a vector projection onto the normal vector:&lt;/p&gt;
&lt;div class="math"&gt;
\begin{equation*}
-(\vec{v}_0 \cdot \hat{n})|\vec{v}_1|
\end{equation*}
&lt;/div&gt;
&lt;p&gt;This projection is a measure of how far &lt;span class="math"&gt;\(\vec{g}(t)\)&lt;/span&gt; travels towards line &lt;span class="math"&gt;\(\vec{l}(s)\)&lt;/span&gt; per interval t. Since the numerator is how far the lines are apart (projected onto the normal vector), and the denominator is how fast one line is approaching the second along that same normal, it makes sense that the ratio is the amount of time (t) until they intersect!&lt;/p&gt;
&lt;p&gt;If the denominator is equal to zero, the two lines are parallel, and the sign of the numerator determines where the segment lies relative to the line.&lt;/p&gt;
&lt;/dd&gt;
&lt;/dl&gt;
&lt;p&gt;With the exception of cases where t is between 0 &amp;amp; 1, the numerator and denominator must be considered when defining the segments location. A summary of all cases is given below.&lt;/p&gt;
&lt;table class="table table-striped table-sm blog-table bsp-cases w-85 mx-auto"&gt;
&lt;colgroup&gt;
&lt;col style="width: 25%"&gt;
&lt;col style="width: 17%"&gt;
&lt;col style="width: 21%"&gt;
&lt;col style="width: 37%"&gt;
&lt;/colgroup&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th class="head"&gt;&lt;p&gt;t&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Numerator&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Denominator&lt;/p&gt;&lt;/th&gt;
&lt;th class="head"&gt;&lt;p&gt;Line Segment Location&lt;/p&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;0 &amp;lt; t &amp;lt; 1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Bisected by line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t &amp;gt; 0 or t &amp;lt; 1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;gt; 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;In front of line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t &amp;gt; 0 or t &amp;lt; 1&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;lt;0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Behind line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t = 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;lt; 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;In front of line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;t = 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;&amp;gt; 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Behind line&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;p&gt;-&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;= 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;= 0&lt;/p&gt;&lt;/td&gt;
&lt;td&gt;&lt;p&gt;Colinear&lt;/p&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;/div&gt;
&lt;div class="section" id="putting-it-into-a-function"&gt;
&lt;span id="implementation"&gt;&lt;/span&gt;&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id7"&gt;Putting it Into a Function&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;Cases in hand we're ready to implement the first half of our bisect function!&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;segment_start&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;segment_end&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&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="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:]&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-5"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-6"&gt;&lt;/a&gt;    &lt;span class="n"&gt;v0&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment_end&lt;/span&gt; &lt;span class="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;segment_start&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-7"&gt;&lt;/a&gt;    &lt;span class="n"&gt;v1&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;line&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="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;line&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;a name="rest_code_8a768217953846ed94411a9a13679a69-8"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-9"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# need to solve for the intersection equation, first find the numerator and denominator&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-10"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-11"&gt;&lt;/a&gt;    &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;line&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="o"&gt;-&lt;/span&gt; &lt;span class="n"&gt;segment_start&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-12"&gt;&lt;/a&gt;    &lt;span class="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cross&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;v1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-13"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-14"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# if the denominator is zero the lines are parallel&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denominator&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;a name="rest_code_8a768217953846ed94411a9a13679a69-16"&gt;&lt;/a&gt;    &lt;span class="n"&gt;not_parallel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-17"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-18"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# the intersection time is the point along the line segment where the line bisects it&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-19"&gt;&lt;/a&gt;    &lt;span class="n"&gt;intersection&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-20"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-21"&gt;&lt;/a&gt;    &lt;span class="n"&gt;ahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&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="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-22"&gt;&lt;/a&gt;    &lt;span class="n"&gt;behind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_or&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&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="n"&gt;denominator&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-23"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-24"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# segments are colinear if they are parallel and the numerator is zero&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-25"&gt;&lt;/a&gt;    &lt;span class="n"&gt;colinear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isclose&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;numerator&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;a name="rest_code_8a768217953846ed94411a9a13679a69-26"&gt;&lt;/a&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-27"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# bisected segments are segments that aren't parallel and t is in (0,1)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-28"&gt;&lt;/a&gt;    &lt;span class="n"&gt;bisected&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-29"&gt;&lt;/a&gt;        &lt;span class="n"&gt;not_parallel&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-30"&gt;&lt;/a&gt;        &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_and&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;intersection&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;intersection&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_8a768217953846ed94411a9a13679a69-31"&gt;&lt;/a&gt;    &lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Right now all it does is build a set of boolean masks used to index the segment array. Before returning the new segments we need to write some code to split bisected segments into two new segments. Notice how when calculating t the denominator has an extra component &lt;code&gt;parallel&lt;/code&gt;. This boolean value is cast as a 1 if the denominator would have been zero, and prevents Numpy from throwing a warning about divide by zero errors.&lt;/p&gt;
&lt;/div&gt;
&lt;div class="section" id="handling-bisected-lines"&gt;
&lt;h4&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id8"&gt;Handling Bisected Lines&lt;/a&gt;&lt;/h4&gt;
&lt;p&gt;If a segment is bisected by the line at time t, we need to create two new segments, one from 0-t, and a second from t-1. Fancy indexing with Numpy can be slower than doing simple operations on an entire array (this is highly dependent on the application), so we'll create two new segments for every segment and then filter.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_1b6a6fd3712c47e6ae465a9b55d4b927-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;intersection_points&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segment_start&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;intersection&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newaxis&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="n"&gt;v0&lt;/span&gt;
&lt;a name="rest_code_1b6a6fd3712c47e6ae465a9b55d4b927-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;l_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;segments&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="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;:],&lt;/span&gt; &lt;span class="n"&gt;intersection_points&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;axis&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;a name="rest_code_1b6a6fd3712c47e6ae465a9b55d4b927-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;r_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stack&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;intersection_points&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;segments&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="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="n"&gt;axis&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;/pre&gt;&lt;p&gt;Notice how they're split into left and right segments, not 'in front' and 'behind'. That's because right now we don't know which segments are which, and need to filter them based on the numerator. If the numerator is &amp;gt;0, the first point of the segment is ahead of the line, making &lt;code&gt;l_segment&lt;/code&gt; ahead and &lt;code&gt;r_segment&lt;/code&gt; behind.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_0baa2e745d204252b4288546bdd25512-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;mask&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;numerator&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="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newaxis&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;newaxis&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;a name="rest_code_0baa2e745d204252b4288546bdd25512-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;bisected_ahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;l_segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r_segments&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;bisected&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;a name="rest_code_0baa2e745d204252b4288546bdd25512-3"&gt;&lt;/a&gt;&lt;span class="n"&gt;bisected_behind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;where&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;logical_not&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mask&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="n"&gt;l_segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;r_segments&lt;/span&gt;&lt;span class="p"&gt;)[&lt;/span&gt;&lt;span class="n"&gt;bisected&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
&lt;/pre&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/left_right_segments.png" src="https://www.fotonixx.com/images/bsp_2d/left_right_segments.png"&gt;
&lt;p class="caption"&gt;An example of two segments where the left and right splits are on opposite sides of the dividing line&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Once the bisected segments have been split, all that's left to do is combine them with their respective ahead/behind sets, and return the three sets of segments. The full function is found &lt;a class="reference external" href="https://github.com/rfrazier716/bsp/blob/12af4fba95cc594b2f3da604491de75d508d1d58/bsp.py#L9"&gt;here&lt;/a&gt;, with a set of if statements to catch cases where ahead/behind sets are empty arrays. The &lt;code&gt;Tuple&lt;/code&gt; Typehint is from the &lt;a class="reference external" href="https://docs.python.org/3/library/typing.html"&gt;typing&lt;/a&gt; module, and is useful for IDE's to infer what types the function will return.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="recursively-building-the-tree"&gt;
&lt;h3&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id15"&gt;Recursively Building the Tree&lt;/a&gt;&lt;/h3&gt;
&lt;p&gt;Compared to the amount of code required to partition our line segments, the function build the BSP tree is relatively simple.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-2"&gt;&lt;/a&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-3"&gt;&lt;/a&gt;    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-4"&gt;&lt;/a&gt;        &lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colinear&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bisect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# get the bisected segments&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-5"&gt;&lt;/a&gt;        &lt;span class="n"&gt;node_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# make your line hashable so it's usable as a node&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-6"&gt;&lt;/a&gt;        &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_node&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;line&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;division_line&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;colinear_segments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;colinear&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# add the node to the graph&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-7"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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="c1"&gt;# if there's any elements behind&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-8"&gt;&lt;/a&gt;            &lt;span class="n"&gt;node_behind&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;behind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;behind&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="c1"&gt;# recursively call for all segments behind&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-9"&gt;&lt;/a&gt;            &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_behind&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&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="c1"&gt;# add an edge from this node to the behind node&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-10"&gt;&lt;/a&gt;        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;size&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;a name="rest_code_6dad8242166e408980a244b8403ef030-11"&gt;&lt;/a&gt;            &lt;span class="n"&gt;node_ahead&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ahead&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="c1"&gt;# recursively call for all segments in front&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-12"&gt;&lt;/a&gt;            &lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;add_edge&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;node_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;node_ahead&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;position&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="c1"&gt;# add an edge from this node to the front node&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-13"&gt;&lt;/a&gt;        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;node_id&lt;/span&gt;  &lt;span class="c1"&gt;# return the hashed id&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-14"&gt;&lt;/a&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-15"&gt;&lt;/a&gt;    &lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# make a new directed graph&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-16"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-17"&gt;&lt;/a&gt;        &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&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;a name="rest_code_6dad8242166e408980a244b8403ef030-18"&gt;&lt;/a&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-19"&gt;&lt;/a&gt;    &lt;span class="c1"&gt;# run the recursive helper function, which should add all nodes and edges&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-20"&gt;&lt;/a&gt;    &lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_6dad8242166e408980a244b8403ef030-21"&gt;&lt;/a&gt;    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relabel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert_node_labels_to_integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Let's pick it apart, starting with the function signature. The function expects a nx2x2 array of line segments, and an optional starting segment that it used as the first dividing line. The output will be a NetworkX directed graph object where every node is a dividing line holding the colinear segments for that line.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_1e4373d6ca744ec48bd82049dcd09156-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;ndarray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Next is the initialization block, here we create the actual graph object and check if the user provided a starting segment. If no segment was given, the first segment of the array is used instead. The function then calls the bsp_helper function that fills the graph with divided segments, and then relabels all nodes sequentially before returning the graph.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;graph&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;  &lt;span class="c1"&gt;# make a new directed graph&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-2"&gt;&lt;/a&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-3"&gt;&lt;/a&gt;    &lt;span class="n"&gt;starting_segment&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&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;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-4"&gt;&lt;/a&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-5"&gt;&lt;/a&gt;&lt;span class="c1"&gt;# run the recursive helper function, which should add all nodes and edges&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;bsp_helper&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;starting_segment&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_3bcbcd11a1f74679a17cd2380cd7fd6e-7"&gt;&lt;/a&gt;&lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;relabel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;convert_node_labels_to_integers&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;graph&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;The final piece is the nested bsp_helper function. Defining bsp_helper within build_tree allows it to access the graph variable without explicitly passing it (this is part of the &lt;a class="reference external" href="https://realpython.com/python-namespaces-scope/#variable-scope"&gt;enclosing scope&lt;/a&gt; in an LEBG model). It also keeps bsp_helper out of the module namespace. The function calculates where each segment in a set falls relative to the dividing line. It then creates a new node with two attributes: the dividing line of the node, and any segments that are colinear to that dividing line. If there's any segments in front of the line, the helper function recursively calls itself with only the set of segments ahead of current dividing line, returning the id of the created node. The returned node is connected to the current node with an edge, whose position attribute is +1 (meaning it's ahead of the current node). The same is then done for segments behind the current line. If recursive functions are a new concept, RealPython has a &lt;a class="reference external" href="https://realpython.com/python-thinking-recursively/"&gt;great tutorial&lt;/a&gt; on understanding and implementing them, as well as introduces the &lt;a class="reference external" href="https://docs.python.org/3/library/functools.html#functools.lru_cache"&gt;@lru_cache&lt;/a&gt; decorator.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;div class="section" id="taking-it-for-a-test-drive"&gt;
&lt;h2&gt;&lt;a class="toc-backref" href="https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/#id16"&gt;Taking it for a Test Drive&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;As a last step we'll create a tree from scratch and plot the results. First lets make a function to draw the segments stored in a BSP tree, with markers showing where the the segments are divided. The drawing itself will be handled by &lt;a class="reference external" href="https://matplotlib.org"&gt;matplotlib&lt;/a&gt;, which can also be installed from pip.&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-1"&gt;&lt;/a&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;draw_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;DiGraph&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-2"&gt;&lt;/a&gt;    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="kc"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-3"&gt;&lt;/a&gt;        &lt;span class="n"&gt;axis&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;gca&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-4"&gt;&lt;/a&gt;    &lt;span class="n"&gt;all_segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;concatenate&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;value&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="nb"&gt;dict&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;get_node_attributes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;"colinear_segments"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;values&lt;/span&gt;&lt;span class="p"&gt;()])&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-5"&gt;&lt;/a&gt;    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;segment&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;all_segments&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;a name="rest_code_585c80c0bcd14c5196c2c722da55ad63-6"&gt;&lt;/a&gt;        &lt;span class="n"&gt;axis&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;plot&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="n"&gt;segment&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;T&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/pre&gt;&lt;p&gt;Next we'll import some &lt;a class="reference external" href="https://www.fotonixx.com/bsp_2d/points.csv"&gt;prebuilt line segments&lt;/a&gt; and reshape the list of points into an appropriate shape. Then it's as simple as creating the bsp tree and plotting the results!&lt;/p&gt;
&lt;pre class="code python"&gt;&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-1"&gt;&lt;/a&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;np&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;loadtxt&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;"points.csv"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-2"&gt;&lt;/a&gt;&lt;span class="n"&gt;segments&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;reshape&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;shape&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="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;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;a name="rest_code_aa371840d7ef41c085811068dba7a743-3"&gt;&lt;/a&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-4"&gt;&lt;/a&gt;&lt;span class="n"&gt;tree&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_tree&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;segments&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-5"&gt;&lt;/a&gt;&lt;span class="n"&gt;fig&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;figure&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-6"&gt;&lt;/a&gt;&lt;span class="n"&gt;draw_segments&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tree&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;color&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'k'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;marker&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'o'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;a name="rest_code_aa371840d7ef41c085811068dba7a743-7"&gt;&lt;/a&gt;&lt;span class="n"&gt;plt&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;show&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/pre&gt;&lt;div class="figure align-center"&gt;
&lt;img alt="/images/bsp_2d/doom_tree.png" src="https://www.fotonixx.com/images/bsp_2d/doom_tree.png" style="width: 800px;"&gt;
&lt;p class="caption"&gt;The fruit of our labor, bisected points are shown in red&lt;/p&gt;
&lt;/div&gt;
&lt;p&gt;Despite creating an impressive BSP tree, at the end of the day it's still just a graph, and could use a killer application to make the power of BSP shine! &lt;a class="reference external" href="https://github.com/rfrazier716/bsp/blob/master/bsp.py"&gt;The full code&lt;/a&gt; has additional functions for performing operations on trees as well as implementing CSG operations. Alternatively, pair BSP with &lt;a class="reference external" href="http://pyglet.org"&gt;Pyglet&lt;/a&gt; you're half way towards a working 2.5D game engine.&lt;/p&gt;
&lt;/div&gt;&lt;/div&gt;</description><category>graphics</category><category>numpy</category><category>python</category><guid>https://www.fotonixx.com/posts/2d-binary-spacing-partitioning-with-python-and-networkx/</guid><pubDate>Tue, 30 Mar 2021 00:20:56 GMT</pubDate></item><item><title>Rendering Constructive Solid Geometry With Python</title><link>https://www.fotonixx.com/posts/efficient-csg/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/efficient_csg/preview_image.png"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;I recently hit a road block with my &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;ray tracer&lt;/a&gt;: cubes, cylinders, and spheres rendered fine, but there wasn't an easy way to create arbitrary shapes whose intersection and normal functions I hadn't already hard coded. Since &lt;a class="reference external" href="https://github.com/rfrazier716/PyRayT"&gt;PyRayT's&lt;/a&gt; end use is for optical design, at the bare minimum it needed a flexible way to create lenses and mirrors. Flipping through Jamis Bucks' &lt;a class="reference external" href="https://pragprog.com/titles/jbtracer/the-ray-tracer-challenge/"&gt;The Ray Tracer Challenge&lt;/a&gt;, it turns out the last chapter &lt;em&gt;Constructive Solid Geometry&lt;/em&gt; (CSG) addressed my needs perfectly! However, Buck's equations for CSG did not blend well with PyRayTs flow of rendering multiple rays at once. Today I'll be covering my own algorithm for adding constructive solid geometry to a ray tracer, as well as its implementation in Python using &lt;a class="reference external" href="https://numpy.org"&gt;NumPy&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.fotonixx.com/posts/efficient-csg/"&gt;Read more…&lt;/a&gt; (14 min remaining to read)&lt;/p&gt;&lt;/div&gt;</description><category>constructive solid geometry</category><category>csg</category><category>numpy</category><category>pyrayt</category><category>python</category><category>ray tracing</category><category>rendering</category><guid>https://www.fotonixx.com/posts/efficient-csg/</guid><pubDate>Mon, 15 Mar 2021 11:35:26 GMT</pubDate></item><item><title>Getting Up and Running with Nikola</title><link>https://www.fotonixx.com/posts/getting-up-and-running-with-nikola/</link><dc:creator>Ryan Frazier</dc:creator><description>&lt;figure&gt;&lt;img src="https://www.fotonixx.com/images/teaser_html.jpg"&gt;&lt;/figure&gt; &lt;div&gt;&lt;p&gt;I have no background in web development or design, which is probably as good of a disclosure as any when writing a post about deploying a website.&lt;/p&gt;
&lt;p&gt;&lt;a href="https://www.fotonixx.com/posts/getting-up-and-running-with-nikola/"&gt;Read more…&lt;/a&gt; (4 min remaining to read)&lt;/p&gt;&lt;/div&gt;</description><category>github</category><category>nikola</category><category>rst</category><guid>https://www.fotonixx.com/posts/getting-up-and-running-with-nikola/</guid><pubDate>Mon, 01 Mar 2021 21:43:38 GMT</pubDate></item></channel></rss>