<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>adventures in optimization</title>
    <link>https://ryanjoneil.dev/</link>
    <description>Recent content on adventures in optimization</description>
    <generator>Hugo -- 0.151.1</generator>
    <language>en</language>
    <lastBuildDate>Sat, 18 Oct 2025 00:00:00 +0000</lastBuildDate>
    <atom:link href="https://ryanjoneil.dev/index.xml" rel="self" type="application/rss+xml" />
    <item>
      <title>🖍 Visualizing Decision Diagrams with Dash and Cytoscape</title>
      <link>https://ryanjoneil.dev/posts/2025-10-18-visualizing-decision-diagrams-with-dash-and-cytoscape/</link>
      <pubDate>Sat, 18 Oct 2025 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2025-10-18-visualizing-decision-diagrams-with-dash-and-cytoscape/</guid>
      <description>Dash and Cytoscape check most of the boxes for decision diagram visualization</description>
      <content:encoded><![CDATA[<p><a href="../2023-09-13-visualizing-decision-diagrams/">I posted a couple years ago</a> about my adventures applying various graph visualization tools to Decision Diagrams (DD). This is an interesting problem because DDs have characteristics that don&rsquo;t apply to other graphs. They are layered<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> and each arc from a layer <code>n-1</code> to layer <code>n</code> has the same direction. Nodes in a layer can be merged or split apart, with those operations generally staying within the same layer<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>. Sub-diagrams can even be <a href="https://arxiv.org/abs/2302.05483">peeled off of parent diagrams during branch-and-bound</a>, while maintaining much of their original structure.</p>
<p>At the time, I settled on using <a href="https://mermaid.js.org/">Mermaid</a> for automated DD rendering, but that still <a href="../2023-09-13-visualizing-decision-diagrams/#mermaid">had a few issues</a>. The rendering itself was nice, but it was hard to keep labels readable. Mermaid uses its own data format for graph representation. I&rsquo;d rather draw graphs based on something like a Python data structure without translating that data into an intermediate format.</p>
<p>Since then, I&rsquo;ve found myself stepping iteratively through processes that modify DDs in various ways for a side research project<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>. None of the options I looked at before are quite suitable. I need to visually inspect the impacts of DD operations like reduction and restriction, and to separate specific arcs in an iterative process that I can drive interactively. This led me to experiment with <a href="https://dash.plotly.com/">Dash</a> and, by extension, <a href="https://cytoscape.org/">Cytoscape</a><sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup>.</p>
<h2 id="dash--cytoscape">Dash &amp; Cytoscape</h2>
<p>Let&rsquo;s look at the same example diagram as before using Dash. First we initialize a Python list of graph elements to display. We&rsquo;ll feed this list directly into Dash&rsquo;s Cytoscape layout.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>ELEMENTS <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># nodes: layer 0</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;r&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;r&#34;</span>}},
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># arcs: layer 0 -&gt; layer 1</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;r&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;1&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;r&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;2&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;r&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;3&#34;</span>}},
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># nodes: layer 1</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;1&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;0&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;2&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;[[1,2],4]&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;3&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;3&#34;</span>}},
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># arcs: layer 1 -&gt; layer 2</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;2&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;4&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;2&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;5&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;3&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;6&#34;</span>}},
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># nodes: layer 2</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;4&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;10&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;5&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;20&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;6&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;100&#34;</span>}},
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># arcs: layer 2 -&gt; layer 3</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;4&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;t&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;5&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;t&#34;</span>}},
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;6&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;t&#34;</span>}},
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># nodes: layer 3</span>
</span></span><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;id&#34;</span>: <span style="color:#a5d6ff">&#34;t&#34;</span>, <span style="color:#a5d6ff">&#34;label&#34;</span>: <span style="color:#a5d6ff">&#34;t&#34;</span>}},
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>Already this is pretty nice. This list is easy to generate from any graph data model. It&rsquo;s just a flat list of nodes and arcs. If we need to, we can add additional information directly to the <code>data</code> dictionary.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>    {<span style="color:#a5d6ff">&#34;data&#34;</span>: {<span style="color:#a5d6ff">&#34;source&#34;</span>: <span style="color:#a5d6ff">&#34;6&#34;</span>, <span style="color:#a5d6ff">&#34;target&#34;</span>: <span style="color:#a5d6ff">&#34;t&#34;</span>, <span style="color:#a5d6ff">&#34;xyzzy&#34;</span>: <span style="color:#a5d6ff">&#34;plugh&#34;</span>}},
</span></span></code></pre></div><p>Next we initialize Cytoscape layouts and create a Dash app.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">dash_cytoscape</span> <span style="color:#ff7b72">as</span> <span style="color:#ff7b72">cyto</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">dash</span> <span style="color:#ff7b72">import</span> Dash
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>cyto<span style="color:#ff7b72;font-weight:bold">.</span>load_extra_layouts()
</span></span><span style="display:flex;"><span>app <span style="color:#ff7b72;font-weight:bold">=</span> Dash()
</span></span></code></pre></div><p>The <code>app</code> is responsible for serving a web page containing our Cytoscape layout. We can add more data and layouts, interactive elements such as buttons, and logic through <a href="https://dash.plotly.com/basic-callbacks">callbacks</a> to the app as well<sup id="fnref:5"><a href="#fn:5" class="footnote-ref" role="doc-noteref">5</a></sup>.</p>
<p>Now we just add a Cytoscape layout to the <code>app</code> and run it. Note the <code>dagre</code> layout name renders the diagram top down, while <code>klay</code> renders it left to right.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>app<span style="color:#ff7b72;font-weight:bold">.</span>layout <span style="color:#ff7b72;font-weight:bold">=</span> cyto<span style="color:#ff7b72;font-weight:bold">.</span>Cytoscape(
</span></span><span style="display:flex;"><span>    layout<span style="color:#ff7b72;font-weight:bold">=</span>{ <span style="color:#a5d6ff">&#34;name&#34;</span>: <span style="color:#a5d6ff">&#34;dagre&#34;</span> },
</span></span><span style="display:flex;"><span>    elements<span style="color:#ff7b72;font-weight:bold">=</span>ELEMENTS,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>app<span style="color:#ff7b72;font-weight:bold">.</span>run()
</span></span></code></pre></div><p>That&rsquo;s it! So what does our beautiful diagram look like?</p>
<p><img alt="Dash &amp; Cytoscape default styles" loading="lazy" src="/files/2025-10-18-visualizing-decision-diagrams-with-dash-and-cytoscape/diagram-awful.png#center"></p>
<h2 id="styling">Styling</h2>
<p>Wait, that&rsquo;s not very good, is it? If anything, it&rsquo;s at least as bad as any of the other options, right?</p>
<p>At this point, yes, but one of the qualities that separates Cytoscape from other graph visualization options is its capacity for element styling. Let&rsquo;s improve on this visualization by adding some styles.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>STYLES <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;selector&#34;</span>: <span style="color:#a5d6ff">&#34;edge&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;style&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;curve-style&#34;</span>: <span style="color:#a5d6ff">&#34;bezier&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;target-arrow-shape&#34;</span>: <span style="color:#a5d6ff">&#34;triangle&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>    {
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;selector&#34;</span>: <span style="color:#a5d6ff">&#34;node&#34;</span>,
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;style&#34;</span>: {
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;shape&#34;</span>: <span style="color:#a5d6ff">&#34;rectangle&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;width&#34;</span>: <span style="color:#a5d6ff">&#34;label&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;height&#34;</span>: <span style="color:#a5d6ff">&#34;label&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;content&#34;</span>: <span style="color:#a5d6ff">&#34;data(label)&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;font-family&#34;</span>: <span style="color:#a5d6ff">&#34;monospace&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;text-valign&#34;</span>: <span style="color:#a5d6ff">&#34;center&#34;</span>,
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">&#34;padding&#34;</span>: <span style="color:#a5d6ff">&#34;10px&#34;</span>,
</span></span><span style="display:flex;"><span>        },
</span></span><span style="display:flex;"><span>    },
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>app<span style="color:#ff7b72;font-weight:bold">.</span>layout <span style="color:#ff7b72;font-weight:bold">=</span> cyto<span style="color:#ff7b72;font-weight:bold">.</span>Cytoscape(
</span></span><span style="display:flex;"><span>    layout<span style="color:#ff7b72;font-weight:bold">=</span>{ <span style="color:#a5d6ff">&#34;name&#34;</span>: <span style="color:#a5d6ff">&#34;dagre&#34;</span> },
</span></span><span style="display:flex;"><span>    elements<span style="color:#ff7b72;font-weight:bold">=</span>ELEMENTS,
</span></span><span style="display:flex;"><span>    stylesheet<span style="color:#ff7b72;font-weight:bold">=</span>STYLES,
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>app<span style="color:#ff7b72;font-weight:bold">.</span>run()
</span></span></code></pre></div><p>Already, this is significantly better than the default style.</p>
<p><img alt="Dash &amp; Cytoscape improved styles" loading="lazy" src="/files/2025-10-18-visualizing-decision-diagrams-with-dash-and-cytoscape/diagram-better.png#center"></p>
<p>Since the elements and styles are cleanly separated, it&rsquo;s convenient to style nodes and arcs based on aspects of their data. To give you a sense of what this means for DDs, here is a screenshot from that research project I mentioned.</p>
<p><img alt="Dash &amp; Cytoscape enhanced styles" loading="lazy" src="/files/2025-10-18-visualizing-decision-diagrams-with-dash-and-cytoscape/diagram-custom.png#center"></p>
<p>In this case, border colors, background colors, and line styles have different meanings. It&rsquo;s easy to add interactivity like toggling on more information in the node labels, or restructuring the diagram and its representation based on user input. Try out the example to see how Dash is built from the ground up for interactivity.</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2025-10-18-visualizing-decision-diagrams-with-dash-and-cytoscape/dash-cytoscape.py"><code>dash-cytoscape.py</code></a> provides the full example visualization</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>Though this is less the case as DD implementations become more like Dynamic Programming and abandon layers.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Ibid.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Not to make excuses, but research projects go a lot slower lately than they used to.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>Dash delegates all of its graph rendering functionality to Cytoscape, and provides an API layer for graph data management and interactivity.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:5">
<p>These are out of scope for this post.&#160;<a href="#fnref:5" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>🐏 RAMS Reboot</title>
      <link>https://ryanjoneil.dev/posts/2025-06-25-rams-reboot/</link>
      <pubDate>Wed, 25 Jun 2025 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2025-06-25-rams-reboot/</guid>
      <description>Solve MILPs with open source optimization solvers and Ruby.</description>
      <content:encoded><![CDATA[<p>Some years ago, I worked on real-time meal delivery at Zoomer, a YC startup based out of Philadelphia. Zoomer&rsquo;s production tech stack was primarily Ruby. As it grew we moved from using heuristics for things like routing and scheduling to open source optimization solvers.</p>
<p>Like most languages that aren&rsquo;t Python, Ruby doesn&rsquo;t have an especially mature ecosystem for optimization (or data science, or machine learning, for that matter). For some use cases that didn&rsquo;t matter. When we upgraded the routing engine, we built a model in C++ using <a href="https://www.gecode.org/">Gecode</a> and wrapped a Ruby gem around a <a href="https://www.swig.org/">SWIG</a> wrapper. But when we wanted to use integer programming to build schedules, the lack of solver APIs proved inconvenient.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<p>At the time, <a href="https://coin-or.github.io/pulp/index.html">PuLP</a> was probably the most commonly used open source multi-solver Python library for linear and integer programming.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup> This led me the opportunity to develop <a href="https://github.com/ryanjoneil/rams">RAMS</a>, a PuLP-inspired library for basic MILP modeling in Ruby.</p>
<p>Then the Zoomer team became part of Grubhub. We moved to a Java stack and a commercial optimization solver. Improvements to the RAMS project languished on my todo list. It lagged behind major versions of Ruby, optimization, solvers, and dependencies, painfully out of date and unmaintained.</p>
<p>Then, last month, <a href="https://github.blog/news-insights/product-news/github-copilot-meet-the-new-coding-agent/">Github released its Copilot agent</a>. Unlike vibe coding directly in the editor, which sounds like <a href="https://deplet.ing/the-copilot-delusion/">speeding maniacally through a bad acid trip</a>, the idea here is more like running a project: create issues, receive and comment on pull requests, iterate.</p>
<p>I figured the grunt work of library upgrades should be perfect fodder to try out an AI developer assistant. RAMS is already well structured and tested. The upgrade is well defined. No creativity required.</p>
<h2 id="a-rams-modeling-example">A RAMS modeling example</h2>
<p>This post is meandering through two topics: solving optimization models with Ruby and RAMS, and my experiences maintaining that library using Copilot. I could have split this into two posts, but that didn&rsquo;t feel right. So let&rsquo;s show what building a model in RAMS looks like first.</p>
<p>I don&rsquo;t use Ruby with any regularity these days<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup>, but modeling with RAMS reminded me how elegant Ruby DSLs can be. Here&rsquo;s a simple example of a binary integer program.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#!/usr/bin/env ruby</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>require <span style="color:#a5d6ff">&#39;rams&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff;font-weight:bold">RAMS</span><span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#79c0ff;font-weight:bold">Model</span><span style="color:#ff7b72;font-weight:bold">.</span>new
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>x1 <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>variable <span style="color:#a5d6ff">type</span>: <span style="color:#a5d6ff">:binary</span>
</span></span><span style="display:flex;"><span>x2 <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>variable <span style="color:#a5d6ff">type</span>: <span style="color:#a5d6ff">:binary</span>
</span></span><span style="display:flex;"><span>x3 <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>variable <span style="color:#a5d6ff">type</span>: <span style="color:#a5d6ff">:binary</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>constrain(x1 <span style="color:#ff7b72;font-weight:bold">+</span> x2 <span style="color:#ff7b72;font-weight:bold">+</span> x3 <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">2</span>)
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>constrain(x2 <span style="color:#ff7b72;font-weight:bold">+</span> x3 <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>sense <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">:max</span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>objective <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">*</span> x1 <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">2</span> <span style="color:#ff7b72;font-weight:bold">*</span> x2 <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">3</span> <span style="color:#ff7b72;font-weight:bold">*</span> x3
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>solution <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>solve
</span></span><span style="display:flex;"><span>puts <span style="color:#a5d6ff">&lt;&lt;-HERE
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff"></span><span style="color:#a5d6ff">objective</span>: <span style="color:#8b949e;font-style:italic">#{solution.objective}</span>
</span></span><span style="display:flex;"><span>x1 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#8b949e;font-style:italic">#{solution[x1]}</span>
</span></span><span style="display:flex;"><span>x2 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#8b949e;font-style:italic">#{solution[x2]}</span>
</span></span><span style="display:flex;"><span>x3 <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#8b949e;font-style:italic">#{solution[x3]}</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff;font-weight:bold">HERE</span>
</span></span></code></pre></div><p>I think that&rsquo;s rather nice, and very clean.</p>
<h2 id="rams-enhancements">RAMS enhancements</h2>
<p>The biggest change in RAMS is that it now supports the <a href="https://highs.dev/">HiGHS optimization solver</a>. Prior to v0.2.0, GLPK was the default solver, but now that is HiGHS. There are a number of smaller changes as well.</p>
<ul>
<li>RAMS requires Ruby v3.1.</li>
<li>CPLEX support was removed since I can&rsquo;t test it.<sup id="fnref:4"><a href="#fn:4" class="footnote-ref" role="doc-noteref">4</a></sup></li>
<li>One can set solver paths using environment variables (e.g. <code>RAMS_SOLVER_PATH_CBC</code>).</li>
<li>Improved documentation and <a href="https://raw.githubusercontent.com/ryanjoneil/rams/main/logo.svg">a logo</a>!</li>
</ul>
<h2 id="the-copilot-agent-as-coding-companion">The Copilot agent as coding companion</h2>
<p>While I tend to err on the side of LLM skepticism, working with the Copilot agent for this upgrade was generally positive. It was a bit like working with a fast, responsive, and inexperienced developer. The issues it ran into were pretty much the same, but the time scale was compressed.</p>
<p>I had it open three pull requests for me.</p>
<h3 id="-pr-29-upgrade-ruby-and-dependencies">🤨 PR 29: <a href="https://github.com/ryanjoneil/rams/pull/29">Upgrade Ruby and dependencies</a></h3>
<p>Performance here was middling. Copilot got through some of the task without assistance. It also made a number of changes that were unhelpful and irrelevant to the request.</p>
<p>On a positive note, I forgot to ask it to change from CircleCI to GitHub Actions for testing. This gave me <a href="https://github.com/ryanjoneil/rams/pull/29#issuecomment-2997815058">the opportunity to test its response to feature creep</a>. It responded with <a href="https://github.com/ryanjoneil/rams/pull/29/commits/8f8f054044f3008cfd12a8f81bfd32c519555f70">a partially working GitHub Actions workflow</a> (and no grumbling!).</p>
<p>Copilot made a number of errors and wasn&rsquo;t able to finish the upgrade on its own.</p>
<ul>
<li>It decided to <a href="https://github.com/ryanjoneil/rams/pull/29/commits/8f8f054044f3008cfd12a8f81bfd32c519555f70#diff-faff1af3d8ff408964a57b2e475f69a6b7c7b71c9978cccc8f471798caac2c88R44-R65">build the optimizers from source</a> instead of simply installing binary packages using <code>apt</code> or <code>dnf</code>. Not only is this wasteful and overly complicated, <a href="https://github.com/ryanjoneil/rams/actions/runs/15834266623/job/44634918114#step:6:1563">it ultimately wasn&rsquo;t able to build and install them from source</a>.</li>
<li>Once I told it to use a Fedora 42 base image, this improved, but it couldn&rsquo;t figure out what package to use for the CBC solver. It switched back and forth without prompting between <code>cbc</code> (incorrect) and <code>coin-or-Cbc</code> (correct).</li>
<li>It inexplicably <a href="https://github.com/ryanjoneil/rams/pull/29#issuecomment-2997857566">couldn&rsquo;t figure out the latest stable version of Ruby</a>.</li>
<li><a href="https://github.com/ryanjoneil/rams/pull/29#discussion_r2162808749">It added a bunch of architecture-specific package definitions</a> to the build, unprompted. This was unnecessary given that RAMS is a vanilla Ruby project.</li>
<li>I had to help it figure out that <a href="https://github.com/ryanjoneil/rams/pull/29/commits/135b6967d35f530c35ed4e111a04f59ba7300a67">the CBC binary is now called <code>coin.cbc</code> on Fedora</a>. This wasn&rsquo;t entirely surprising.</li>
</ul>
<h3 id="-pr-32-add-environment-variables-for-solver-paths">🤩 PR 32: <a href="https://github.com/ryanjoneil/rams/pull/32">Add environment variables for solver paths</a></h3>
<p>Copilot did a great job on this task. I had no issue with the code it wrote. It followed the style of the rest of the package nicely. It added appropriate documentation and unit tests.</p>
<h3 id="-pr-34-support-the-highs-optimization-solver">👌 PR 34: <a href="https://github.com/ryanjoneil/rams/pull/34">Support the HiGHS optimization solver</a></h3>
<p>Copilot did pretty well here, even though it didn&rsquo;t get the feature working. It was able to create a new solver interface and get most of the logic for solution parsing right. I was a little surprised that <a href="https://github.com/ryanjoneil/rams/pull/34#pullrequestreview-2952084649">it forgot to test the new solver integration in GitHub Actions</a>. The biggest issue it needed my help on <a href="https://github.com/ryanjoneil/rams/pull/34/commits/18e8095e97edba8198e1830e4a2a86960975a964#diff-3ac91a373440402d1cdd88e9987f4ea20efa6c0d82fee69ed7a4b4e0ff24b1d1L22-L23">was solution status parsing</a>, where it didn&rsquo;t realize that the second condition here will never trigger.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">:feasible</span> <span style="color:#ff7b72">if</span> status <span style="color:#ff7b72;font-weight:bold">=~</span> <span style="color:#79c0ff">/feasible/i</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">:infeasible</span> <span style="color:#ff7b72">if</span> status <span style="color:#ff7b72;font-weight:bold">=~</span> <span style="color:#79c0ff">/infeasible/i</span>
</span></span></code></pre></div><p>This should have been the following (note the <code>^</code>).</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-ruby" data-lang="ruby"><span style="display:flex;"><span><span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">:feasible</span> <span style="color:#ff7b72">if</span> status <span style="color:#ff7b72;font-weight:bold">=~</span> <span style="color:#79c0ff">/^feasible/i</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">:infeasible</span> <span style="color:#ff7b72">if</span> status <span style="color:#ff7b72;font-weight:bold">=~</span> <span style="color:#79c0ff">/infeasible/i</span>
</span></span></code></pre></div><div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>I don&rsquo;t remember finding any MILP modeling interfaces for Ruby like <a href="https://coin-or.github.io/pulp/index.html">PuLP</a> in 2016-17. More recently, <a href="https://github.com/wouterken/rulp">Rulp</a> and <a href="https://github.com/ankane/opt">Opt</a> have been developed.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>PulLP is still <a href="https://github.com/coin-or/pulp/graphs/contributors">heavily used and developed</a>.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Once upon a time <a href="/tags/obfuscation/">I was a Perl programmer</a>. Ruby was originally written to be a better Perl. I&rsquo;ve long since given up the old ways.&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:4">
<p>For now, RAMS is focussing on open source solvers. Maintaining commercial solver licenses can be challenging when you&rsquo;re not part of academia. PRs welcome.&#160;<a href="#fnref:4" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>🖖 Hi, I&#39;m Ryan.</title>
      <link>https://ryanjoneil.dev/about/</link>
      <pubDate>Thu, 10 Apr 2025 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/about/</guid>
      <description>&lt;p&gt;I build decision science and optimization software.&lt;/p&gt;
&lt;p&gt;By day, I am an optimization engineer, coder, and co-founder of &lt;a href=&#34;https://nextmv.io&#34;&gt;Nextmv&lt;/a&gt;. I&amp;rsquo;m interested in hybrid optimization, &lt;a href=&#34;https://www.andrew.cmu.edu/user/vanhoeve/mdd/&#34;&gt;decision diagrams&lt;/a&gt;, and &lt;a href=&#34;https://en.wikipedia.org/wiki/Integer_programming&#34;&gt;mixed integer programming&lt;/a&gt;. My applications skew toward logistics for delivery platforms, with detours into &lt;a href=&#34;https://www.euro-online.org/websites/esicup/&#34;&gt;cutting and packing&lt;/a&gt;. Lately I&amp;rsquo;ve been embedding a lot of trained machine learning models in optimization problems, and exploring applications of &lt;a href=&#34;https://pubsonline.informs.org/doi/10.1287/opre.2022.0382&#34;&gt;inverse optimization&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;For the past several years, I&amp;rsquo;ve worked in real-time optimization for on-demand delivery, scheduling, forecasting, and simulation. I received a MS in Operations Research by night at &lt;a href=&#34;https://seor.gmu.edu/&#34;&gt;George Mason University&lt;/a&gt;, then a PhD in the same department under the advisement of &lt;a href=&#34;https://seor.vse.gmu.edu/~khoffman/&#34;&gt;Karla Hoffman&lt;/a&gt;.&lt;/p&gt;</description>
      <content:encoded><![CDATA[<p>I build decision science and optimization software.</p>
<p>By day, I am an optimization engineer, coder, and co-founder of <a href="https://nextmv.io">Nextmv</a>. I&rsquo;m interested in hybrid optimization, <a href="https://www.andrew.cmu.edu/user/vanhoeve/mdd/">decision diagrams</a>, and <a href="https://en.wikipedia.org/wiki/Integer_programming">mixed integer programming</a>. My applications skew toward logistics for delivery platforms, with detours into <a href="https://www.euro-online.org/websites/esicup/">cutting and packing</a>. Lately I&rsquo;ve been embedding a lot of trained machine learning models in optimization problems, and exploring applications of <a href="https://pubsonline.informs.org/doi/10.1287/opre.2022.0382">inverse optimization</a>.</p>
<p>For the past several years, I&rsquo;ve worked in real-time optimization for on-demand delivery, scheduling, forecasting, and simulation. I received a MS in Operations Research by night at <a href="https://seor.gmu.edu/">George Mason University</a>, then a PhD in the same department under the advisement of <a href="https://seor.vse.gmu.edu/~khoffman/">Karla Hoffman</a>.</p>
<h2 id="appearances">Appearances</h2>
<p>This is a running list of talks I&rsquo;ve given and am scheduled to give. It probably isn&rsquo;t exhaustive. Some of them have slides or videos available.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">2025</td>
          <td style="text-align: left">Jun 26</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/operationalize-a-gurobi-price-optimization-notebook-deploy-collaborate-test-and-visualize-with-nextmv">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Operationalize a Gurobi price optimization notebook: Deploy, collaborate, test, and visualize with Nextmv</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">May 15</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://odsc.com/boston/">ODSC East 2025</a></td>
          <td style="text-align: left">Predict &amp; Prescribe: Combining forecasts with optimized plans</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Apr 8</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.uni.lu/fdef-en/research-departments/department-of-economics-and-management/">University of Luxembourg</a></td>
          <td style="text-align: left">Decision model, meet reality: Testing lessons from food logistics and delivery operations</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Mar 14-16</td>
          <td style="text-align: left"><a href="https://symposia.gerad.ca/ICS2025/en/schedule?slot_id=2418">📄</a> <a href="https://symposia.gerad.ca/ICS2025/en/schedule?slot_id=2419">📄</a></td>
          <td style="text-align: left"><a href="https://sites.google.com/view/ics-2025">INFORMS Computing Society Conference 2025</a></td>
          <td style="text-align: left">Chair and organizer of the solvers cluster</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Mar 6</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/nextmv-hexaly-integration-how-run-test-and-manage-with-decisionops-workflow">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Nextmv Hexaly Integration: How to run, test, and manage with DecisionOps workflows</td>
      </tr>
      <tr>
          <td style="text-align: left">2024</td>
          <td style="text-align: left">Nov 7</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/nextmv-ml-or-connectors-a-price-optimization-example-with-gurobipy-gurobi-ml-and-gurobipy-pandas">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Nextmv ML/OR connectors: A price optimization example with Gurobipy, Gurobi ML, and Gurobipy Pandas</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Oct 21</td>
          <td style="text-align: left"><a href="https://submissions.mirasmart.com/InformsAnnual2024/Itinerary/PresentationDetail.aspx?evdid=3149">📄</a> <a href="/slides/2024-informs.pdf">💻</a></td>
          <td style="text-align: left"><a href="https://meetings.informs.org/wordpress/seattle2024/">INFORMS Annual Meeting 2024</a></td>
          <td style="text-align: left">Solving the Weapon Target Assignment Problem with Decision Diagrams</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Oct 3</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/uncertainty-ml-or-and-stochastic-optimization-demo-and-q-a-with-seeker-creator">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Uncertainty, ML + OR, and stochastic optimization: Demo and Q&amp;A with Seeker creator</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Jul 30</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/operationalizing-highs-based-mip-models-and-q-a-with-project-developers">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Operationalizing HiGHS-based MIP models and Q&amp;A with project developers</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Jun 27</td>
          <td style="text-align: left"><a href="/slides/2024-highs-workshop.pdf">💻</a></td>
          <td style="text-align: left"><a href="https://workshop24.highs.dev/">HiGHS Workshop 2024</a></td>
          <td style="text-align: left">Symphonic HiGHS: Operationalizing next moves with DecisionOps</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Jun 18</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/combining-machine-learning-ml-and-operations-research-or-through-horizontal-computing">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Combining machine learning (ML) and operations research (OR) through horizontal computing</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Jun 7</td>
          <td style="text-align: left"><a href="https://www.euro-online.org/websites/or-in-practice/7-june-2024-three-model-problem-combining-machine-learning-ml-and-operations-research-or-through-horizontal-computing/">📄</a> <a href="/slides/2024-euro-practitioners-forum.pdf">💻</a> <a href="https://u.pcloud.link/publink/show?code=RKY">🎥</a></td>
          <td style="text-align: left"><a href="https://www.euro-online.org/web/pages/1699/practitioners-forum">EURO Practitioners&rsquo; Forum</a></td>
          <td style="text-align: left">Three model problem: Combining machine learning (ML) and operations research (OR) through horizontal computing</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Apr 14</td>
          <td style="text-align: left"><a href="https://meetings.informs.org/wordpress/analytics2024/agenda/exhibitor-workshops/#nextmv">📄</a></td>
          <td style="text-align: left"><a href="https://meetings.informs.org/wordpress/analytics2024/">INFORMS Analytics Conference 2024</a></td>
          <td style="text-align: left">The sushi is ready. How do I deliver it? Forecast, schedule, route with DecisionOps</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Apr 10</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/getting-started-with-decisionops-for-decision-science-models-using-gurobi">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Getting started with DecisionOps for decision science models using Gurobi</td>
      </tr>
      <tr>
          <td style="text-align: left">2023</td>
          <td style="text-align: left">Dec 6</td>
          <td style="text-align: left"><a href="https://global2023.pydata.org/cfp/talk/review/QWNCD3B7EP8MR9BZMN7VVG8PG97ZG3L3">📄</a> <a href="https://github.com/ryanjoneil/2023-pydata-global-order-up">🧑‍💻️</a> <a href="/slides/2023-pydata-global.pdf">💻</a> <a href="https://www.youtube.com/watch?v=TFSEaJDEQZw">🎥</a></td>
          <td style="text-align: left"><a href="https://pydata.org/global2023/">PyData Global 2023</a></td>
          <td style="text-align: left">Order up! How do I deliver it? Build on-demand logistics apps with Python, OR-Tools, and DecisionOps</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Nov 16</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/forecast-schedule-route-3-starter-models-for-on-demand-logistics">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Forecast, schedule, route: 3 starter models for on-demand logistics</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Oct 17</td>
          <td style="text-align: left"><a href="https://www.abstractsonline.com/pp8/?__hstc=194041586.cc5f446034c6d268dfea27d9b7ceb6dc.1681140481721.1691273766763.1691449438596.35&amp;__hssc=194041586.3.1691449438596&amp;__hsfp=1912911101&amp;hsCtaTracking=8f511889-324a-41b3-a438-37ad295392e9%7C0c80c5d7-cc8d-4989-9b70-52de4c44b90b#!/10856/presentation/7867">📄</a></td>
          <td style="text-align: left"><a href="https://meetings.informs.org/wordpress/phoenix2023/">INFORMS Annual Meeting 2023</a></td>
          <td style="text-align: left">Adapting to Change in On-Demand Delivery: Unpacking a Suite of Testing Methodologies</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Sep 20</td>
          <td style="text-align: left"><a href="https://decisioncamp2023.wordpress.com/program/#NEXTMV">📄</a> <a href="/slides/2023-decisioncamp.pdf">💻</a> <a href="https://www.youtube.com/watch?v=jh767AIuFP0">🎥</a></td>
          <td style="text-align: left"><a href="https://decisioncamp2023.wordpress.com/">DecisionCAMP 2023</a></td>
          <td style="text-align: left">Decision model, meet the real world: Testing optimization models for use in production environments</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Aug 27</td>
          <td style="text-align: left"><a href="/slides/2023-dpsolve.pdf">💻</a></td>
          <td style="text-align: left"><a href="https://sites.google.com/view/dpsolve2023/">DPSOLVE 2023</a></td>
          <td style="text-align: left">Implementing Decision Diagrams in Production Systems</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">May 11</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/several-people-are-optimizing-collaborative-workflows-for-decision-model-operations">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Several people are optimizing: Collaborative workflows for decision model operations</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Apr 17</td>
          <td style="text-align: left"><a href="https://meetings.informs.org/wordpress/analytics2023/tracks/technology-tutorials/">📄</a></td>
          <td style="text-align: left"><a href="https://meetings.informs.org/wordpress/analytics2023/">INFORMS Analytics Conference 2023</a></td>
          <td style="text-align: left">Decision Model, Meet Production: A Collaborative Workflow for Optimizing More Operations</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Feb 16</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/decision-diagrams-in-operations-research-optimization-vehicle-routing-and-beyond">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Decision diagrams in operations research, optimization, vehicle routing, and beyond</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Jan 18</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/in-conversation-with-karla-hoffman-about-optimization-and-operations-research">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">In conversation with Karla Hoffman</td>
      </tr>
      <tr>
          <td style="text-align: left">2022</td>
          <td style="text-align: left">Nov 16</td>
          <td style="text-align: left"><a href="https://www.nextmv.io/videos/decision-model-meet-production">🎥</a></td>
          <td style="text-align: left"><a href="https://nextmv.io">Nextmv Videos</a></td>
          <td style="text-align: left">Decision model, meet production</td>
      </tr>
      <tr>
          <td style="text-align: left">2020</td>
          <td style="text-align: left">Oct 5</td>
          <td style="text-align: left"><a href="https://youtu.be/t6rI2JFVqG4">🎥</a></td>
          <td style="text-align: left"><a href="https://connect.informs.org/phillychapter/home">INFORMS Philadelphia Chapter</a></td>
          <td style="text-align: left">Real-Time Routing for On-Demand Delivery</td>
      </tr>
      <tr>
          <td style="text-align: left">2019</td>
          <td style="text-align: left">Oct 22</td>
          <td style="text-align: left"><a href="/slides/2019-informs.pdf">💻</a></td>
          <td style="text-align: left">INFORMS Annual Meeting 2019</td>
          <td style="text-align: left">Decision Diagrams for Real-Time Routing</td>
      </tr>
      <tr>
          <td style="text-align: left">2017</td>
          <td style="text-align: left">July 6</td>
          <td style="text-align: left"><a href="https://pydata.org/seattle2017/schedule/presentation/81/">📄</a> <a href="https://youtu.be/n9Fz74pFiA4?list=PLGVZCDnMOq0rxoq9Nx0B4tqtr891vaCn7">🎥</a></td>
          <td style="text-align: left"><a href="https://pydata.org/seattle2017/">PyData Seattle 2017</a></td>
          <td style="text-align: left">Practical Optimization for Stats Nerds</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Mar 5</td>
          <td style="text-align: left"><a href="https://nbviewer.org/github/ryanjoneil/talks/blob/master/2017/07/pydata-seattle/practical-optimization-for-stats-nerds.slides.html#/">💻</a></td>
          <td style="text-align: left"><a href="https://www.meetup.com/Data-Science-DC/events/238107178/">Data Science DC</a></td>
          <td style="text-align: left">Practical Optimization for Stats Nerds</td>
      </tr>
      <tr>
          <td style="text-align: left">2015</td>
          <td style="text-align: left">Dec 4</td>
          <td style="text-align: left"><a href="https://nbviewer.org/github/ryanjoneil/talks/blob/master/2015/11/pydata-nyc/optimize-your-docker-infrastructure-with-python.ipynb">💻</a> <a href="https://www.youtube.com/watch?v=l6NjecDJFb8">🎥</a></td>
          <td style="text-align: left">PyData NYC 2015</td>
          <td style="text-align: left">Optimize your Docker Infrastructure with Python</td>
      </tr>
      <tr>
          <td style="text-align: left">2014</td>
          <td style="text-align: left">Jul 17</td>
          <td style="text-align: left"><a href="https://www.euro-online.org/conferences/program/#abstract/13132">📄</a> <a href="/slides/2014-ifors.pdf">💻</a></td>
          <td style="text-align: left">IFORS 2014</td>
          <td style="text-align: left">A MIP-Based Dual Bounding Technique for the Irregular Nesting Problem</td>
      </tr>
      <tr>
          <td style="text-align: left">2010</td>
          <td style="text-align: left">Feb 19</td>
          <td style="text-align: left"><a href="https://pyvideo.org/pycon-us-2010/pycon-2010--optimal-resource-allocation-using-pyt.html">🎥</a></td>
          <td style="text-align: left"><a href="https://pyvideo.org/events/pycon-us-2010.html">PyCon 2010</a></td>
          <td style="text-align: left">Optimal Resource Allocation using Python</td>
      </tr>
  </tbody>
</table>
<h2 id="articles-papers--patents">Articles, papers &amp; patents</h2>
<p>I&rsquo;m an desultory blogger and intermittent academic. Most of my current and old posts live <a href="/posts">here</a>. Some of my other content is below.</p>
<table>
  <thead>
      <tr>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
          <th style="text-align: left"></th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td style="text-align: left">2024</td>
          <td style="text-align: left">Dec 4</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://pubsonline.informs.org/magazine/orms-today">ORMS Today</a></td>
          <td style="text-align: left"><a href="https://pubsonline.informs.org/do/10.1287/orms.2024.04.05/full/">Ops Researchers, It’s Time to Git with the Flow</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Mar 26</td>
          <td style="text-align: left"><a href="https://patentimages.storage.googleapis.com/fc/4e/f5/e0117c9cd03a98/US11940286.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://www.uspto.gov/">USPTO</a></td>
          <td style="text-align: left"><a href="https://patents.google.com/patent/US11940286B1/en">Fast computational generation of digital pickup and delivery plans</a> describes algorithms for fast on-demand routing in pickup and delivery problems.</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Oct 31</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/5-things-software-teams-should-know-about-operations-research-and-decision-science">5 things software teams should know about operations research and decision science</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Oct 17</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/new-integration-bring-your-hexaly-decision-model-to-nextmv">New integration: Bring your Hexaly decision model to Nextmv</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Mar 7</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/nextmv-gurobi-integration-build-test-deploy-decision-models-using-gurobi-and-decisionops">Nextmv Gurobi integration: Build, test, deploy decision models using Gurobi and DecisionOps</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Feb 13</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/ci-cd-for-decision-science-what-is-it-how-does-it-work-and-why-does-it-matter">CI/CD for decision science: What is it, how does it work, and why does it matter?</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Feb 1</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/new-decision-apps-an-open-source-decision-model-hub-and-an-individual-plan">New decision apps, an open source decision model hub, and an individual plan</a></td>
      </tr>
      <tr>
          <td style="text-align: left">2023</td>
          <td style="text-align: left">Dec 26</td>
          <td style="text-align: left"><a href="https://patentimages.storage.googleapis.com/f8/2b/3d/dafc1159aafaab/US11853909B1.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://www.uspto.gov/">USPTO</a></td>
          <td style="text-align: left"><a href="https://patents.google.com/patent/US11853909B1/en">Prediction of travel time and determination of prediction interval</a> describes technology for predicting travel times for on-demand delivery platforms.</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Dec 19</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/shift-scheduling-optimization-generating-shift-types-planning-for-demand-and-assigning-workers">Shift scheduling optimization: Generating shift types, planning for demand, and assigning workers</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Jun 13</td>
          <td style="text-align: left"><a href="https://patentimages.storage.googleapis.com/c6/08/09/060978f00373e0/US11675688.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://www.uspto.gov/">USPTO</a></td>
          <td style="text-align: left"><a href="https://patents.google.com/patent/US11675688B2/en">Runners for optimization solvers and simulators</a> describes technology for creating and executing Decision Diagram-based optimization solvers and state-based simulators in cloud environments.</td>
      </tr>
      <tr>
          <td style="text-align: left">2022</td>
          <td style="text-align: left">Apr 20</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/you-need-a-solver-what-is-a-solver">You need a solver. What is a solver?</a></td>
      </tr>
      <tr>
          <td style="text-align: left">2021</td>
          <td style="text-align: left">Mar 2</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/binaries-are-beautiful">Binaries are beautiful</a></td>
      </tr>
      <tr>
          <td style="text-align: left">2020</td>
          <td style="text-align: left">Sep 11</td>
          <td style="text-align: left"><a href="https://digitalcommons.bucknell.edu/cgi/viewcontent.cgi?article=2883&amp;context=fac_journ">🖨️</a></td>
          <td style="text-align: left"><a href="https://www.springer.com/journal/43069">Operations Research Forum</a></td>
          <td style="text-align: left"><a href="https://link.springer.com/article/10.1007/s43069-020-00024-1">MIPLIBing: Seamless Benchmarking of Mathematical Optimization Problems and Metadata Extensions</a> presents a Python library that automatically downloads queried subsets from the current versions of MIPLIB, MINLPLib, and QPLIB, provides a centralized local cache across projects, and tracks the best solution values and bounds on record for each problem.</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Mar 2</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog">Nextmv Blog</a></td>
          <td style="text-align: left"><a href="https://www.nextmv.io/blog/how-hop-hops">How Hop Hops</a></td>
      </tr>
      <tr>
          <td style="text-align: left">2019</td>
          <td style="text-align: left">May</td>
          <td style="text-align: left"><a href="http://www.optimization-online.org/DB_FILE/2018/10/6853.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://www.sciencedirect.com/journal/operations-research-letters">Operations Research Letters</a></td>
          <td style="text-align: left"><a href="https://www.sciencedirect.com/science/article/abs/pii/S0167637718305364">Decision diagrams for solving traveling salesman problems with pickup and delivery in real time</a> explores the use of Multivalued Decision Diagrams and Assignment Problem inference duals for real-time optimization of TSPPDs.</td>
      </tr>
      <tr>
          <td style="text-align: left">2018</td>
          <td style="text-align: left">Oct 2</td>
          <td style="text-align: left"><a href="http://www.optimization-online.org/DB_FILE/2018/10/6838.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://optimization-online.org/author/roneil1/">Optimization Online</a></td>
          <td style="text-align: left"><a href="https://optimization-online.org/2018/10/6838/">Integer Models for the Asymmetric Traveling Salesman Problem with Pickup and Delivery</a> proposes a new ATSPPD model, new valid inequalities for the Sarin-Sherali-Bhootra ATSPPD, and studies the impact of relaxing complicating constraints in these.</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Sep 13</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://bytes.grubhub.com/">Grubhub Bytes</a></td>
          <td style="text-align: left"><a href="https://bytes.grubhub.com/decisions-are-first-class-citizens-9994fd8fe802">Decisions are first class citizens: an introduction to Decision Engineering</a></td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Sep 2</td>
          <td style="text-align: left"><a href="http://www.optimization-online.org/DB_FILE/2017/12/6370.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://optimization-online.org/author/roneil1/">Optimization Online</a></td>
          <td style="text-align: left"><a href="http://www.optimization-online.org/DB_HTML/2017/12/6370.html">Exact Methods for Solving Traveling Salesman Problems with Pickup and Delivery in Real Time</a> examines exact methods for solving TSPPDs with consolidation in real-time applications. It considers enumerative, Mixed Integer Programming, Constraint Programming, and hybrid optimization approaches under various time budgets.</td>
      </tr>
      <tr>
          <td style="text-align: left"></td>
          <td style="text-align: left">Apr 10</td>
          <td style="text-align: left"><a href="https://optimization-online.org/wp-content/uploads/2018/04/6571.pdf">🖨️</a></td>
          <td style="text-align: left"><a href="https://optimization-online.org/author/roneil1/">Optimization Online</a></td>
          <td style="text-align: left"><a href="https://optimization-online.org/2018/04/6571/">The Meal Delivery Routing Problem</a> introduces the MDRP to formalize and study an important emerging class of dynamic delivery operations. It also develops optimization-based algorithms tailored to solve the courier assignment (dynamic vehicle routing) and capacity management (offline shift scheduling) problems encountered in meal delivery operations.</td>
      </tr>
      <tr>
          <td style="text-align: left">2015</td>
          <td style="text-align: left">Jan 5</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://web.archive.org/web/20161107120859/http://blog.yhat.com/">The Yhat Blog</a></td>
          <td style="text-align: left"><a href="https://web.archive.org/web/20160909032804/http://blog.yhat.com/posts/currency-portfolio-optimization-using-scienceops.html">Currency Portfolio Optimization Using ScienceOps</a></td>
      </tr>
      <tr>
          <td style="text-align: left">2014</td>
          <td style="text-align: left">Nov 10</td>
          <td style="text-align: left"></td>
          <td style="text-align: left"><a href="https://web.archive.org/web/20161107120859/http://blog.yhat.com/">The Yhat Blog</a></td>
          <td style="text-align: left"><a href="https://web.archive.org/web/20160308065802/http://blog.yhat.com/posts/how-yhat-does-cloud-balancing.html">How Yhat Does Cloud Balancing: A Case Study</a></td>
      </tr>
  </tbody>
</table>
<h2 id="software">Software</h2>
<p>Most of my work is proprietary, but some of it is open. Here are a few projects I&rsquo;ve built or made significant contributions. I&rsquo;ve also done some work on projects such as <a href="https://coin-or.github.io/pulp/">PuLP</a>, <a href="https://github.com/thserra/MIPLIBing">MIPLIBing</a>, and <a href="https://github.com/grubhub/mdrplib">MDRPlib</a>.</p>
<h3 id="activeish-projects">Active(ish) projects</h3>
<ul>
<li>The <a href="https://github.com/ryanjoneil/rams">Ruby Algebraic Modeling System</a> is a simple modeling tool for formulating and solving MILPs in Ruby.</li>
<li><a href="https://github.com/ryanjoneil/ap.cpp">ap.cpp</a> is an incremental primal-dual assignment problem solver written in C++. It can vastly improve propagation in hybrid optimization models that use AP relaxations. I use it within custom propagators in <a href="https://www.gecode.org/">Gecode</a> and in Decision Diagrams for solving the Traveling Salesman Problem with side constraints.</li>
<li><a href="https://github.com/ryanjoneil/ap">ap</a> is a Go version of ap.cpp.</li>
<li><a href="https://github.com/ryanjoneil/tsppd-hybrid">TSPPD Hybrid Optimization Code</a> and <a href="https://github.com/ryanjoneil/tsppd-dd">TSPPD Decision Diagram Code</a> are both used in my dissertation. The former contains C++14 code for hybrid CP and MIP models for solving TSPPDs. The latter uses a hybridized Decision Diagram implementation with an Assignment Problem inference dual inside a branch-and-bound.</li>
<li><a href="https://github.com/grubhub/tsppdlib">TSPPDlib</a> is a standard test set for TSPPDs. The instances are based on observed meal delivery data at Grubhub.</li>
</ul>
<h3 id="defunct-projects">Defunct projects</h3>
<ul>
<li><a href="https://pythonhosted.org/python-zibopt/">python-zibopt</a> was a Python interface to the <a href="https://www.scipopt.org/">SCIP Optimization Suite</a>. This was no longer necessary once <a href="https://github.com/scipopt/PySCIPOpt">PySCIPOpt</a> emerged.</li>
<li><a href="https://github.com/ryanjoneil/chute">Chute</a> was a simple, lightweight tool for running discrete event simulations in Python.</li>
<li><a href="https://code.google.com/archive/p/pygep/">PyGEP</a> was a simple library suitable for academic study of GEP (Gene Expression Programming) in Python 2.</li>
</ul>
<h2 id="et-al">Et al</h2>
<p>In my spare time, I&rsquo;m a cat and early music enthusiast, plus&hellip;</p>
<ul>
<li>a board member of <a href="https://www.classicaluprising.org/">Classical Uprising</a>,</li>
<li>a mentor of startup founders at the <a href="https://roux.northeastern.edu/entrepreneurship/">Roux Institute</a>,</li>
<li>chair of the <a href="https://www.informs.org/About-INFORMS/Governance/Committees/Member-Services-Group/Membership-Committee">INFORMS Membership Committee</a>,</li>
<li>and a cellist in the <a href="https://usm.maine.edu/osher-school-music/performance-opportunities/">Southern Maine Symphony Orchestra</a>.</li>
</ul>
<h2 id="iconography">Iconography</h2>
<ul>
<li>📄 = abstract</li>
<li>🧑‍💻️ = code</li>
<li>🖨️ = pdf</li>
<li>🎟 = registration</li>
<li>💻 = slides</li>
<li>🎥 = video</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>🧺 ICS 2025 Solvers Cluster Takeaways</title>
      <link>https://ryanjoneil.dev/posts/2025-03-18-ics-2025-solvers-cluster-takeaways/</link>
      <pubDate>Tue, 18 Mar 2025 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2025-03-18-ics-2025-solvers-cluster-takeaways/</guid>
      <description>Hot takes from the solvers cluster at the INFORMS Computing Society Conference 2025.</description>
      <content:encoded><![CDATA[<p>I just returned from the <a href="https://sites.google.com/view/ics-2025/home">2025 INFORMS Computing Society conference</a>, where I had the privilege of organizing a cluster on optimization solvers. The cluster had two sessions, <a href="https://symposia.gerad.ca/ICS2025/en/schedule?slot_id=2418">Solvers I</a> and <a href="https://symposia.gerad.ca/ICS2025/en/schedule?slot_id=2419">Solvers II</a>, and focussed on new developments in the implementation of optimization solvers.</p>
<p>In the coming days, I&rsquo;m going to explore some of these solvers in more depth. For now, I wanted to give a few hot takes from the sessions while they are still fresh in my mind.</p>
<h2 id="hybrid-optimization-is-everywhere">Hybrid optimization is everywhere</h2>
<p>Hybrid optimization combines multiple techniques to solve a given problem. Most of the hybrid optimization literature focuses on leveraging strengths of different techniques to solve a particular well-defined problem, such as a routing problem with time windows, but it can also provide clear benefits to general-purpose solvers.</p>
<p><a href="https://developers.google.com/optimization">OR-Tools</a>, which is likely the most commonly used open source solver, gave a talk on the design of their <a href="https://developers.google.com/optimization/cp">CP-SAT[-LP]</a> algorithm. To users interacting with OR-Tools through its APIs, CP-SAT looks like an ordinary constraint programming (CP) solver. Internally, however, they boost its CP solver with techniques from satisfiability (SAT) and linear programming (LP). This gives a whole that is much more powerful that its parts, as shown below.</p>
<p><img alt="The CP-SAT[-LP] solver architecture" loading="lazy" src="/files/2025-03-18-ics-2025-solvers-cluster-takeaways/cp-sat-lp.png#center"></p>
<p>☝ <em>Please pardon the poor quality of this photo I took during their ICS talk.</em></p>
<p>Meanwhile, the commercial optimizer Hexaly incorporates <a href="https://www.hexaly.com/event/2025-informs-computing-society-conference">a basketful of technologies</a> under the hood. These include techniques from from exact methods, heuristics like large neighborhood search, and even some ideas from Decision Diagrams (DD).</p>
<p><img alt="Hexaly Optimizer" loading="lazy" src="/files/2025-03-18-ics-2025-solvers-cluster-takeaways/hexaly.jpg#center"></p>
<p>Interestingly, both solvers admit to some component algorithms being well behind leading implementations. OR-Tools&rsquo;s SAT and LP solvers are somewhat rudimentary, and Hexaly&rsquo;s simplex and interior point algorithms would not be competitive on their own. It is the combination of multiple algorithms and approaches that makes the solvers powerful.</p>
<h2 id="state-based-modeling-has-a-big-opportunity">State-based modeling has a big opportunity</h2>
<p>Mixed Integer Programming (MIP) (and other math programming classes), CP, and Dynamic Programming (DP) have all been standard techniques in the optimization toolkit for decades. While MIP and CP both benefit from standard formats and solver interoperability through systems like <a href="https://www.minizinc.org/">MiniZinc</a>, <a href="https://ampl.com/">AMPL</a>, and other projects, that never really happened for DP. Even now, DP models are usually bespoke and lack both modeling standards and standard solvers.</p>
<p>That is rapidly changing with the development of both <a href="https://didp.ai/">Domain-Independent Dynamic Programming</a> (DIDP), and new DD solvers like <a href="https://github.com/ldmbouge/CODD">CODD</a>. These efforts are still nascent, but there is growing momentum toward building both domain-independent solvers and modeling languages for state-based models. If this succeeds, DP and state-based models have the potential to become similar to MIP and CP in power, portability, and expressiveness.</p>
<h2 id="established-technologies-are-rapidly-innovating-too">Established technologies are rapidly innovating, too</h2>
<p>Other talks in the cluster showed <a href="http://www.maxicp.org/">MaxiCP</a>, a CP solver with roots in <a href="http://www.maxicp.org/">MiniCP</a> that is suitable for real-life use, recent developments in proving global optimality for Mixed Integer Non-Linear Programs (MINLP) in <a href="https://en.wikipedia.org/wiki/FICO_Xpress">Xpress</a>, and an interesting new heuristic solver based on a technique called <a href="https://github.com/RKO-solver/Framework">Random-Key Optimization</a> (RKO) which represents solutions as vectors between 0 and 1 and changes the modeling exercise into solution decoding.</p>
<p>During an interview several years ago, an optimization team leader at a major logistics company who told me that &ldquo;optimization is a solved problem&rdquo; and that new solver development was therefore not interesting. That isn&rsquo;t what I see, though. Instead, I see the practical application of optimization continuing to grow beyond the boundaries of what today&rsquo;s solvers can handle, and a ton of activity in development of those solvers to make them ever more powerful and flexible.</p>
]]></content:encoded>
    </item>
    <item>
      <title>👔 Hierarchical Optimization with HiGHS</title>
      <link>https://ryanjoneil.dev/posts/2024-11-11-hierarchical-optimization-with-highs/</link>
      <pubDate>Mon, 11 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2024-11-11-hierarchical-optimization-with-highs/</guid>
      <description>Managing trade-offs between different objectives with HiGHS.</description>
      <content:encoded><![CDATA[<p>In the <a href="../2024-11-08-hierarchical-optimization-with-gurobi/">last post</a>, we used Gurobi&rsquo;s hierarchical optimization features to compute the Pareto front for primary and secondary objectives in an assignment problem. This relied on Gurobi&rsquo;s <code>setObjectiveN</code> method and its internal code for managing hierarchical problems.</p>
<p>Some practitioners may need to do this without access to a commercial license. This post adapts the previous example to use HiGHS and its native Python interface, <a href="https://pypi.org/project/highspy/"><code>highspy</code></a>. It&rsquo;s also useful to see what the procedure is in order to understand it better. This isn&rsquo;t exactly what I&rsquo;d call <em>hard</em>, but it is <em>easy to mess up</em>.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h2 id="code">Code</h2>
<p>The mathematical models are available in the last post, so I won&rsquo;t restate them here. We start in roughly the same manner as before<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup>: create a binary variable for each worker-patient pair, add assignment problem constraints, and state the primary objective.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">itertools</span> <span style="color:#ff7b72">import</span> product
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">highspy</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>n <span style="color:#ff7b72;font-weight:bold">=</span> len(data[<span style="color:#a5d6ff">&#34;cost&#34;</span>])
</span></span><span style="display:flex;"><span>workers <span style="color:#ff7b72;font-weight:bold">=</span> range(n)
</span></span><span style="display:flex;"><span>patients <span style="color:#ff7b72;font-weight:bold">=</span> range(n)
</span></span><span style="display:flex;"><span>workers_patients <span style="color:#ff7b72;font-weight:bold">=</span> list(product(workers, patients))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>h <span style="color:#ff7b72;font-weight:bold">=</span> highspy<span style="color:#ff7b72;font-weight:bold">.</span>Highs()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># x[w,p] = 1 if worker w is assigned to patient p.</span>
</span></span><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> {(w, p): h<span style="color:#ff7b72;font-weight:bold">.</span>addBinary(obj<span style="color:#ff7b72;font-weight:bold">=</span>data[<span style="color:#a5d6ff">&#34;cost&#34;</span>][w][p]) <span style="color:#ff7b72">for</span> w, p <span style="color:#ff7b72;font-weight:bold">in</span> workers_patients}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Each worker is assigned to one patient.</span>
</span></span><span style="display:flex;"><span>h<span style="color:#ff7b72;font-weight:bold">.</span>addConstrs(sum(x[w, p] <span style="color:#ff7b72">for</span> p <span style="color:#ff7b72;font-weight:bold">in</span> patients) <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span> <span style="color:#ff7b72">for</span> w <span style="color:#ff7b72;font-weight:bold">in</span> workers)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Each patient is assigned one worker.</span>
</span></span><span style="display:flex;"><span>h<span style="color:#ff7b72;font-weight:bold">.</span>addConstrs(sum(x[w, p] <span style="color:#ff7b72">for</span> w <span style="color:#ff7b72;font-weight:bold">in</span> workers) <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span> <span style="color:#ff7b72">for</span> p <span style="color:#ff7b72;font-weight:bold">in</span> patients)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Primary objective: minimize cost.</span>
</span></span><span style="display:flex;"><span>h<span style="color:#ff7b72;font-weight:bold">.</span>setMinimize()
</span></span><span style="display:flex;"><span>h<span style="color:#ff7b72;font-weight:bold">.</span>solve()
</span></span><span style="display:flex;"><span>cost <span style="color:#ff7b72;font-weight:bold">=</span> h<span style="color:#ff7b72;font-weight:bold">.</span>getObjectiveValue()
</span></span></code></pre></div><p>Note that if the costs and affinities were lists instead of matrices, we could have used <code>h.addBinaries</code> instead of <code>h.addBinary</code>.</p>
<p>From here we&rsquo;ll be solving the model twice for every value of alpha. These expressions for total cost and affinity will make a code a little cleaner.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>cost_expr <span style="color:#ff7b72;font-weight:bold">=</span> sum(data[<span style="color:#a5d6ff">&#34;cost&#34;</span>][w][p] <span style="color:#ff7b72;font-weight:bold">*</span> x[w, p] <span style="color:#ff7b72">for</span> w, p <span style="color:#ff7b72;font-weight:bold">in</span> workers_patients)
</span></span><span style="display:flex;"><span>affinity_expr <span style="color:#ff7b72;font-weight:bold">=</span> sum(data[<span style="color:#a5d6ff">&#34;affinity&#34;</span>][w][p] <span style="color:#ff7b72;font-weight:bold">*</span> x[w, p] <span style="color:#ff7b72">for</span> w, p <span style="color:#ff7b72;font-weight:bold">in</span> workers_patients)
</span></span></code></pre></div><p>Now comes the hierarchical optimization logic. For every value of alpha, we find the best affinity possible while keeping cost within alpha of its best possible value.</p>
<ul>
<li>Update the objective function to maximize affinity (see the calls to <code>h.changeColCost</code> and <code>h.setMaximize</code>).</li>
<li>Constrain the cost to be within alpha of the original optimal cost (see <code>cost_cons</code>).</li>
<li>Re-optimize and save the maximal affinity.</li>
</ul>
<p>Now we constrain the affinity and re-optimize cost.<sup id="fnref:3"><a href="#fn:3" class="footnote-ref" role="doc-noteref">3</a></sup></p>
<ul>
<li>Update the objective function to minimize cost again.</li>
<li>Constrain the affinity.</li>
</ul>
<p>Once that&rsquo;s done, we remove the additional constraints and repeat for a new value of alpha.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">for</span> alpha <span style="color:#ff7b72;font-weight:bold">in</span> alphas:
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Secondary objective: maximize affinity.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (w, p), x_wp <span style="color:#ff7b72;font-weight:bold">in</span> x<span style="color:#ff7b72;font-weight:bold">.</span>items():
</span></span><span style="display:flex;"><span>        h<span style="color:#ff7b72;font-weight:bold">.</span>changeColCost(x_wp<span style="color:#ff7b72;font-weight:bold">.</span>index, data[<span style="color:#a5d6ff">&#34;affinity&#34;</span>][w][p])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Constrain cost to be within alpha of maximum.</span>
</span></span><span style="display:flex;"><span>    cost_cons <span style="color:#ff7b72;font-weight:bold">=</span> h<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(cost_expr <span style="color:#ff7b72;font-weight:bold">&lt;=</span> (<span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">+</span> alpha) <span style="color:#ff7b72;font-weight:bold">*</span> cost)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>setMaximize()
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>solve()
</span></span><span style="display:flex;"><span>    affinity <span style="color:#ff7b72;font-weight:bold">=</span> h<span style="color:#ff7b72;font-weight:bold">.</span>getObjectiveValue()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Re-optimize with original cost objective, constraining affinity.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (w, p), x_wp <span style="color:#ff7b72;font-weight:bold">in</span> x<span style="color:#ff7b72;font-weight:bold">.</span>items():
</span></span><span style="display:flex;"><span>        h<span style="color:#ff7b72;font-weight:bold">.</span>changeColCost(x_wp<span style="color:#ff7b72;font-weight:bold">.</span>index, data[<span style="color:#a5d6ff">&#34;cost&#34;</span>][w][p])
</span></span><span style="display:flex;"><span>    affinity_cons <span style="color:#ff7b72;font-weight:bold">=</span> h<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(affinity_expr <span style="color:#ff7b72;font-weight:bold">&gt;=</span> affinity)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>setMinimize()
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>solve()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">yield</span> alpha, h<span style="color:#ff7b72;font-weight:bold">.</span>getObjectiveValue(), affinity
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Remove cost and affinity constraints for</span>
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>removeConstr(cost_cons)
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>removeConstr(affinity_cons)
</span></span></code></pre></div><p>Encouragingly, running this using the <code>model.py</code> linked below gives the same values as the Gurobi model, albeit not as quickly. Floating point values are rounded for readability.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>| alpha | cost     | affinity |
</span></span><span style="display:flex;"><span>| ----- | -------- | -------- |
</span></span><span style="display:flex;"><span>| 0.0   | 11212.0  | 53816.0  |
</span></span><span style="display:flex;"><span>| 0.05  | 11761.0  | 74001.0  |
</span></span><span style="display:flex;"><span>| 0.1   | 12332.0  | 79981.0  |
</span></span><span style="display:flex;"><span>| 0.15  | 12886.0  | 83103.0  |
</span></span><span style="display:flex;"><span>| 0.2   | 13454.0  | 85394.0  |
</span></span><span style="display:flex;"><span>| 0.25  | 13996.0  | 87136.0  |
</span></span><span style="display:flex;"><span>| 0.3   | 14557.0  | 88546.0  |
</span></span><span style="display:flex;"><span>| 0.35  | 15125.0  | 89751.0  |
</span></span><span style="display:flex;"><span>| 0.4   | 15670.0  | 90664.0  |
</span></span><span style="display:flex;"><span>| 0.45  | 16255.0  | 91345.0  |
</span></span><span style="display:flex;"><span>| 0.5   | 16816.0  | 91997.0  |
</span></span><span style="display:flex;"><span>| 0.55  | 17370.0  | 92537.0  |
</span></span><span style="display:flex;"><span>| 0.6   | 17924.0  | 93012.0  |
</span></span><span style="display:flex;"><span>| 0.65  | 18495.0  | 93491.0  |
</span></span><span style="display:flex;"><span>| 0.7   | 19055.0  | 93829.0  |
</span></span><span style="display:flex;"><span>| 0.75  | 19591.0  | 94228.0  |
</span></span><span style="display:flex;"><span>| 0.8   | 20167.0  | 94530.0  |
</span></span><span style="display:flex;"><span>| 0.85  | 20737.0  | 94833.0  |
</span></span><span style="display:flex;"><span>| 0.9   | 21295.0  | 95114.0  |
</span></span><span style="display:flex;"><span>| 0.95  | 21812.0  | 95361.0  |
</span></span><span style="display:flex;"><span>| 1.0   | 22402.0  | 95613.0  |
</span></span></code></pre></div><h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2024-11-11-hierarchical-optimization-with-highs/model.py"><code>model.py</code></a> hierarchical objectives HiGHS model</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>It gets even easier to mess up with more than two objectives.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>Isn&rsquo;t it nice that MIP modeling is similar across different APIs?&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:3">
<p>Exercise for the reader: why do we need to re-optimize cost?&#160;<a href="#fnref:3" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>👔 Hierarchical Optimization with Gurobi</title>
      <link>https://ryanjoneil.dev/posts/2024-11-08-hierarchical-optimization-with-gurobi/</link>
      <pubDate>Fri, 08 Nov 2024 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2024-11-08-hierarchical-optimization-with-gurobi/</guid>
      <description>Managing trade-offs between different objectives with Gurobi.</description>
      <content:encoded><![CDATA[<p>One of the first technology choices to make when setting up an optimization stack is which modeling interface to use. Even if we restrict our choices to Python interfaces for MIP modeling, there are lots of options to consider.</p>
<p>If you use a specific solver, you can opt for its native Python interface. Examples include libraries like <a href="https://pypi.org/project/gurobipy/"><code>gurobipy</code></a>, <a href="https://docs.mosek.com/latest/pythonfusion/index.html">Fusion</a>, <a href="https://pypi.org/project/highspy/"><code>highspy</code></a>, or <a href="https://github.com/scipopt/PySCIPOpt"><code>PySCIPOpt</code></a>. This approach provides access to important solver-specific features such as lazy constraints, heuristics, and various solver settings. However, it can also lock you into a solver before ready for that.</p>
<p>You can also choose a modeling API that targets multiple solvers. In the Python ecosystem. These are libraries like <a href="https://amplpy.ampl.com/en/latest/"><code>amplpy</code></a>, <a href="http://www.pyomo.org/">Pyomo</a>, <a href="https://github.com/metab0t/PyOptInterface">PyOptInterface</a>, and <a href="https://linopy.readthedocs.io/en/latest/"><code>linopy</code></a>. These interfaces target multiple solver backends (both open source and commercial) and provide a subset of the functionality of each. Since they make it easy to switch between solvers, this is usually where I start.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup></p>
<h2 id="hierarchical-assignment">Hierarchical assignment</h2>
<p>However, there are plenty of times when solver-specific APIs are useful, or even critical. One example is hierarchical optimization. This is a simple technique for managing trade-offs between multiple objectives in a problem. Let&rsquo;s look at an example.</p>
<p>Imagine we are assigning in-home health care workers ($w \in W$) to patients ($p \in P$). For simplicity, let&rsquo;s say we have $n$ workers and $n$ patients, and we are assigning them one-to-one. Each worker has a given cost ($c_{wp}$) of assignment to each patient, which may reflect something like the travel time to get to them. We want to assign each worker to exactly one patient while minimizing the overall cost.</p>
<h3 id="model">Model</h3>
<p>So far, what we have is a simple linear sum assignment problem.</p>
<p>$$
\begin{align*}
&amp; \text{min}  &amp;&amp; z = \sum_{wp} c_{wp} x_{wp} \\
&amp; \text{s.t.} &amp;&amp; \sum_w x_{wp} = 1 &amp;&amp; \forall \quad p \in P \\
&amp;             &amp;&amp; \sum_p x_{wp} = 1 &amp;&amp; \forall \quad w \in W \\
&amp;             &amp;&amp; x \in \{0,1\}^{|W \times P|}
\end{align*}
$$</p>
<p>Solving this model gives us the minimum cost assignment. That&rsquo;s all well and good, but now say we have a secondary objective of maximizing <em>affinity</em> of workers to patients ($a_{wp}$). That is, we want to <em>prefer assignments that increase overall affinity while still minimizing cost</em>. This is actually a common goal in health care scheduling: if possible, send the same worker to a given patient that you usually send.</p>
<p>Hierarchical optimization gives us a simple way to solve this problem. First, we optimize the model as stated above. This gives us an optimal objective value $z^*$. Then we re-solve the same optimization model, while constraining the cost to be $z^*$ and using the secondary objective function. This says to the optimizer, &ldquo;improve the affinity as much as you can, but keep the cost optimal.&rdquo;</p>
<p>$$
\begin{align*}
&amp; \text{max}  &amp;&amp; w = \sum_{wp} a_{wp} x_{wp} \\
&amp; \text{s.t.} &amp;&amp; \sum_{wp} c_{wp} x_{wp} \le z^* \\
&amp;             &amp;&amp; \sum_w x_{wp} = 1 &amp;&amp; \forall \quad p \in P \\
&amp;             &amp;&amp; \sum_p x_{wp} = 1 &amp;&amp; \forall \quad w \in W \\
&amp;             &amp;&amp; x \in \{0,1\}^{|W \times P|}
\end{align*}
$$</p>
<p>From here, the natural question becomes: what if we trade off some cost for affinity? If we&rsquo;re willing to increase cost by some percentage, how much more affinity do we get? We can do this by setting a constant $\alpha \ge 0$ and solving the model a number of times.<sup id="fnref:2"><a href="#fn:2" class="footnote-ref" role="doc-noteref">2</a></sup></p>
<p>$$
\begin{align*}
&amp; \text{max}   &amp;&amp; w = \sum_{wp} a_{wp} x_{wp} \\
&amp; \text{s.t.} &amp;&amp; \sum_{wp} c_{wp} x_{wp} \le (1 + \alpha) z^* \\
&amp;             &amp;&amp; \sum_w x_{wp} = 1 &amp;&amp; \forall \quad p \in P \\
&amp;             &amp;&amp; \sum_p x_{wp} = 1 &amp;&amp; \forall \quad w \in W \\
&amp;             &amp;&amp; x \in \{0,1\}^{|W \times P|}
\end{align*}
$$</p>
<p>For example, if $\alpha = 0.05$, then we&rsquo;re willing to accept a 5% increase in overall cost to improve affinity. Setting different values of $\alpha$ lets us explore the space of that trade-off and its impact on cost and affinity.</p>
<p>Once we solve this and get the optimal affinity ($w^*$), we should re-optimize for the primary objective again while constraining the secondary one.</p>
<p>$$
\begin{align*}
&amp; \text{min}  &amp;&amp; \sum_{wp} c_{wp} x_{wp} \\
&amp; \text{s.t.} &amp;&amp; \sum_{wp} a_{wp} x_{wp} \ge w^* \\
&amp;             &amp;&amp; \sum_w x_{wp} = 1 &amp;&amp; \forall \quad p \in P \\
&amp;             &amp;&amp; \sum_p x_{wp} = 1 &amp;&amp; \forall \quad w \in W \\
&amp;             &amp;&amp; x \in \{0,1\}^{|W \times P|}
\end{align*}
$$</p>
<h3 id="code">Code</h3>
<p>So the math looks reasonable. How do we implement it? If we have a Gurobi license, we can use <a href="https://docs.gurobi.com/projects/optimizer/en/current/features/multiobjective.html#multiple-objectives">its built-in facilities for multiobjective optimization</a>. This means that, instead solving a model multiple times and adding constraints to keep cost within $\alpha$ of its optimal value, we can create a single model that does all of this for us.</p>
<p>Assume we have input data which looks like this.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;cost&#34;</span>: [
</span></span><span style="display:flex;"><span>        [<span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">20</span>, <span style="color:#f85149">...</span>],
</span></span><span style="display:flex;"><span>        [<span style="color:#a5d6ff">30</span>, <span style="color:#a5d6ff">40</span>, <span style="color:#f85149">...</span>],
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">...</span>
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;affinity&#34;</span>: [
</span></span><span style="display:flex;"><span>        [<span style="color:#a5d6ff">25</span>, <span style="color:#a5d6ff">15</span>, <span style="color:#f85149">...</span>],
</span></span><span style="display:flex;"><span>        [<span style="color:#a5d6ff">35</span>, <span style="color:#a5d6ff">25</span>, <span style="color:#f85149">...</span>],
</span></span><span style="display:flex;"><span>        <span style="color:#f85149">...</span>
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We start with a simple assignment problem formulation.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">gurobipy</span> <span style="color:#ff7b72">as</span> <span style="color:#ff7b72">gp</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>n <span style="color:#ff7b72;font-weight:bold">=</span> len(data[<span style="color:#a5d6ff">&#34;cost&#34;</span>])
</span></span><span style="display:flex;"><span>workers <span style="color:#ff7b72;font-weight:bold">=</span> range(n)
</span></span><span style="display:flex;"><span>patients <span style="color:#ff7b72;font-weight:bold">=</span> range(n)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m <span style="color:#ff7b72;font-weight:bold">=</span> gp<span style="color:#ff7b72;font-weight:bold">.</span>Model()
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>ModelSense <span style="color:#ff7b72;font-weight:bold">=</span> gp<span style="color:#ff7b72;font-weight:bold">.</span>GRB<span style="color:#ff7b72;font-weight:bold">.</span>MINIMIZE
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># x[w,p] = 1 if worker w is assigned to patient p.</span>
</span></span><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVars(n, n, vtype<span style="color:#ff7b72;font-weight:bold">=</span>gp<span style="color:#ff7b72;font-weight:bold">.</span>GRB<span style="color:#ff7b72;font-weight:bold">.</span>BINARY)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(n):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Each worker is assigned to one patient.</span>
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(gp<span style="color:#ff7b72;font-weight:bold">.</span>quicksum(x[i, p] <span style="color:#ff7b72">for</span> p <span style="color:#ff7b72;font-weight:bold">in</span> patients) <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Each patient is assigned one worker.</span>
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(gp<span style="color:#ff7b72;font-weight:bold">.</span>quicksum(x[w, i] <span style="color:#ff7b72">for</span> w <span style="color:#ff7b72;font-weight:bold">in</span> workers) <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span>)
</span></span></code></pre></div><p>We add primary and secondary objectives, and call <code>optimize</code>. The objectives are solved in descending order of the <code>priority</code> flag for <code>Model.setObjectiveN</code>. <code>reltol</code> allows us to degrade the primary objective by some amount (e.g. 5%) to improve the secondary objective.</p>
<p>One catch is that the model only has one objective sense. Since we are <em>minimizing</em> the primary objective, we give the secondary objective a <code>weight</code> of <code>-1</code> in order to <em>maximize</em> it.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">itertools</span> <span style="color:#ff7b72">import</span> product
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Primary objective: minimize cost.</span>
</span></span><span style="display:flex;"><span>z <span style="color:#ff7b72;font-weight:bold">=</span> (data[<span style="color:#a5d6ff">&#34;cost&#34;</span>][w][p] <span style="color:#ff7b72;font-weight:bold">*</span> x[w, p] <span style="color:#ff7b72">for</span> w, p <span style="color:#ff7b72;font-weight:bold">in</span> product(workers, patients))
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>setObjectiveN(expr<span style="color:#ff7b72;font-weight:bold">=</span>gp<span style="color:#ff7b72;font-weight:bold">.</span>quicksum(z), index<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0</span>, name<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;cost&#34;</span>, priority<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>, reltol<span style="color:#ff7b72;font-weight:bold">=</span>alpha)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Secondary objective: maximize affinity. Since the model sense is minimize,</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># we negate the secondary objective in order to maximize it.</span>
</span></span><span style="display:flex;"><span>w <span style="color:#ff7b72;font-weight:bold">=</span> (data[<span style="color:#a5d6ff">&#34;affinity&#34;</span>][w][p] <span style="color:#ff7b72;font-weight:bold">*</span> x[w, p] <span style="color:#ff7b72">for</span> w, p <span style="color:#ff7b72;font-weight:bold">in</span> product(workers, patients))
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>setObjectiveN(
</span></span><span style="display:flex;"><span>    expr<span style="color:#ff7b72;font-weight:bold">=</span>gp<span style="color:#ff7b72;font-weight:bold">.</span>quicksum(w), index<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>, name<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;affinity&#34;</span>, priority<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0</span>, weight<span style="color:#ff7b72;font-weight:bold">=-</span><span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>optimize()
</span></span></code></pre></div><p>Then we use this magic syntax to pull out the optimal cost and affinity.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>params<span style="color:#ff7b72;font-weight:bold">.</span>ObjNumber <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span>cost <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>ObjNVal
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>params<span style="color:#ff7b72;font-weight:bold">.</span>ObjNumber <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>affinity <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>ObjNVal
</span></span></code></pre></div><h3 id="results">Results</h3>
<p>If we solve this in a loop with alpha values from <code>0</code> to <code>1</code> in increments of <code>0.05</code>, we can plot the trade-off between cost and affinity. Going from $\alpha = 0$ to $\alpha = 0.05$ or $\alpha = 0.1$ gives a pretty sizable improvement in affinity. After that, the return starts to gradually level off. This allows us to make a more informed choice about these two objectives.</p>
<p><img alt="Pareto front - cost vs affinity" loading="lazy" src="/files/2024-11-08-hierarchical-optimization-with-gurobi/plot.png#center"></p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2024-11-08-hierarchical-optimization-with-gurobi/generate.py"><code>generate.py</code></a> generates input data</li>
<li><a href="/files/2024-11-08-hierarchical-optimization-with-gurobi/input-100x100.json"><code>input-100x100.json</code></a> contains input data</li>
<li><a href="/files/2024-11-08-hierarchical-optimization-with-gurobi/model.py"><code>model.py</code></a> hierarchical objectives Gurobi model</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>While commercial libraries like AMPL have always focussed on modeling performance, some of the open source options targeting multiple solvers come with significant performance penalties during formulation and model handoff to the solver. Newer options like <code>linopy</code> (<a href="https://linopy.readthedocs.io/en/latest/benchmark.html">benchmarks</a>) and PyOptInterface (<a href="https://metab0t.github.io/PyOptInterface/benchmark.html">benchmarks</a>) don&rsquo;t have that issue.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
<li id="fn:2">
<p>This gives us a <a href="https://en.wikipedia.org/wiki/Pareto_front">Pareto front</a>, which explores the trade-offs between different objectives.&#160;<a href="#fnref:2" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>📅 Reducing Overscheduling</title>
      <link>https://ryanjoneil.dev/posts/2023-11-26-reducing-overscheduling/</link>
      <pubDate>Sun, 26 Nov 2023 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2023-11-26-reducing-overscheduling/</guid>
      <description>Minimize overbooking while scheduling team meetings</description>
      <content:encoded><![CDATA[<p>At a <a href="https://nextmv.io">Nextmv</a> <a href="https://www.youtube.com/watch?v=XTeit7TAWj4">tech talk</a> a couple weeks ago, I showed a <a href="https://en.wikipedia.org/wiki/Least_absolute_deviations">least absolute deviations</a> (LAD) regression model using OR-Tools. This isn&rsquo;t new &ndash; I pulled the formulation from Rob Vanderbei&rsquo;s &ldquo;<a href="https://vanderbei.princeton.edu/tex/LocalWarming/LocalWarmingSIREVrev.pdf">Local Warming</a>&rdquo; paper, and I&rsquo;ve shown similar models at conference talks in the past using other modeling APIs and solvers.</p>
<p>There are a couple reasons I keep coming back to this problem. One is that it&rsquo;s a great example of how to build a machine learning model using an optimization solver. Unless you have an optimization background, it&rsquo;s probably not obvious you can do this. Building a regression or classification model with a solver directly is a great way to understand the model better. And you can customize it in interesting ways, like adding <a href="https://www.robots.ox.ac.uk/~az/lectures/ml/2011/lect6.pdf">epsilon insensitivity</a>.</p>
<p>Another is that <a href="https://en.wikipedia.org/wiki/Least_squares">least squares</a>, while most commonly used regression form, has a fatal flaw: it isn&rsquo;t robust to outliers in the input data. This is because least squares minimize the <em>sum of squared residuals</em>, as shown in the formulation below. Here, $A$ is an $m \times n$ matrix of feature data, $b$ is a vector of observations to fit, and $x$ is a vector of coefficients the optimizer must find.</p>
<p>$$
\min f(x) = \Vert Ax-b \Vert^2
$$</p>
<p>Since the objective function minimizes squared residuals, outliers have a much bigger impact than other data. LAD regression solves this by simply summing the values of the residuals as they are.</p>
<p>$$
\min f(x) = \vert Ax-b \vert
$$</p>
<p>So why isn&rsquo;t this used more? Simple &ndash; least squares has a convenient analytical solution, while LAD requires an algorithm to solve. For instance, you can formulate LAD regression as a linear program, but now you need a solver.</p>
<p>$$
\begin{align*}
\min         \quad &amp; 1&rsquo;z \\
\text{s.t.}\ \quad &amp; z \ge Ax - b \\
&amp; z \ge b - Ax
\end{align*}
$$</p>
<p>While I like using this example, it paints a rather negative picture of squaring. If it does funny things to solvers, is there any good reason to square? Thus I&rsquo;ve been on the lookout for a practical example where squaring a variable or expression makes a model more useful.</p>
<p>Luckily for me, Erwin Kalvelagen recently <a href="https://yetanothermathprogrammingconsultant.blogspot.com/2023/10/scheduling-team-meetings.html">posted</a> about using optimization to schedule team meetings. This is an application where minimizing squared values of <em>overbooking</em> can be beneficial &ndash; it may be worse to be triple booked than double booked.</p>
<p>I won&rsquo;t recreate the reasoning behind Erwin&rsquo;s post here. You can read his blog for that. What we&rsquo;ll do is look at both the formulations in his post, along with a couple extras using <a href="https://julialang.org/">Julia</a> for code, <a href="https://jump.dev/">JuMP</a> for modeling, <a href="https://www.scipopt.org/">SCIP</a> for optimization, and <a href="https://gadflyjl.org/stable/">Gadfly</a> for visualization. All model code and data are linked in the resources section at the end.</p>
<h2 id="maximize-attendance">Maximize attendance</h2>
<p>To start off, I built a new data set, which you can find in the resources section. This differentiates team membership between two types of employees: individual contributors (starting with <code>ic</code> in the data), who attend meetings for 1 or 2 teams, and managers (prefixed with <code>mgr</code>), who attend meetings to coordinate across multiple teams. We schedule meetings for 10 teams (prefix <code>t</code>) into 3 time slots (<code>s</code>).</p>
<p>The first model in Erwin&rsquo;s post maximizes attendance. This means it tries to schedule team members for as many unique time slots as possible. It doesn&rsquo;t consider overbooking.</p>
<p>$$
\begin{align*}
\max\quad       &amp; \sum_{i,s} y_{i,s} \\
\text{s.t.}\quad&amp; \sum_{s} x_{t,s} = 1                  &amp;\quad\forall&amp;\ t   &amp; \text{schedule each team meeting once}\\
&amp; y_{i,s} \le \sum_{t} m_{i,t}\ x_{t,s} &amp;\quad\forall&amp;\ i,s &amp; \text{individuals attend team meetings}\\
&amp; x_{t,s} \in \{0,1\}                 &amp;\quad\forall&amp;\ t,s\\
&amp; y_{i,s} \in \{0,1\}                 &amp;\quad\forall&amp;\ i,s
\end{align*}
$$</p>
<p>This yields the following team schedule, with red representing a scheduled team meeting.</p>
<p><img alt="Maximize attendance - team schedules" loading="lazy" src="/files/2023-11-26-reducing-overscheduling/maximize-attendance-teams.svg#center"></p>
<p>If we look at the manager schedules, we&rsquo;ll see that every manager is completely booked. This makes sense. That&rsquo;s what managers do, right? Go to meetings?</p>
<p><img alt="Maximize attendance - manager attendance" loading="lazy" src="/files/2023-11-26-reducing-overscheduling/maximize-attendance-managers.svg#center"></p>
<h2 id="minimize-overbooking">Minimize overbooking</h2>
<p>The model gets more interesting once we account for overbooking. Erwin&rsquo;s post has a model that minimizes overbooking, where overbooking is the number of additional meetings in a time slot. If a team member is double booked, that&rsquo;s 1 overbooking. If they are triple booked, that&rsquo;s 2 overbookings.</p>
<h3 id="sum-of-overbooking">Sum of overbooking</h3>
<p>The second model in Erwin&rsquo;s post minimizes the sum of all overbookings. He does this by adding a continuous <code>c</code> vector that only incurs value once a team member goes over a single meeting in a given time slot.</p>
<p>$$
\begin{align*}
\min\quad       &amp; \sum_{i,s} c_{i,s} \\
\text{s.t.}\quad&amp; \sum_{s} x_{t,s} = 1                      &amp;\quad\forall&amp;\ t   &amp; \text{schedule each team meeting once}\\
&amp; c_{i,s} \ge \sum_{t} m_{i,t}\ x_{t,s} - 1 &amp;\quad\forall&amp;\ i,s &amp; \text{measure overbooking}\\
&amp; x_{t,s} \in \{0,1\}                     &amp;\quad\forall&amp;\ t,s\\
&amp; c_{i,s} \ge 0                             &amp;\quad\forall&amp;\ i,s
\end{align*}
$$</p>
<p>Given our data this results in the following team schedule, which is probably not all that interesting. I&rsquo;ll leave this visualization out from now on.</p>
<p><img alt="Minimize overbooking - team schedules" loading="lazy" src="/files/2023-11-26-reducing-overscheduling/minimize-overbooking-teams.svg#center"></p>
<p>Where it gets interesting is plotting overbookings for the managers. Here we see that 3 manager time slots are triple booked <em>(red)</em>, while 8 are double booked <em>(gray)</em>.</p>
<p><img alt="Minimize overbooking - manager overbooking" loading="lazy" src="/files/2023-11-26-reducing-overscheduling/minimize-overbooking-managers.svg#center"></p>
<h3 id="sum-of-squared-overbooking">Sum of squared overbooking</h3>
<p>Let&rsquo;s say it&rsquo;s worse to triple book (or, gasp, <em>quadruple</em> book) than to double book. How can the model account for this? One answer, if you have a MIQP-enabled solver, is to simply square the <code>c</code> values.</p>
<p>$$
\begin{align*}
\min\quad       &amp; \sum_{i,s} c_{i,s}^2 \\
\text{s.t.}\quad&amp; \sum_{s} x_{t,s} = 1                      &amp;\quad\forall&amp;\ t   &amp; \text{schedule each team meeting once}\\
&amp; c_{i,s} \ge \sum_{t} m_{i,t}\ x_{t,s} - 1 &amp;\quad\forall&amp;\ i,s &amp; \text{measure overbooking}\\
&amp; x_{t,s} \in \{0,1\}                     &amp;\quad\forall&amp;\ t,s\\
&amp; c_{i,s} \ge 0                             &amp;\quad\forall&amp;\ i,s
\end{align*}
$$</p>
<p>This completely eliminates triple booking, as shown below. No manager is worse off than being double booked, which seems normal given my experiences.</p>
<p><img alt="Minimize squared overbooking - manager overbooking" loading="lazy" src="/files/2023-11-26-reducing-overscheduling/minimize-overbooking-squared-managers.svg"></p>
<p>The problem with this is that the solver now takes a lot longer. It&rsquo;s not bad for the data in this example, but if you try it with something larger you&rsquo;ll see what I mean. You can find the data generator code in the resources section.</p>
<h3 id="constrained-bottleneck">Constrained bottleneck</h3>
<p>So how can we do something similar without the computational cost? One option is to continue using MILP formulations, but in the context of hierarchical optimization. This means splitting the model into two. First, we try to minimize the maximum overbookings for any team member (the <em>bottleneck</em>, if you will). This involves adding a variable $b$ representing that maximum.</p>
<p>$$ b = \max\Bigl\{\sum_{t} m_{i,t}\ x_{t,s} - 1 : i \in I, s \in S \Bigr\} $$</p>
<p>Now we can simply minimize $b$ using a MILP instead of a MIQP.</p>
<p>$$
\begin{align*}
\min\quad       &amp; b \\
\text{s.t.}\quad&amp; \sum_{s} x_{t,s} = 1                &amp;\quad\forall&amp;\ t   &amp; \text{schedule each team meeting once}\\
&amp; b \ge \sum_{t} m_{i,t}\ x_{t,s} - 1 &amp;\quad\forall&amp;\ i,s &amp; \text{maximum overbooking}\\
&amp; x_{t,s} \in \{0,1\}               &amp;\quad\forall&amp;\ t,s
\end{align*}
$$</p>
<p>Once we solve the first model, we get the minimal value of $b$, which we call $b^*$. We can simply use $b^*$ as an upper bound for overbookings in the second original model.</p>
<p>$$
\begin{align*}
\min\quad       &amp; \sum_{i,s} c_{i,s} \\
\text{s.t.}\quad&amp; \sum_{s} x_{t,s} = 1                      &amp;\quad\forall&amp;\ t   &amp; \text{schedule each team meeting once}\\
&amp; c_{i,s} \ge \sum_{t} m_{i,t}\ x_{t,s} - 1 &amp;\quad\forall&amp;\ i,s &amp; \text{measure overbooking}\\
&amp; x_{t,s} \in \{0,1\}                     &amp;\quad\forall&amp;\ t,s\\
&amp; 0 \le c_{i,s} \le b^*                     &amp;\quad\forall&amp;\ i,s
\end{align*}
$$</p>
<p>As we see below, this model also eliminates triple bookings, and it&rsquo;s quite a bit faster to solve than the MIQP.</p>
<p><img alt="Minimize bottleneck - manager overbooking" loading="lazy" src="/files/2023-11-26-reducing-overscheduling/minimize-bottleneck-managers.svg#center"></p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2023-11-26-reducing-overscheduling/main.go"><code>main.go</code></a> generates input data</li>
<li><a href="/files/2023-11-26-reducing-overscheduling/membership.csv"><code>membership.csv</code></a> contains input data</li>
<li><a href="/files/2023-11-26-reducing-overscheduling/maximize-attendance.jl"><code>maximize-attendance.jl</code></a> MILP model</li>
<li><a href="/files/2023-11-26-reducing-overscheduling/minimize-overbooking.jl"><code>minimize-overbooking.jl</code></a> MILP model</li>
<li><a href="/files/2023-11-26-reducing-overscheduling/minimize-overbooking-squared.jl"><code>minimize-overbooking-squared.jl</code></a> MIQP model</li>
<li><a href="/files/2023-11-26-reducing-overscheduling/minimize-bottleneck.jl"><code>minimize-bottleneck.jl</code></a> hierarchical MILP models</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>🖍 Visualizing Decision Diagrams</title>
      <link>https://ryanjoneil.dev/posts/2023-09-13-visualizing-decision-diagrams/</link>
      <pubDate>Wed, 13 Sep 2023 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2023-09-13-visualizing-decision-diagrams/</guid>
      <description>A look at a few tools for visualizing decision diagrams</description>
      <content:encoded><![CDATA[<p>I attended <a href="https://sites.google.com/view/dpsolve2023/">DPSOLVE 2023</a> recently and found lots of good inspiration for the next version of Nextmv&rsquo;s Decision Diagram (DD) solver, <a href="https://www.nextmv.io/blog/how-hop-hops">Hop</a>. It&rsquo;s a few years old now, and we learned a lot applying it in the field. Hop formed the basis for our first routing models. While those models moved to a different structure in our <a href="https://www.nextmv.io/docs/vehicle-routing/get-started">latest routing code</a>, the <a href="https://www.nextmv.io/docs/vehicle-routing/legacy/routing">first version</a> broke ground combining DDs with Adaptive Large Neighborhood Search (ALNS), and its use continues to grow organically.</p>
<p>A feature I&rsquo;d love for Hop is the ability to visualize DDs and monitor the search. That could work interactively, like Gecode&rsquo;s <a href="https://researchmgt.monash.edu/ws/portalfiles/portal/257776846/3566886_oa.pdf">GIST</a>, or passively during the search process. This requires automatic generation of images representing potentially large diagrams. So I spent a few hours looking at graph rendering options for DDs.</p>
<h2 id="manual-rendering">Manual rendering</h2>
<p>We&rsquo;ll start with examples of visualizations built by hand. These form a good standard for how we want DDs to look if we automate rendering. We&rsquo;ll start with some examples from academic literature, look at some we&rsquo;ve used in <a href="https://nextmv.io">Nextmv</a> presentations, and show an interesting example that embeds in <a href="https://gohugo.io/">Hugo</a>, the popular static site generator I use for this blog.</p>
<p>All the literature on using Decision Diagrams (DD) for optimization that I&rsquo;m aware of depicts DDs as top-down, layered, directed graphs (digraphs). Some of the diagrams we come across appear to be coded and rendered, while some are fussily created by hand with a diagramming tool.</p>
<h3 id="academia">Academia</h3>
<p>I believe most of of the examples we find in academic literature are coded by hand and rendered using the LaTeX <a href="https://tikz.net/">TikZ</a> package. Below is one of the first diagrams that newcomers to DDs encounter. It&rsquo;s from <a href="https://link.springer.com/book/10.1007/978-3-319-42849-9">Decision Diagrams for Optimization</a> by Bergman et al, 2016.</p>
<p><img alt="Exact &amp; relaxed BDDs" loading="lazy" src="/files/2023-09-13-visualizing-decision-diagrams/ddo-exact-relaxed.png#center"></p>
<p>It doesn&rsquo;t matter here what model this represents. It&rsquo;s a Binary Decision Diagram (BDD), which means that each variable can be $0$ or $1$. The BDD on the left is exact, while the BDD on the right is a relaxed version of the same.</p>
<p>There&rsquo;s quite a bit going on, so it&rsquo;s worth an explanation. Let&rsquo;s look at the &ldquo;exact&rdquo; BDD on the left first.</p>
<ul>
<li>Horizontal layers group arcs with a binary variable (e.g. $x_1$, $x_2$).</li>
<li>Arcs assign either the value $0$ or $1$ to their layer&rsquo;s variable. Dotted lines assign $0$ while solid lines assign $1$.</li>
<li>Arc labels specify their costs. The BDD searches for a longest (or shortest) path from the root node $r$ to the terminal node $t$.</li>
</ul>
<p>The &ldquo;relaxed&rdquo; BDD on the right <em>overapproximates</em> both the objective value and the set of feasible solutions of the exact BDD on the left.</p>
<ul>
<li>The diagram is limited to a fixed width (2, in this case) at each layer.</li>
<li>The achieve this, the DD merges exact nodes together.</li>
<li>Thus, on the left of the relaxed BDD, there is a single node in which $x_2$ can be $0$ or $1$.</li>
</ul>
<p>Here&rsquo;s another example of an exact BDD from the same book.</p>
<p><img alt="Exact BDD" loading="lazy" src="/files/2023-09-13-visualizing-decision-diagrams/ddo-exact.png#center"></p>
<p>In this diagram, each node has a state. For example, the state of $r$ is $\{1,2,3,4,5\}$. If we start at the root node $r$ and assign $x_1 = 0$, we end up at node $u_1$ with state $\{2,3,4,5\}$.</p>
<p>Most other academic literature about DDs uses images similar to these.</p>
<h3 id="nextmv">Nextmv</h3>
<p>We&rsquo;ve rendered a number of DDs over the years at <a href="https://nextmv.io">Nextmv</a>. Most of these images demonstrate a concept instead of a particular model. We usually create them by hand in a diagramming tool like <a href="https://whimsical.com/">Whimsical</a>, <a href="https://www.lucidchart.com/">Lucidchart</a>, or <a href="https://excalidraw.com/">Excalidraw</a>. I built the diagrams below by hand in Whimsical. I think the result is nice, if time consuming and fussy.</p>
<p>This is a representation of an exact DD. It doesn&rsquo;t indicate whether this is a BDD or a Multivalued Decision Diagram (MDD). It doesn&rsquo;t have any labels or variable names. It just shows what a DD search might look like in the abstract.</p>
<p><img alt="Exact DD" loading="lazy" src="/files/2023-09-13-visualizing-decision-diagrams/nextmv-dd-exact.png#center"></p>
<p>The restricted DD below is more involved. It addition to horizontal layers, it divides nodes into explored and deferred groups. Most of the examples I&rsquo;ve seen mix different types of nodes, like exact and relaxed. I really like differentiating node types like this.</p>
<p>In this representation, deferred nodes are in Hop&rsquo;s queue for later exploration. Thus they don&rsquo;t connect to any child nodes yet. This is the kind of thing I&rsquo;d like to generate with real diagrams during search so I can examine the state of the solver.</p>
<p><img alt="Restricted DD" loading="lazy" src="/files/2023-09-13-visualizing-decision-diagrams/nextmv-dd-restricted.png#center"></p>
<p>My favorite of my DD renderings so far is the next one. This shows a single-vehicle pickup-and-delivery problem. The arc labels are stops (e.g. 🐶, 🐱). The path the 🚗 follows to the terminal node is the route. The gray boxes group together nodes to merge based on state to reduce isomorphisms out of the diagram.</p>
<p><img alt="Reduced MDD" loading="lazy" src="/files/2023-09-13-visualizing-decision-diagrams/nextmv-pdp-reduced.png#center"></p>
<p>We also have some images like those in our <a href="https://www.nextmv.io/blog/introducing-expanders-in-hop">post on expanders</a> by hand. As you can see, coding these by hand gets tedious.</p>
<h3 id="goat">GoAT</h3>
<p>TikZ is a program that renders manually coded graphics, while Whimsical is a WYSIWYG diagram editor. I like the Whimsical images a lot better &ndash; they feel cleaner and easier to understand.</p>
<p>Hugo <a href="https://gohugo.io/content-management/diagrams/#goat-diagrams-ascii">supports GoAT diagrams</a> by default, so I tried that out too. Here is an arbitrary MDD with two layers. The $[[1,2],4]$ node is a relaxed node; it doesn&rsquo;t really matter here what the label means.</p>



<div class="goat svg-container ">
  
    <svg
      xmlns="http://www.w3.org/2000/svg"
      font-family="Menlo,Lucida Console,monospace"
      
        viewBox="0 0 424 329"
      >
      <g transform='translate(8,16)'>
<path d='M 160,16 L 248,16' fill='none' stroke='currentColor'></path>
<path d='M 280,16 L 368,16' fill='none' stroke='currentColor'></path>
<path d='M 232,80 L 296,80' fill='none' stroke='currentColor'></path>
<path d='M 232,112 L 264,112' fill='none' stroke='currentColor'></path>
<path d='M 264,112 L 296,112' fill='none' stroke='currentColor'></path>
<path d='M 168,144 L 264,144' fill='none' stroke='currentColor'></path>
<path d='M 144,192 L 152,192' fill='none' stroke='currentColor'></path>
<path d='M 256,192 L 264,192' fill='none' stroke='currentColor'></path>
<path d='M 376,192 L 392,192' fill='none' stroke='currentColor'></path>
<path d='M 144,224 L 152,224' fill='none' stroke='currentColor'></path>
<path d='M 256,224 L 264,224' fill='none' stroke='currentColor'></path>
<path d='M 376,224 L 384,224' fill='none' stroke='currentColor'></path>
<path d='M 384,224 L 392,224' fill='none' stroke='currentColor'></path>
<path d='M 168,288 L 240,288' fill='none' stroke='currentColor'></path>
<path d='M 288,288 L 368,288' fill='none' stroke='currentColor'></path>
<path d='M 144,32 L 144,64' fill='none' stroke='currentColor'></path>
<path d='M 152,160 L 152,176' fill='none' stroke='currentColor'></path>
<path d='M 152,224 L 152,272' fill='none' stroke='currentColor'></path>
<path d='M 264,16 L 264,32' fill='none' stroke='currentColor'></path>
<path d='M 264,32 L 264,64' fill='none' stroke='currentColor'></path>
<path d='M 264,112 L 264,144' fill='none' stroke='currentColor'></path>
<path d='M 264,144 L 264,176' fill='none' stroke='currentColor'></path>
<path d='M 264,224 L 264,256' fill='none' stroke='currentColor'></path>
<path d='M 384,32 L 384,64' fill='none' stroke='currentColor'></path>
<path d='M 384,112 L 384,176' fill='none' stroke='currentColor'></path>
<path d='M 384,224 L 384,272' fill='none' stroke='currentColor'></path>
<path d='M 144,64 L 144,72' fill='none' stroke='currentColor'></path>
<polygon points='160.000000,64.000000 148.000000,58.400002 148.000000,69.599998' fill='currentColor' transform='rotate(90.000000, 144.000000, 64.000000)'></polygon>
<path d='M 152,176 L 152,184' fill='none' stroke='currentColor'></path>
<polygon points='168.000000,176.000000 156.000000,170.399994 156.000000,181.600006' fill='currentColor' transform='rotate(90.000000, 152.000000, 176.000000)'></polygon>
<polygon points='248.000000,288.000000 236.000000,282.399994 236.000000,293.600006' fill='currentColor' transform='rotate(0.000000, 240.000000, 288.000000)'></polygon>
<path d='M 264,64 L 264,72' fill='none' stroke='currentColor'></path>
<polygon points='280.000000,64.000000 268.000000,58.400002 268.000000,69.599998' fill='currentColor' transform='rotate(90.000000, 264.000000, 64.000000)'></polygon>
<path d='M 264,176 L 264,184' fill='none' stroke='currentColor'></path>
<polygon points='280.000000,176.000000 268.000000,170.399994 268.000000,181.600006' fill='currentColor' transform='rotate(90.000000, 264.000000, 176.000000)'></polygon>
<path d='M 264,256 L 264,264' fill='none' stroke='currentColor'></path>
<polygon points='280.000000,256.000000 268.000000,250.399994 268.000000,261.600006' fill='currentColor' transform='rotate(90.000000, 264.000000, 256.000000)'></polygon>
<polygon points='296.000000,288.000000 284.000000,282.399994 284.000000,293.600006' fill='currentColor' transform='rotate(180.000000, 288.000000, 288.000000)'></polygon>
<path d='M 384,64 L 384,72' fill='none' stroke='currentColor'></path>
<polygon points='400.000000,64.000000 388.000000,58.400002 388.000000,69.599998' fill='currentColor' transform='rotate(90.000000, 384.000000, 64.000000)'></polygon>
<path d='M 384,176 L 384,184' fill='none' stroke='currentColor'></path>
<polygon points='400.000000,176.000000 388.000000,170.399994 388.000000,181.600006' fill='currentColor' transform='rotate(90.000000, 384.000000, 176.000000)'></polygon>
<path d='M 264,0 A 16,16 0 0,0 248,16' fill='none' stroke='currentColor'></path>
<path d='M 264,0 A 16,16 0 0,1 280,16' fill='none' stroke='currentColor'></path>
<path d='M 160,16 A 16,16 0 0,0 144,32' fill='none' stroke='currentColor'></path>
<path d='M 368,16 A 16,16 0 0,1 384,32' fill='none' stroke='currentColor'></path>
<path d='M 248,16 A 16,16 0 0,0 264,32' fill='none' stroke='currentColor'></path>
<path d='M 280,16 A 16,16 0 0,1 264,32' fill='none' stroke='currentColor'></path>
<path d='M 144,80 A 16,16 0 0,0 128,96' fill='none' stroke='currentColor'></path>
<path d='M 144,80 A 16,16 0 0,1 160,96' fill='none' stroke='currentColor'></path>
<path d='M 232,80 A 16,16 0 0,0 216,96' fill='none' stroke='currentColor'></path>
<path d='M 296,80 A 16,16 0 0,1 312,96' fill='none' stroke='currentColor'></path>
<path d='M 384,80 A 16,16 0 0,0 368,96' fill='none' stroke='currentColor'></path>
<path d='M 384,80 A 16,16 0 0,1 400,96' fill='none' stroke='currentColor'></path>
<path d='M 128,96 A 16,16 0 0,0 144,112' fill='none' stroke='currentColor'></path>
<path d='M 160,96 A 16,16 0 0,1 144,112' fill='none' stroke='currentColor'></path>
<path d='M 216,96 A 16,16 0 0,0 232,112' fill='none' stroke='currentColor'></path>
<path d='M 312,96 A 16,16 0 0,1 296,112' fill='none' stroke='currentColor'></path>
<path d='M 368,96 A 16,16 0 0,0 384,112' fill='none' stroke='currentColor'></path>
<path d='M 400,96 A 16,16 0 0,1 384,112' fill='none' stroke='currentColor'></path>
<path d='M 168,144 A 16,16 0 0,0 152,160' fill='none' stroke='currentColor'></path>
<path d='M 144,192 A 16,16 0 0,0 128,208' fill='none' stroke='currentColor'></path>
<path d='M 152,192 A 16,16 0 0,1 168,208' fill='none' stroke='currentColor'></path>
<path d='M 256,192 A 16,16 0 0,0 240,208' fill='none' stroke='currentColor'></path>
<path d='M 264,192 A 16,16 0 0,1 280,208' fill='none' stroke='currentColor'></path>
<path d='M 376,192 A 16,16 0 0,0 360,208' fill='none' stroke='currentColor'></path>
<path d='M 392,192 A 16,16 0 0,1 408,208' fill='none' stroke='currentColor'></path>
<path d='M 128,208 A 16,16 0 0,0 144,224' fill='none' stroke='currentColor'></path>
<path d='M 168,208 A 16,16 0 0,1 152,224' fill='none' stroke='currentColor'></path>
<path d='M 240,208 A 16,16 0 0,0 256,224' fill='none' stroke='currentColor'></path>
<path d='M 280,208 A 16,16 0 0,1 264,224' fill='none' stroke='currentColor'></path>
<path d='M 360,208 A 16,16 0 0,0 376,224' fill='none' stroke='currentColor'></path>
<path d='M 408,208 A 16,16 0 0,1 392,224' fill='none' stroke='currentColor'></path>
<path d='M 264,272 A 16,16 0 0,0 248,288' fill='none' stroke='currentColor'></path>
<path d='M 264,272 A 16,16 0 0,1 280,288' fill='none' stroke='currentColor'></path>
<path d='M 152,272 A 16,16 0 0,0 168,288' fill='none' stroke='currentColor'></path>
<path d='M 384,272 A 16,16 0 0,1 368,288' fill='none' stroke='currentColor'></path>
<path d='M 248,288 A 16,16 0 0,0 264,304' fill='none' stroke='currentColor'></path>
<path d='M 280,288 A 16,16 0 0,1 264,304' fill='none' stroke='currentColor'></path>
<circle cx='264' cy='16' r='6' stroke='currentColor' fill='#fff'></circle>
<circle cx='264' cy='288' r='6' stroke='currentColor' fill='currentColor'></circle>
<text text-anchor='middle' x='64' y='100' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='64' y='212' fill='currentColor' style='font-size:1em'>x</text>
<text text-anchor='middle' x='72' y='100' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='72' y='212' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='144' y='100' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='144' y='212' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='152' y='212' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='232' y='100' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='240' y='100' fill='currentColor' style='font-size:1em'>[</text>
<text text-anchor='middle' x='248' y='100' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='256' y='100' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='256' y='212' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='264' y='100' fill='currentColor' style='font-size:1em'>2</text>
<text text-anchor='middle' x='264' y='212' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='272' y='100' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='280' y='100' fill='currentColor' style='font-size:1em'>,</text>
<text text-anchor='middle' x='288' y='100' fill='currentColor' style='font-size:1em'>4</text>
<text text-anchor='middle' x='296' y='100' fill='currentColor' style='font-size:1em'>]</text>
<text text-anchor='middle' x='376' y='212' fill='currentColor' style='font-size:1em'>1</text>
<text text-anchor='middle' x='384' y='100' fill='currentColor' style='font-size:1em'>3</text>
<text text-anchor='middle' x='384' y='212' fill='currentColor' style='font-size:1em'>0</text>
<text text-anchor='middle' x='392' y='212' fill='currentColor' style='font-size:1em'>0</text>
</g>

    </svg>
  
</div>
<p>I like the way GoAT renders this diagram. It&rsquo;s very readable. Unfortunately, it isn&rsquo;t easy to automate. Creating a GoAT diagram is like using ASCII as a WYSIWYG diagramming tool, as you can see from the code for that image.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>                                .-.
</span></span><span style="display:flex;"><span>                   .-----------+ o +-----------.
</span></span><span style="display:flex;"><span>                  |             &#39;+&#39;             |
</span></span><span style="display:flex;"><span>                  |              |              |
</span></span><span style="display:flex;"><span>                  v              v              v
</span></span><span style="display:flex;"><span>                 .-.        .---------.        .-.
</span></span><span style="display:flex;"><span>        x1      | 0 |      | [[1,2],4] |      | 3 |
</span></span><span style="display:flex;"><span>                 &#39;-&#39;        &#39;----+----&#39;        &#39;+&#39;
</span></span><span style="display:flex;"><span>                                 |              |
</span></span><span style="display:flex;"><span>                    .------------+              |
</span></span><span style="display:flex;"><span>                   |             |              |
</span></span><span style="display:flex;"><span>                   v             v              v
</span></span><span style="display:flex;"><span>                 .--.          .--.           .---.
</span></span><span style="display:flex;"><span>        x2      | 10 |        | 20 |         | 100 |
</span></span><span style="display:flex;"><span>                 &#39;-+&#39;          &#39;-+&#39;           &#39;-+-&#39;
</span></span><span style="display:flex;"><span>                   |             |              |
</span></span><span style="display:flex;"><span>                   |             v              |
</span></span><span style="display:flex;"><span>                   |            .-.             |
</span></span><span style="display:flex;"><span>                    &#39;---------&gt;| * |&lt;----------&#39;
</span></span><span style="display:flex;"><span>                                &#39;-&#39;
</span></span></code></pre></div><h2 id="automated-rendering">Automated rendering</h2>
<p>Now we&rsquo;ll look at a couple options for automatically generating visualizations of DDs. These convert descriptions of graphs into images.</p>
<h3 id="graphviz">Graphviz</h3>
<p><a href="https://graphviz.org/">Graphviz</a> is the tried and true graph visualizer. It&rsquo;s used in the <a href="https://go.dev/blog/pprof">Go <code>pprof</code></a> library for examining CPU and memory profiles, and lots of other places.</p>
<p>Graphviz accepts a language called <a href="https://graphviz.org/doc/info/lang.html">DOT</a>. It uses different <a href="https://graphviz.org/docs/layouts/">layout engines</a> to convert input into a visual representation. The user doesn&rsquo;t have control over node position. That&rsquo;s the job of the layout engine.</p>
<p>Here&rsquo;s the same MDD as written in DOT. The <code>start -&gt; end</code> lines specify arcs in the digraph. The subgraphs organize nodes into layers. We add a dotted border around each layer and a label to say which variable it assigns. There isn&rsquo;t any way of vertically centering and horizontally aligning the layer labels, so I thought it make more sense this way.</p>
<pre tabindex="0"><code class="language-dot" data-lang="dot">digraph G {
    s1 [label = 0]
    s2 [label = &#34;[[1,2],4]&#34;]
    s3 [label = 3]
    s4 [label = 10]
    s5 [label = 20]
    s6 [label = 100]

    r -&gt; s1 [label = 2]
    r -&gt; s2 [label = 4]
    r -&gt; s3 [label = 1]
    s2 -&gt; s4 [label = 10]
    s2 -&gt; s5 [label = 4]
    s3 -&gt; s6 [label = 2]

    subgraph cluster_0 {
        label = &#34;x1&#34;
        labeljust = &#34;l&#34;
        style = &#34;dotted&#34;
        s1
        s2
        s3
    }

    subgraph cluster_1 {
        label = &#34;x2&#34;
        labeljust = &#34;l&#34;
        style = &#34;dotted&#34;
        s4
        s5
        s6
    }

    s4 -&gt; t
    s5 -&gt; t
    s6 -&gt; t
}
</code></pre><p>The result is comprehensible if not very attractive. With some fiddling, it&rsquo;s possible to improve things like the spacing around arc labels. I couldn&rsquo;t figure out how to align the layer labels and boxes. It doesn&rsquo;t seem possible to move the relaxed nodes into their own column either, but that limitation isn&rsquo;t unique to Graphviz.</p>
<p><img alt="Graphviz DD" loading="lazy" src="/files/2023-09-13-visualizing-decision-diagrams/graphviz-dd.png#center"></p>
<h3 id="mermaid">Mermaid</h3>
<p><a href="https://mermaid.js.org/">Mermaid</a> is a JavaScript library for diagramming and charting. One can use it on the web or, presumably, embed it in an application.</p>
<p>Mermaid is similar to Graphviz in many ways, but it supports more diagram types. The input for that MDD in Mermaid is a bit simpler. Labels go inside arcs (e.g. <code>-- 2 --&gt;</code>), and there are more sensible rendering defaults.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>graph TD
</span></span><span style="display:flex;"><span>    start((( )))
</span></span><span style="display:flex;"><span>    stop((( )))
</span></span><span style="display:flex;"><span>    A(0)
</span></span><span style="display:flex;"><span>    B(&#34;[[1,2],4]&#34;)
</span></span><span style="display:flex;"><span>    C(3)
</span></span><span style="display:flex;"><span>    D(10)
</span></span><span style="display:flex;"><span>    E(20)
</span></span><span style="display:flex;"><span>    F(100)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    start -- 2 --&gt; A
</span></span><span style="display:flex;"><span>    start -- 4 --&gt; B
</span></span><span style="display:flex;"><span>    start -- 1 --&gt; C
</span></span><span style="display:flex;"><span>    B -- 10 --&gt; D
</span></span><span style="display:flex;"><span>    B -- 4 --&gt; E
</span></span><span style="display:flex;"><span>    C -- 2 --&gt; F
</span></span><span style="display:flex;"><span>    D --&gt; stop
</span></span><span style="display:flex;"><span>    E --&gt; stop
</span></span><span style="display:flex;"><span>    F --&gt; stop
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    subgraph &#34;x1 &#34;
</span></span><span style="display:flex;"><span>        A; B; C
</span></span><span style="display:flex;"><span>    end
</span></span><span style="display:flex;"><span>    subgraph &#34;x2&#34;
</span></span><span style="display:flex;"><span>        D; E; F
</span></span><span style="display:flex;"><span>    end
</span></span></code></pre></div><p>The result has a lot of the same limitations as the Graphviz version, but it looks more like the GoAT version. The biggest problem, as we see below, is that it&rsquo;s not possible to left-align the layer labels. They can be obscured by arcs.</p>
<script type="module">
    import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
    let isDark = document.body.className.includes('dark');
    mermaid.initialize({theme: (isDark) ? 'dark' : 'neutral'});
</script>

<center>
    <div class="mermaid">
        
graph TD
    start((( )))
    stop((( )))
    A(0)
    B("[[1,2],4]")
    C(3)
    D(10)
    E(20)
    F(100)

    start -- 2 --> A
    start -- 4 --> B
    start -- 1 --> C
    B -- 10 --> D
    B -- 4 --> E
    C -- 2 --> F
    D --> stop
    E --> stop
    F --> stop

    subgraph "x1 "
        A; B; C
    end
    subgraph "x2"
        D; E; F
    end

    </div>
</center>
<p>This got me thinking that there isn&rsquo;t a strong reason DDs have to progress downward layer by layer. They could just as easily go from left to right. If we change the opening line from <code>graph TD</code> to <code>graph LR</code>, then we get the following image.</p>
<script type="module">
    import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
    let isDark = document.body.className.includes('dark');
    mermaid.initialize({theme: (isDark) ? 'dark' : 'neutral'});
</script>

<center>
    <div class="mermaid">
        
graph LR
    start((( )))
    stop((( )))
    A(0)
    B("[[1,2],4]")
    C(3)
    D(10)
    E(20)
    F(100)

    start -- 2 --> A
    start -- 4 --> B
    start -- 1 --> C
    B -- 10 --> D
    B -- 4 --> E
    C -- 2 --> F
    D --> stop
    E --> stop
    F --> stop

    subgraph "x1 "
        A; B; C
    end
    subgraph "x2"
        D; E; F
    end

    </div>
</center>
<p>I think that&rsquo;s pretty nice for a generated image.</p>
]]></content:encoded>
    </item>
    <item>
      <title>👾 Detecting Polygon Intersections</title>
      <link>https://ryanjoneil.dev/posts/2015-09-27-detecting-polygon-intersections/</link>
      <pubDate>Sun, 27 Sep 2015 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2015-09-27-detecting-polygon-intersections/</guid>
      <description>Detecting when two polygons touch or overlap.</description>
      <content:encoded><![CDATA[<p><em>Note: This post has been updated to work with HiGHS.</em></p>
<p>A fun geometry problem to think about is: given two polygons, do they intersect? That is, do they touch on the border or overlap? Does one reside entirely within the other? While this question has obvious applications in computer graphics <em>(see: arcade games of the 1980s)</em>, it&rsquo;s also important in areas such as <a href="https://www.euro-online.org/websites/esicup/">cutting and packing problems</a>.</p>
<p>There are a number of way to answer this. In computer graphics, the problem is often approached using a <a href="https://en.wikipedia.org/wiki/Clipping_(computer_graphics)">clipping algorithm</a>. This post examines a couple of simpler techniques using linear inequalities and properties of convexity. To simplify the presentation, we assume we&rsquo;re only interested in convex polygons in two dimensions. We also assume that rotation is not an issue. That is, if one of the polygons is rotated, we can simply re-test to see if they overlap.</p>
<p><img alt="Don&rsquo;t get clobbered by an asteroid!" loading="lazy" src="/files/2015-09-27-polygon-intersections-part-1-detecting-overlap/asteroids.jpg#center"></p>
<h2 id="problem">Problem</h2>
<p>Let&rsquo;s say we have two objects: a right triangle and a square. We can place them anywhere inside a larger rectangle. The triangle has vertices:</p>
<p>$$\{\left(x_t, y_t\right), \left(x_t, y_t + a\right), \left(x_t + a, y_t\right)\}$$</p>
<p>The square has vertices:</p>
<p>$$\{\left(x_s, y_s\right), \left(x_s, y_s + a\right), \left(x_s + a, y_s + a\right), \left(x_s + a, y_s\right)\}$$</p>
<p>We will be given $\left(x_t, y_t\right)$, $\left(x_s, y_s\right)$, and $a$, but we do not know them a priori. We would like to know, for any set of values these can take, whether or not the triangle and square they define intersect.</p>
<p>$\left(x_t, y_t\right)$ and $\left(x_s, y_s\right)$ are the offsets of the triangle and square with respect to the bottom left corner of the rectangle. If they are far enough apart in any direction, the two objects do not intersect. The figure below shows such a case, with small gray circles representing $\left(x_t, y_t\right)$ and $\left(x_s, y_s\right)$.</p>
<p><img alt="Polygons that do not intersect" loading="lazy" src="/files/2015-09-27-polygon-intersections-part-1-detecting-overlap/polygons-that-do-not-intersect.svg#center"></p>
<p>However, if they are too close in some manner, the objects will either touch or overlap, as shown below.</p>
<p><img alt="Polygons that intersect" loading="lazy" src="/files/2015-09-27-polygon-intersections-part-1-detecting-overlap/polygons-that-intersect.svg#center"></p>
<p>he two polygons can intersect in a few different ways. They may touch on their borders, in which case they will share a single point or line segment. They may overlap such that their intersecting region has nonzero relative interior but each polygon contains points outside the other. Or one of them might live entirely within the other, so that the former is a subset of the latter. Our goal is to determine if any of these cases are true given any $\left(x_t, y_t\right)$, $\left(x_s, y_s\right)$, and $a$.</p>
<h3 id="method-1-define-the-intersecting-polygon-with-linear-inequalities">Method 1. Define the intersecting polygon with linear inequalities</h3>
<p>The first method we use to detect intersection is based on the fact that our polygons themselves are the intersections of finite numbers of linear inequalities. Instead of defining them based on their vertices, we can equivalently represent them as the set of $\left(x, y\right)$ that satisfy a known inequality for each edge.</p>
<p>Let $S_t$ be the set of points in our triangle. It can be defined as follows. $x$ must be greater than or equal to $x_t$. $y$ must be greater than or equal to $y_t$. And $x + y$ must be left of or lower than the triangle&rsquo;s hypotenuse. There are three sides on the triangle, so we have three inequalities.</p>
<p>$$
\begin{array}{rcl}
S_t = \{\,\left(x, y\right) &amp; | &amp; x \ge x_t,\\
&amp;   &amp; y \ge y_t,\\
&amp;   &amp; x + y \le x_t + y_t + a \,\}
\end{array}
$$</p>
<p>Similarly, let $S_s$ be the set of points in our square. This set is defined using four inequalities, which are shown in a slightly compacted form.</p>
<p>$$
\begin{array}{rcl}
S_s = \{\,\left(x, y\right) &amp; | &amp; x_s \le x \le x_s + a,\\
&amp;   &amp; y_s \le y \le y_s + a \,\}
\end{array}
$$</p>
<p>Finally, let $S_i = S_t \cap S_s$ be the set of points that satisfy all seven inequalities.</p>
<p>$$
\begin{array}{rcl}
S_i = \{\,\left(x, y\right) &amp; | &amp; x \ge x_t,\\
&amp;   &amp; y \ge y_t,\\
&amp;   &amp; x + y \le x_t + y_t + a,\\
&amp;   &amp; x_s \le x \le x_s + a,\\
&amp;   &amp; y_s \le y \le y_s + a \,\}
\end{array}
$$</p>
<p>If $S_i \ne \emptyset$, then there must exist some point that satisfies the inequalities of both the triangle and the square. This point resides in both of them, therefore they intersect. If $S_i = \emptyset$, then there is no such point and they do not intersect.</p>
<h3 id="method-2-use-convex-combinations-of-the-polygon-vertices">Method 2. Use convex combinations of the polygon vertices</h3>
<p>Both of our polygons are convex. That is, they contain every <a href="https://en.wikipedia.org/wiki/Convex_combination">convex combination</a> of their vertices. So every point in the triangle, regardless of where it is located, can be represented as a linear combination of ${\left(x_t, y_t\right), \left(x_t + a, y_t\right), \left(x_t, y_t + a\right)}$ where $\lambda_1, \lambda_2, \lambda_3 \ge 0$ and $\lambda_1 + \lambda_2 + \lambda_3 = 1$.</p>
<p>We can define the set $S_t$ equivalently using this concept.</p>
<p>$$
S_t = \{\, \lambda_1 \left(\begin{array}{c} x_t \\ y_t \end{array}\right) +
\lambda_2 \left(\begin{array}{c} x_t + a \\ y_t \end{array}\right) +
\lambda_3 \left(\begin{array}{c} x_t \\ y_t + a \end{array}\right) \, | \\
\lambda_1 + \lambda_2 + \lambda_3 = 1, \\
\lambda_i \ge 0, , i = {1, \ldots, 3 } \, \}
$$</p>
<p>Similarly, the square is defined a the convex combination of its vertices.</p>
<p>$$
S_s = \{\, \lambda_4 \left(\begin{array}{c} x_s \\ y_s \end{array}\right) +
\lambda_5 \left(\begin{array}{c} x_s + a \\ y_s \end{array}\right) +
\lambda_6 \left(\begin{array}{c} x_s \\ y_s + a \end{array}\right) +
\lambda_7 \left(\begin{array}{c} x_s + a \\ y_s + a \end{array}\right) \, | \\
\lambda_4 + \lambda_5 + \lambda_6 + \lambda_7 = 1, \\
\lambda_i \ge 0, , i = {4, \ldots, 7 } \, \}
$$</p>
<p>If there exists a point inside both the triangle and the square, then it must satisfy both convex combinations. Thus we can define our intersecting set $S_i$ as follows. <em>(This is a little loose with the notation, but I think it makes the point a bit better.)</em></p>
<p>$$
\begin{array}{rl}
S_i = \{\, &amp; \\
&amp; \lambda_1 \left(\begin{array}{c} x_t \\ y_t \end{array}\right) +
\lambda_2 \left(\begin{array}{c} x_t + a \\ y_t \end{array}\right) +
\lambda_3 \left(\begin{array}{c} x_t \\ y_t + a \end{array}\right) =\\
&amp; \lambda_4 \left(\begin{array}{c} x_s \\ y_s \end{array}\right) +
\lambda_5 \left(\begin{array}{c} x_s + a \\ y_s \end{array}\right) +
\lambda_6 \left(\begin{array}{c} x_s \\ y_s + a \end{array}\right) +
\lambda_7 \left(\begin{array}{c} x_s + a \\ y_s + a \end{array}\right),\\
&amp; \lambda_1 + \lambda_2 + \lambda_3 = 1,\\
&amp; \lambda_4 + \lambda_5 + \lambda_6 + \lambda_7 = 1,\\
&amp; \lambda_i \ge 0, \, i = {1, \ldots, 7}\\
\,\} &amp;
\end{array}
$$</p>
<p>Just as before, if $S_i \ne \emptyset$, our polygons intersect.</p>
<h2 id="code">Code</h2>
<p>Both models are pretty easy to implement using an <a href="https://en.wikipedia.org/wiki/Linear_programming">LP Solver</a>. But they look very different. That&rsquo;s because in the first method we&rsquo;re thinking about the problem in terms of inequalities and in the second we&rsquo;re thinking about it in terms of vertices. The code below generates a thousand random instances of the problem and tests that each method produces the same result.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">highspy</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">method1</span>(xy_t, xy_s, a):
</span></span><span style="display:flex;"><span>    x_t, y_t <span style="color:#ff7b72;font-weight:bold">=</span> xy_t
</span></span><span style="display:flex;"><span>    x_s, y_s <span style="color:#ff7b72;font-weight:bold">=</span> xy_s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    h <span style="color:#ff7b72;font-weight:bold">=</span> highspy<span style="color:#ff7b72;font-weight:bold">.</span>Highs()
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>silent()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    x <span style="color:#ff7b72;font-weight:bold">=</span> h<span style="color:#ff7b72;font-weight:bold">.</span>addVariable()
</span></span><span style="display:flex;"><span>    y <span style="color:#ff7b72;font-weight:bold">=</span> h<span style="color:#ff7b72;font-weight:bold">.</span>addVariable()
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>addConstrs(
</span></span><span style="display:flex;"><span>        x_t <span style="color:#ff7b72;font-weight:bold">&lt;=</span> x <span style="color:#ff7b72;font-weight:bold">&lt;=</span> x_t <span style="color:#ff7b72;font-weight:bold">+</span> a,
</span></span><span style="display:flex;"><span>        x_s <span style="color:#ff7b72;font-weight:bold">&lt;=</span> x <span style="color:#ff7b72;font-weight:bold">&lt;=</span> x_s <span style="color:#ff7b72;font-weight:bold">+</span> a,
</span></span><span style="display:flex;"><span>        y_t <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y_t <span style="color:#ff7b72;font-weight:bold">+</span> a,
</span></span><span style="display:flex;"><span>        y_s <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y_s <span style="color:#ff7b72;font-weight:bold">+</span> a,
</span></span><span style="display:flex;"><span>        x <span style="color:#ff7b72;font-weight:bold">+</span> y <span style="color:#ff7b72;font-weight:bold">&lt;=</span> x_t <span style="color:#ff7b72;font-weight:bold">+</span> y_t <span style="color:#ff7b72;font-weight:bold">+</span> a,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> h
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">method2</span>(xy_t, xy_s, a):
</span></span><span style="display:flex;"><span>    x_t, y_t <span style="color:#ff7b72;font-weight:bold">=</span> xy_t
</span></span><span style="display:flex;"><span>    x_s, y_s <span style="color:#ff7b72;font-weight:bold">=</span> xy_s
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    h <span style="color:#ff7b72;font-weight:bold">=</span> highspy<span style="color:#ff7b72;font-weight:bold">.</span>Highs()
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>silent()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    lm <span style="color:#ff7b72;font-weight:bold">=</span> [h<span style="color:#ff7b72;font-weight:bold">.</span>addVariable(lb<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0</span>, ub<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">7</span>)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    conv_xt <span style="color:#ff7b72;font-weight:bold">=</span> lm[<span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">*</span> x_t <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">*</span> (x_t <span style="color:#ff7b72;font-weight:bold">+</span> a) <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">2</span>] <span style="color:#ff7b72;font-weight:bold">*</span> x_t
</span></span><span style="display:flex;"><span>    conv_xs <span style="color:#ff7b72;font-weight:bold">=</span> lm[<span style="color:#a5d6ff">3</span>] <span style="color:#ff7b72;font-weight:bold">*</span> x_s <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">4</span>] <span style="color:#ff7b72;font-weight:bold">*</span> (x_s <span style="color:#ff7b72;font-weight:bold">+</span> a) <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">5</span>] <span style="color:#ff7b72;font-weight:bold">*</span> x_s <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">6</span>] <span style="color:#ff7b72;font-weight:bold">*</span> (x_s <span style="color:#ff7b72;font-weight:bold">+</span> a)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    conv_yt <span style="color:#ff7b72;font-weight:bold">=</span> lm[<span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">*</span> y_t <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">*</span> y_t <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">2</span>] <span style="color:#ff7b72;font-weight:bold">*</span> (y_t <span style="color:#ff7b72;font-weight:bold">+</span> a)
</span></span><span style="display:flex;"><span>    conv_ys <span style="color:#ff7b72;font-weight:bold">=</span> lm[<span style="color:#a5d6ff">3</span>] <span style="color:#ff7b72;font-weight:bold">*</span> y_s <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">4</span>] <span style="color:#ff7b72;font-weight:bold">*</span> y_s <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">5</span>] <span style="color:#ff7b72;font-weight:bold">*</span> (y_s <span style="color:#ff7b72;font-weight:bold">+</span> a) <span style="color:#ff7b72;font-weight:bold">+</span> lm[<span style="color:#a5d6ff">6</span>] <span style="color:#ff7b72;font-weight:bold">*</span> (y_s <span style="color:#ff7b72;font-weight:bold">+</span> a)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    h<span style="color:#ff7b72;font-weight:bold">.</span>addConstrs(
</span></span><span style="display:flex;"><span>        conv_xt <span style="color:#ff7b72;font-weight:bold">==</span> conv_xs,
</span></span><span style="display:flex;"><span>        conv_yt <span style="color:#ff7b72;font-weight:bold">==</span> conv_ys,
</span></span><span style="display:flex;"><span>        sum(lm[:<span style="color:#a5d6ff">3</span>]) <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span>,
</span></span><span style="display:flex;"><span>        sum(lm[<span style="color:#a5d6ff">3</span>:]) <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span>,
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> h
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#34;__main__&#34;</span>:
</span></span><span style="display:flex;"><span>    problems1 <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>    problems2 <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">1000</span>):
</span></span><span style="display:flex;"><span>        a <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">2.5</span> <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>        x_t <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">10</span>
</span></span><span style="display:flex;"><span>        y_t <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">10</span>
</span></span><span style="display:flex;"><span>        x_s <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">10</span>
</span></span><span style="display:flex;"><span>        y_s <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>random() <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">10</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        problems1<span style="color:#ff7b72;font-weight:bold">.</span>append(method1([x_t, y_t], [x_s, y_s], a))
</span></span><span style="display:flex;"><span>        problems2<span style="color:#ff7b72;font-weight:bold">.</span>append(method2([x_t, y_t], [x_s, y_s], a))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    overlap1 <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> h <span style="color:#ff7b72;font-weight:bold">in</span> problems1:
</span></span><span style="display:flex;"><span>        h<span style="color:#ff7b72;font-weight:bold">.</span>solve()
</span></span><span style="display:flex;"><span>        overlap1<span style="color:#ff7b72;font-weight:bold">.</span>append(h<span style="color:#ff7b72;font-weight:bold">.</span>getModelStatus())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    overlap2 <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> h <span style="color:#ff7b72;font-weight:bold">in</span> problems2:
</span></span><span style="display:flex;"><span>        h<span style="color:#ff7b72;font-weight:bold">.</span>solve()
</span></span><span style="display:flex;"><span>        overlap2<span style="color:#ff7b72;font-weight:bold">.</span>append(h<span style="color:#ff7b72;font-weight:bold">.</span>getModelStatus())
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assert</span> overlap1 <span style="color:#ff7b72;font-weight:bold">==</span> overlap2
</span></span></code></pre></div><p>These aren&rsquo;t necessarily the best ways to solve this particular problem, but they are quick and flexible. And they leverage existing solver technology. One downside is that they aren&rsquo;t easy to adapt to certain decision making contexts. That is, we can use them to <em>determine whether objects overlap</em>, but not to <em>force objects not to overlap</em>. In the next post, we&rsquo;ll go over another tool from computational geometry that allows us to embed decisions about the relative locations of objects in our models.</p>
<h2 id="exercises">Exercises</h2>
<ul>
<li>We assumed convex polygons in this presentation. How might one extend the model to work on non-convex polygons? What problems does this introduce?</li>
<li>The two methods shown above are equivalent. How can this be proven?</li>
<li>This post only answers the question of whether two convex polygons intersect. Devise models for determining if they only touch, or if one is a subset of the other.</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>😁 Are We Getting Happier?</title>
      <link>https://ryanjoneil.dev/posts/2014-07-18-are-we-getting-happier/</link>
      <pubDate>Fri, 18 Jul 2014 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2014-07-18-are-we-getting-happier/</guid>
      <description>A look at trends in Hedonometer.org&amp;#39;s happiness data using Julia, JuMP, and HiGHS.</description>
      <content:encoded><![CDATA[<p><em>Note: This post was originally written using Julia v0.2, GLPK, and Hedonometer data through 2014. It has been updated to use Julia v1.11, HiGHS, and data through May 26, 2025.</em></p>
<p><a href="https://hedonometer.org/">Hedonometer</a> popped onto my radar a couple weeks ago. It&rsquo;s a nifty project, attempting to convert samples of words found in the Twitter Gardenhose feed into a time series of happiness.</p>
<p>While I&rsquo;m not a computational social scientist, I must say the data does have a nice intuitive quality to it. There are obvious trends in happiness associated with major holidays, days of the week, and seasons. It seems like the sort of data that could be decomposed into trends based on those various components. The Hedonometer group has, of course, done extensive analyses of their own data which you can find on <a href="https://www.hedonometer.org/papers.html">their papers page</a>.</p>
<p>This post examines another approach. It follows the structure of <a href="https://vanderbei.princeton.edu/">Robert Vanderbei</a>&rsquo;s excellent &ldquo;<a href="https://epubs.siam.org/doi/pdf/10.1137/110827296">Local Warming</a>&rdquo; project to separate out the Hedonometer averages into daily, seasonal, solar, and day-of-the-week trends. We&rsquo;ll be using Julia with <a href="https://jump.dev/">JuMP</a> and <a href="https://highs.dev/">HiGHS</a> for linear optimization, <a href="https://gadflyjl.org/z">Gadfly</a> for graphing, and a few other libraries. If you haven&rsquo;t installed Julia, first do that. Missing packages should be installed when you import them.</p>
<h2 id="data">Data</h2>
<p>Hedonometer provides <a href="https://www.hedonometer.org/api.html">an API</a> which we can use to pull daily happiness data in JSON format. We can request specific date rates, or leave the dates off to retrieve the full data set.</p>
<p>We use the <code>HTTP</code>, <code>JSON3</code>, and <code>DataFrame</code> packages to read the Hedonometer data into a data frame. Calls to <code>parse</code> convert strings to date and float types. Finally, we sort the data frame in place by ascending date.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span><span style="color:#ff7b72">using</span> DataFrames, HTTP, JSON3
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>url <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#34;https://hedonometer.org/api/v1/happiness/?format=json&amp;timeseries__title=en_all&#34;</span>
</span></span><span style="display:flex;"><span>response <span style="color:#ff7b72;font-weight:bold">=</span> HTTP<span style="color:#ff7b72;font-weight:bold">.</span>get(url)
</span></span><span style="display:flex;"><span>df <span style="color:#ff7b72;font-weight:bold">=</span> DataFrame(JSON3<span style="color:#ff7b72;font-weight:bold">.</span>read(response<span style="color:#ff7b72;font-weight:bold">.</span>body)[<span style="color:#a5d6ff">:objects</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>df<span style="color:#ff7b72;font-weight:bold">.</span>date <span style="color:#ff7b72;font-weight:bold">=</span> parse<span style="color:#ff7b72;font-weight:bold">.</span>(Date, df<span style="color:#ff7b72;font-weight:bold">.</span>date)
</span></span><span style="display:flex;"><span>df<span style="color:#ff7b72;font-weight:bold">.</span>happiness <span style="color:#ff7b72;font-weight:bold">=</span> parse<span style="color:#ff7b72;font-weight:bold">.</span>(<span style="color:#ff7b72">Float64</span>, df<span style="color:#ff7b72;font-weight:bold">.</span>happiness)
</span></span><span style="display:flex;"><span>sort!(df, <span style="color:#a5d6ff">:date</span>)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>5367×4 DataFrame
</span></span><span style="display:flex;"><span>  Row │ date        frequency  happiness  timeseries            
</span></span><span style="display:flex;"><span>      │ Date        Int64      Float64    String                
</span></span><span style="display:flex;"><span>──────┼─────────────────────────────────────────────────────────
</span></span><span style="display:flex;"><span>    1 │ 2008-09-09    2009276      6.042  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    2 │ 2008-09-10    5263723      6.028  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    3 │ 2008-09-11    5298101      6.02   /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    4 │ 2008-09-12    5351503      6.028  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    5 │ 2008-09-13    5153710      6.035  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    6 │ 2008-09-14    5170835      6.04   /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    7 │ 2008-09-15    5553350      6.004  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    8 │ 2008-09-16    5421531      6.011  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>    9 │ 2008-09-17    5380008      6.02   /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>   10 │ 2008-09-18    5591645      6.034  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>   11 │ 2008-09-19    5695345      6.063  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>   12 │ 2008-09-20    5291298      6.081  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>   13 │ 2008-09-21    5363113      6.066  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>  ⋮   │     ⋮           ⋮          ⋮                ⋮
</span></span><span style="display:flex;"><span> 5356 │ 2023-05-15  170487394      6.026  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5357 │ 2023-05-16  174192397      6.021  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5358 │ 2023-05-17  186034773      6.016  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5359 │ 2023-05-18  189092448      6.03   /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5360 │ 2023-05-19  179957496      6.026  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5361 │ 2023-05-20  167540306      6.044  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5362 │ 2023-05-21  167091303      6.031  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5363 │ 2023-05-22  171660415      6.03   /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5364 │ 2023-05-23  166443756      6.033  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5365 │ 2023-05-24  183687637      6.025  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5366 │ 2023-05-25  170265817      6.014  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span> 5367 │ 2023-05-26  180664806      6.032  /api/v1/timeseries/3/
</span></span><span style="display:flex;"><span>                                               5342 rows omitted
</span></span></code></pre></div><p>Note that the data does seem to be missing a few days. This means we need to compute day offsets in our model instead of using row indices.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span>last(df<span style="color:#ff7b72;font-weight:bold">.</span>date) <span style="color:#ff7b72;font-weight:bold">-</span> first(df<span style="color:#ff7b72;font-weight:bold">.</span>date)
</span></span><span style="display:flex;"><span>nrow(df)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>5372 days
</span></span><span style="display:flex;"><span>5367
</span></span></code></pre></div><p>Now lets take a look at happiness over time, as computed by Hedonometer.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> plot_happiness(df<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#ff7b72">DataFrame</span>)
</span></span><span style="display:flex;"><span>    plot(
</span></span><span style="display:flex;"><span>        df,
</span></span><span style="display:flex;"><span>        x<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">:date</span>,
</span></span><span style="display:flex;"><span>        y<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">:happiness</span>,
</span></span><span style="display:flex;"><span>        color<span style="color:#ff7b72;font-weight:bold">=</span>[<span style="color:#79c0ff">colorant</span><span style="color:#a5d6ff">&#34;darkblue&#34;</span>],
</span></span><span style="display:flex;"><span>        Guide<span style="color:#ff7b72;font-weight:bold">.</span>xlabel(<span style="color:#a5d6ff">&#34;Date&#34;</span>),
</span></span><span style="display:flex;"><span>        Guide<span style="color:#ff7b72;font-weight:bold">.</span>ylabel(<span style="color:#a5d6ff">&#34;Happiness&#34;</span>),
</span></span><span style="display:flex;"><span>        Coord<span style="color:#ff7b72;font-weight:bold">.</span>cartesian(
</span></span><span style="display:flex;"><span>            xmin<span style="color:#ff7b72;font-weight:bold">=</span>minimum(df<span style="color:#ff7b72;font-weight:bold">.</span>date),
</span></span><span style="display:flex;"><span>            xmax<span style="color:#ff7b72;font-weight:bold">=</span>maximum(df<span style="color:#ff7b72;font-weight:bold">.</span>date)
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        Theme(
</span></span><span style="display:flex;"><span>            key_position<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">:none</span>,
</span></span><span style="display:flex;"><span>            line_width<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0.75</span>px,
</span></span><span style="display:flex;"><span>            background_color<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">colorant</span><span style="color:#a5d6ff">&#34;white&#34;</span>
</span></span><span style="display:flex;"><span>        ),
</span></span><span style="display:flex;"><span>        Geom<span style="color:#ff7b72;font-weight:bold">.</span>line
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>plot_happiness(df)
</span></span></code></pre></div><p><img alt="Happiness over time" loading="lazy" src="/files/2014-07-18-are-we-getting-happier/happiness-over-time.png#center"></p>
<p>The data looks right, so we&rsquo;re off to a good start. Now we have to think about what sort of components we believe are important factors to this index. We&rsquo;ll start with the same ones as in the Vanderbei model:</p>
<ul>
<li>A linear happiness trend describing how our overall happiness changes over time.</li>
<li>Seasonal trends accounting for mood changes with weather.</li>
<li>Solar cycle trends.</li>
</ul>
<p>We&rsquo;ll add to this weekly trends, as zooming into the data shows we tend to be happier on the weekends than on work days. In the next section we&rsquo;ll build a model to separate out the effects of these trends on the Hedonometer index.</p>
<h2 id="model">Model</h2>
<p>Vanderbei&rsquo;s model analyzes daily temperature data for a particular location using <a href="https://en.wikipedia.org/wiki/Least_absolute_deviations">least absolute deviations</a> (LAD). This is similar to the well-known <a href="https://en.wikipedia.org/wiki/Least_squares">least squares approach</a>, but while the latter penalizes the model quadratically more for bigger errors, the former does not. In mathematical notation, the least squares model takes in a known $m \times n$ matrix $A$ and $m \times 1$ vector $y$ of observed data, then searches for a vector $x$ such that $Ax = \hat{y}$ and $\sum_i \left\lVert y_i - \hat{y}_i \right\rVert_2^2$ is minimized.</p>
<p>The LAD model is similar in that it takes in the same data, but instead of minimizing the sum of the squared $L^2$ norms, it minimizes the sum of the $L^1$ norms. Thus we penalize our model using simply the absolute values of its errors instead of their squares. This makes the LAD model more <em>robust</em>, that is, less sensitive to outliers in our input data.</p>
<p>Using a robust model with this data set makes sense because it clearly contains a lot of outliers. While some of them, such as December 25th, may be recurrent, we&rsquo;re going to ignore that detail for now. After all, not every day is Christmas.</p>
<p>We formulate our model below using JuMP with the HiGHS solver. The code works by defining a set of variables called <code>coefficients</code> that will converge to optimal values for $x$. For each observation we compute a row of the $A$ matrix that has the following components:</p>
<ul>
<li>Linear daily trend ($a_1$ = day number in the data set)</li>
<li>Seasonal variation: $\cos(2, \pi, a_1 / 365.25)$ and $\sin(2, \pi, a_1 / 365.25)$</li>
<li>Solar cycle variation: $\cos(2, \pi, a_1 / (10.66 \times 365.25))$ and $\sin(2, \pi, a_1 / (10.66 \times 365.25))$</li>
<li>Weekly variation: $\cos(2, \pi, a_1 / 7)$ and $\sin(2, \pi, a_1 / 7)$</li>
</ul>
<p>We then add a linear variable representing the residual, or error, of the fitted model for each observation. Constraints enforce that these variables always take the absolute values of those errors.</p>
<p>Minimizing the sum of those residuals gives us a set of eight coefficients for the model. We return these and a function that predicts the happiness level for an offset from the first data record. (Note that the first record appears to be from Wednesday, September 9, 2008.)</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> train(df<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#ff7b72">DataFrame</span>)
</span></span><span style="display:flex;"><span>    m <span style="color:#ff7b72;font-weight:bold">=</span> Model(HiGHS<span style="color:#ff7b72;font-weight:bold">.</span>Optimizer)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Define a linear variable for each of our regression coefficients.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Note that by default, JuMP variables are unrestricted in sign.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">@variable</span>(m, x[<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">8</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Residuals are the absolute values of the error comparing our</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># observed and fitted values.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic">#</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># If alpha - beta = residual and alpha, beta &gt;= 0, then we can min</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># alpha + beta to get the absolute value of the residual.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">@variable</span>(m, alpha[<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span>nrow(df)] <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">@variable</span>(m, beta[<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span>nrow(df)] <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># This builds rows for determining fitted values. The first value is</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># 1 since it is multiplied by our our trend line&#39;s offset. The other</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># values correspond to the trends described above. Sinusoidal elements</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># have two variables with sine and cosine terms.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">function</span> constants(i)
</span></span><span style="display:flex;"><span>        [
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">1</span>,                                <span style="color:#8b949e;font-style:italic"># Offset</span>
</span></span><span style="display:flex;"><span>            i,                                <span style="color:#8b949e;font-style:italic"># Daily trend</span>
</span></span><span style="display:flex;"><span>            cos(<span style="color:#a5d6ff">2</span>pi <span style="color:#ff7b72;font-weight:bold">*</span> i <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#a5d6ff">365.25</span>),            <span style="color:#8b949e;font-style:italic"># Seasonal variation</span>
</span></span><span style="display:flex;"><span>            sin(<span style="color:#a5d6ff">2</span>pi <span style="color:#ff7b72;font-weight:bold">*</span> i <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#a5d6ff">365.25</span>),            <span style="color:#8b949e;font-style:italic">#</span>
</span></span><span style="display:flex;"><span>            cos(<span style="color:#a5d6ff">2</span>pi <span style="color:#ff7b72;font-weight:bold">*</span> i <span style="color:#ff7b72;font-weight:bold">/</span> (<span style="color:#a5d6ff">10.66</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">365.25</span>)),  <span style="color:#8b949e;font-style:italic"># Solar cycle variation</span>
</span></span><span style="display:flex;"><span>            sin(<span style="color:#a5d6ff">2</span>pi <span style="color:#ff7b72;font-weight:bold">*</span> i <span style="color:#ff7b72;font-weight:bold">/</span> (<span style="color:#a5d6ff">10.66</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">365.25</span>)),  <span style="color:#8b949e;font-style:italic">#</span>
</span></span><span style="display:flex;"><span>            cos(<span style="color:#a5d6ff">2</span>pi <span style="color:#ff7b72;font-weight:bold">*</span> i <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#a5d6ff">7</span>),                 <span style="color:#8b949e;font-style:italic"># Weekly variation</span>
</span></span><span style="display:flex;"><span>            sin(<span style="color:#a5d6ff">2</span>pi <span style="color:#ff7b72;font-weight:bold">*</span> i <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#a5d6ff">7</span>)                  <span style="color:#8b949e;font-style:italic">#</span>
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># This builds a linear expression as the dot product of a row&#39;s</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># constants and the coefficient variables.</span>
</span></span><span style="display:flex;"><span>    expression(i) <span style="color:#ff7b72;font-weight:bold">=</span> dot(constants(i), x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    start <span style="color:#ff7b72;font-weight:bold">=</span> minimum(df<span style="color:#ff7b72;font-weight:bold">.</span>date)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> (i, row) <span style="color:#ff7b72">in</span> enumerate(eachrow(df))
</span></span><span style="display:flex;"><span>        days <span style="color:#ff7b72;font-weight:bold">=</span> (row<span style="color:#ff7b72;font-weight:bold">.</span>date <span style="color:#ff7b72;font-weight:bold">-</span> start)<span style="color:#ff7b72;font-weight:bold">.</span>value
</span></span><span style="display:flex;"><span>        <span style="color:#d2a8ff;font-weight:bold">@constraint</span>(m, alpha[i] <span style="color:#ff7b72;font-weight:bold">-</span> beta[i] <span style="color:#ff7b72;font-weight:bold">==</span> expression(days) <span style="color:#ff7b72;font-weight:bold">-</span> row<span style="color:#ff7b72;font-weight:bold">.</span>happiness)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Minimize the total sum of these residuals.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">@objective</span>(m, Min, sum(alpha <span style="color:#ff7b72;font-weight:bold">+</span> beta))
</span></span><span style="display:flex;"><span>    optimize!(m)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Return the model coefficients and a function that predicts happiness</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># for a given day, by index from the start of the data set.</span>
</span></span><span style="display:flex;"><span>    coefficients <span style="color:#ff7b72;font-weight:bold">=</span> value<span style="color:#ff7b72;font-weight:bold">.</span>(x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># And we would like our model to work over vectors.</span>
</span></span><span style="display:flex;"><span>    predict(i) <span style="color:#ff7b72;font-weight:bold">=</span> dot(constants(i), coefficients)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> coefficients, predict
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>coefficients, predictor <span style="color:#ff7b72;font-weight:bold">=</span> train(df)
</span></span><span style="display:flex;"><span>coefficients
</span></span></code></pre></div><p>This gives us the optimal value of $x$. The second value is the change in happiness per day. We can see from this that there does seem to be a small negative trend.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>8-element Vector{Float64}:
</span></span><span style="display:flex;"><span>  6.056241434337748
</span></span><span style="display:flex;"><span> -1.2891297798930273e-5
</span></span><span style="display:flex;"><span> -0.004956377505740697
</span></span><span style="display:flex;"><span>  0.00933036370632761
</span></span><span style="display:flex;"><span> -0.014231170085464805
</span></span><span style="display:flex;"><span> -0.01043882249306958
</span></span><span style="display:flex;"><span> -0.01121031443373725
</span></span><span style="display:flex;"><span> -0.003886782963711294
</span></span></code></pre></div><p>We can call our predictor function to obtain the fitted happiness level for any day number starting from September 9, 2008.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span>predictor(<span style="color:#a5d6ff">1000</span>)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>6.0206559094198635
</span></span></code></pre></div><p>Similarly, we can compute a range of fitted happiness values.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span>predictor<span style="color:#ff7b72;font-weight:bold">.</span>(<span style="color:#a5d6ff">1000</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">1009</span>)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>10-element Vector{Float64}:
</span></span><span style="display:flex;"><span> 6.0206559094198635
</span></span><span style="display:flex;"><span> 6.0133111627737295
</span></span><span style="display:flex;"><span> 6.01441070655176
</span></span><span style="display:flex;"><span> 6.0230645068990105
</span></span><span style="display:flex;"><span> 6.032696070016563
</span></span><span style="display:flex;"><span> 6.0359946962536455
</span></span><span style="display:flex;"><span> 6.030420605574473
</span></span><span style="display:flex;"><span> 6.020117462633424
</span></span><span style="display:flex;"><span> 6.012792131062921
</span></span><span style="display:flex;"><span> 6.013911265631212
</span></span></code></pre></div><h2 id="bootstrapping">Bootstrapping</h2>
<p>We now have a set of coefficients and a predictive model. That&rsquo;s nice, but we&rsquo;d like to have some sense of a reasonable range on our model&rsquo;s coefficients. For instance, how certain are we that our daily trend is really even positive? To deal with these uncertainties, we use a method called <a href="https://en.wikipedia.org/wiki/Bootstrapping_%28statistics%29">bootstrapping</a>.</p>
<p>Bootstrapping involves building fake observed data based on our fitted model and its associated errors. We then fit the model to our fake data to determine new coefficients. If we repeat this enough times, we may be able to generate decent confidence intervals around our model coefficients.</p>
<p>First step: compute the errors between the observed and fitted data. We&rsquo;ll construct a new data frame that contains everything we need to construct fake data.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Compute fitted data corresponding to our observations and their associated errors.</span>
</span></span><span style="display:flex;"><span>start <span style="color:#ff7b72;font-weight:bold">=</span> minimum(df<span style="color:#ff7b72;font-weight:bold">.</span>date)
</span></span><span style="display:flex;"><span>fitted <span style="color:#ff7b72;font-weight:bold">=</span> DataFrame(
</span></span><span style="display:flex;"><span>    date<span style="color:#ff7b72;font-weight:bold">=</span>df<span style="color:#ff7b72;font-weight:bold">.</span>date,
</span></span><span style="display:flex;"><span>    happiness<span style="color:#ff7b72;font-weight:bold">=</span>predictor<span style="color:#ff7b72;font-weight:bold">.</span>(map(d <span style="color:#ff7b72;font-weight:bold">-&gt;</span> d<span style="color:#ff7b72;font-weight:bold">.</span>value, df<span style="color:#ff7b72;font-weight:bold">.</span>date <span style="color:#ff7b72;font-weight:bold">.-</span> start))
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>fitted<span style="color:#ff7b72;font-weight:bold">.</span>error <span style="color:#ff7b72;font-weight:bold">=</span> fitted<span style="color:#ff7b72;font-weight:bold">.</span>happiness <span style="color:#ff7b72;font-weight:bold">-</span> df<span style="color:#ff7b72;font-weight:bold">.</span>happiness
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>4×7 DataFrame
</span></span><span style="display:flex;"><span> Row │ variable  mean        min         median      max         nmissing  eltype   
</span></span><span style="display:flex;"><span>     │ Symbol    Union…      Any         Any         Any         Int64     DataType 
</span></span><span style="display:flex;"><span>─────┼──────────────────────────────────────────────────────────────────────────────
</span></span><span style="display:flex;"><span>   1 │ date                  2008-09-09  2016-01-19  2023-05-26         0  Date
</span></span><span style="display:flex;"><span>   2 │ observed  6.01506     5.628       6.016       6.376              0  Float64
</span></span><span style="display:flex;"><span>   3 │ fitted    6.01858     5.95996     6.0245      6.06771            0  Float64
</span></span><span style="display:flex;"><span>   4 │ error     0.00351745  -0.327932   0.0         0.353297           0  Float64
</span></span></code></pre></div><p>Note that the median for our errors is exactly zero. This is a good sign.</p>
<p>Now we build a function that creates a fake input data set using the fitted values with randomly selected errors. That is, for each observation, we add a randomly selected error with replacement to its corresponding fitted value. Once we&rsquo;ve done that for every observation, we have a complete fake data set.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span><span style="color:#ff7b72">function</span> fake_data(fitted<span style="color:#ff7b72;font-weight:bold">::</span><span style="color:#ff7b72">DataFrame</span>)
</span></span><span style="display:flex;"><span>    indices <span style="color:#ff7b72;font-weight:bold">=</span> rand(<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span>nrow(fitted), nrow(fitted))
</span></span><span style="display:flex;"><span>    DataFrame(
</span></span><span style="display:flex;"><span>        date<span style="color:#ff7b72;font-weight:bold">=</span>fitted<span style="color:#ff7b72;font-weight:bold">.</span>date,
</span></span><span style="display:flex;"><span>        happiness<span style="color:#ff7b72;font-weight:bold">=</span>fitted<span style="color:#ff7b72;font-weight:bold">.</span>happiness <span style="color:#ff7b72;font-weight:bold">+</span> fitted<span style="color:#ff7b72;font-weight:bold">.</span>error[indices]
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">end</span>
</span></span></code></pre></div><p>Le&rsquo;ts plot some fake data to see if it looks similar.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span>plot_happiness(fake_data(fitted))
</span></span></code></pre></div><p><img alt="Fake happiness over time" loading="lazy" src="/files/2014-07-18-are-we-getting-happier/fake-happiness-over-time.png#center"></p>
<p>Visually, the plot of an arbitrary fake data set looks a lot like our original data, but not exactly.</p>
<p>Now we generate 199 fake data sets and run them through our optimization function above. This generates 100 sets of model coefficients and then computes $2\sigma$ confidence intervals around them.</p>
<p>The following code took a few minutes on my machine. If you&rsquo;re intent on running it yourself, you may want to get some coffee in the meantime.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span><span style="color:#ff7b72">using</span> HypothesisTests
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>coefficient_data <span style="color:#ff7b72;font-weight:bold">=</span> [train(fake_data(fitted))[<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">10</span>]
</span></span><span style="display:flex;"><span>confidence_intervals <span style="color:#ff7b72;font-weight:bold">=</span> map(
</span></span><span style="display:flex;"><span>    i <span style="color:#ff7b72;font-weight:bold">-&gt;</span> confint(OneSampleTTest([c[i] <span style="color:#ff7b72">for</span> c <span style="color:#ff7b72">in</span> coefficient_data])),
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span>length(coefficients)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>confidence_intervals
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>8-element Vector{Tuple{Float64, Float64}}:
</span></span><span style="display:flex;"><span> (6.055873073964057, 6.056558575993962)
</span></span><span style="display:flex;"><span> (-1.304766046882304e-5, -1.2820860816666347e-5)
</span></span><span style="display:flex;"><span> (-0.005375474015660127, -0.004919187929729383)
</span></span><span style="display:flex;"><span> (0.009063427594182353, 0.009549696262121937)
</span></span><span style="display:flex;"><span> (-0.01457854811479927, -0.014061680921618894)
</span></span><span style="display:flex;"><span> (-0.010625952275843275, -0.010121904880077722)
</span></span><span style="display:flex;"><span> (-0.011441598179220986, -0.010964483538449823)
</span></span><span style="display:flex;"><span> (-0.004290285203751687, -0.003833687701331523)
</span></span></code></pre></div><h2 id="results">Results</h2>
<p>From the above output we can see that appear to be trending slightly less happy over time, with a daily trend of -0.00001293 in Hedonometer units and a 95% confidence interval on that trend of approximately -0.00001305, and -0.00001282. Bummer.</p>
<p>Now we take a quick look at our model output. First, we plot the fitted happiness values for the same time period as the observed data. We can see that this resembles the same general trend minus the outliers. The width of the curve is due to weekly variation.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span>plot_happiness(fitted)
</span></span></code></pre></div><p><img alt="Fitted happiness over time" loading="lazy" src="/files/2014-07-18-are-we-getting-happier/fitted-happiness-over-time.png#center"></p>
<p>Now we take a look at what a typical week looks like in terms of its effect on our happiness. As September 9, 2008 was a Wednesday, we index Sunday starting at 6.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-julia" data-lang="julia"><span style="display:flex;"><span>daily(i) <span style="color:#ff7b72;font-weight:bold">=</span> coefficients[<span style="color:#a5d6ff">6</span>]<span style="color:#ff7b72;font-weight:bold">*</span>cos(<span style="color:#a5d6ff">2</span>pi<span style="color:#ff7b72;font-weight:bold">*</span>i<span style="color:#ff7b72;font-weight:bold">/</span><span style="color:#a5d6ff">7</span>) <span style="color:#ff7b72;font-weight:bold">+</span> coefficients[<span style="color:#a5d6ff">7</span>]<span style="color:#ff7b72;font-weight:bold">*</span>sin(<span style="color:#a5d6ff">2</span>pi<span style="color:#ff7b72;font-weight:bold">*</span>i<span style="color:#ff7b72;font-weight:bold">/</span><span style="color:#a5d6ff">7</span>)
</span></span><span style="display:flex;"><span>plot(
</span></span><span style="display:flex;"><span>    x <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#a5d6ff">&#34;Sun&#34;</span>, <span style="color:#a5d6ff">&#34;Mon&#34;</span>, <span style="color:#a5d6ff">&#34;Tues&#34;</span>, <span style="color:#a5d6ff">&#34;Wed&#34;</span>, <span style="color:#a5d6ff">&#34;Thurs&#34;</span>, <span style="color:#a5d6ff">&#34;Fri&#34;</span>, <span style="color:#a5d6ff">&#34;Sat&#34;</span>],
</span></span><span style="display:flex;"><span>    y <span style="color:#ff7b72;font-weight:bold">=</span> map(daily, [<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>]),
</span></span><span style="display:flex;"><span>    Guide<span style="color:#ff7b72;font-weight:bold">.</span>xlabel(<span style="color:#a5d6ff">&#34;Day of the Week&#34;</span>),
</span></span><span style="display:flex;"><span>    Guide<span style="color:#ff7b72;font-weight:bold">.</span>ylabel(<span style="color:#a5d6ff">&#34;Happiness&#34;</span>),
</span></span><span style="display:flex;"><span>    Geom<span style="color:#ff7b72;font-weight:bold">.</span>line,
</span></span><span style="display:flex;"><span>    Geom<span style="color:#ff7b72;font-weight:bold">.</span>point
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The resulting graph highlights what I think we all already know about the work week.</p>
<p><img alt="Happiness by day of week" loading="lazy" src="/files/2014-07-18-are-we-getting-happier/happiness-by-day-of-week.png#center"></p>
<p>That&rsquo;s it for this analysis. We&rsquo;ve learned that, for the being at least, we seem to be trending less happy. When we initially did this analysis, almost 11 years ago, the opposite was true. The fitted data shows pretty clearly when that trend took a stark turn down.</p>
<h2 id="exercises">Exercises</h2>
<p>The particularly ambitious reader may find the following exercises interesting.</p>
<ul>
<li>The code that reruns the model using randomly constructed fake data is eligible for parallelization. Rewrite the list comprehension that calls <code>train</code> so it runs concurrently.</li>
<li>According to Google, the lunar cycle is approximately 29.53 days. Add parameters for this to the LAD model above. Does it make sense to include the lunar cycle in the model? In other words, are we lunatics?</li>
<li>Some of the happier days in the Hedonometer data, such as Christmas, are recurring, and therefore not really outliers. How might one go about accounting for the effects of those days?</li>
<li>Try the same analysis using a least-squares model. Which model is better for this data?</li>
</ul>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2014-07-18-are-we-getting-happier/are-we-getting-happier.jl"><code>are-we-getting-happier.jl</code></a> contains all the code in this post</li>
<li><a href="/files/2014-07-18-are-we-getting-happier/hedonometer.json"><code>hedonometer.json</code></a> contains the Hedonometer data as of May 16, 2025</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>🗺️ Preprocessing for Routing Problems - Part 2</title>
      <link>https://ryanjoneil.dev/posts/2014-06-27-preprocessing-for-routing-problems-part-2/</link>
      <pubDate>Fri, 27 Jun 2014 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2014-06-27-preprocessing-for-routing-problems-part-2/</guid>
      <description>Preprocessing techniques for dividing space based on closest points by driving distance.</description>
      <content:encoded><![CDATA[<p>In the <a href="../2014-05-28-preprocessing-for-routing-problems-part-1/">previous post</a>, we considered preprocessing for the vehicle routing problem where the vehicles have different starting locations. Our goal was to create potentially overlapping regions for the entire US which we could later use for route construction. We defined these regions using all 5-digit zip codes in the continental US for which one of our regional headquarters is the closest, or one of $n$ closest, headquarters in terms of Euclidean distance. The resulting regions gave us some flexibility in terms of how much redundancy we allow in our coverage of the country.</p>
<p>This post refines those regions, replacing the Euclidean distance with a more realistic metric: estimated travel time. Doing this should give us a better sense of how much space a given driver can actually cover. It should also divide the country up more equitably among our drivers.</p>
<p>Our approach here will be similar to that of the last post, but instead of ranking our headquarter-zip pairs by Euclidean distance, we&rsquo;ll rank them by estimated travel time. The catch is that, while the former is easy to compute using the <a href="https://cran.r-project.org/web/packages/SpatialTools/index.html"><code>SpatialTools</code></a> library, we have to request the latter from a third party. In this post, we&rsquo;ll use the MapQuest <a href="https://developer.mapquest.com/documentation/api/directions/route-matrix/post.html">Route Matrix</a>, since it provides estimates based on <a href="https://www.openstreetmap.org/">OpenStreetMap</a> data to us for free, and doesn&rsquo;t cap the number of requests we can make.</p>
<p>To do this we&rsquo;re going to need a lot of point estimates for location-to-location travel times. In fact, building a full data set for replacing our Euclidean distance ranks would require $37,341 \times 25 = 933,525$ travel time estimates. That&rsquo;s a bit prohibitive. The good news is we don&rsquo;t need to all the data points unless we generate 25 levels of redundancy. We can just request enough travel time estimates to make us reasonably certain that we&rsquo;ve got all the necessary data. In the last post we generated regions for 1, 2, and 3 levels of redundancy, so here we&rsquo;ll get travel times for the 10 closest headquarters to each zip code, and take the leap of faith that the closest 3 headquarters by travel time for each zip will be among the 10 closest by Euclidean distance.</p>
<p>Let&rsquo;s assume that you have just executed the code from the <a href="../2014-05-28-preprocessing-for-routing-problems-part-1/">last post</a> and have its variables in your current scope.<sup id="fnref:1"><a href="#fn:1" class="footnote-ref" role="doc-noteref">1</a></sup> First, we define some constants we&rsquo;re going to need in order to make MapQuest requests.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Define some constants for making requests to MapQuest and determining</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># when to save and what to request.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(RCurl)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(rjson)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(utils)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>MAPQUEST_API_KEY <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">&#39;YOUR KEY HERE&#39;</span>
</span></span><span style="display:flex;"><span>MAPQUEST_API_URL <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">&#39;http://www.mapquestapi.com/directions/v2/routematrix?key=%s&amp;json=%s&#39;</span>
</span></span><span style="display:flex;"><span>ZIPS_BETWEEN_SAVE <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">250</span>
</span></span><span style="display:flex;"><span>HQ_RANK_MIN <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">1</span>  <span style="color:#8b949e;font-style:italic"># Min/max distance ranks for time estimates</span>
</span></span><span style="display:flex;"><span>HQ_RANK_MAX <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">10</span>
</span></span></code></pre></div><p>Now we create a data frame to hold our HQ-to-zip travel estimates. The rows correspond to zip codes and the columns correspond to our headquarter locations. We initialize the data frame to contain no estimates and write it to a CSV file. Since it will take on the order of days for us to fill this file in, we&rsquo;re going to write it out and read it back in periodically. That way we can pick up where we left off by simply rerunning the code in case of an error or loss of network connectivity.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Write out a blank file containing our time estimates.</span>
</span></span><span style="display:flex;"><span>TIME_CSV_PATH <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">&#39;hqs_to_zips_time.csv&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> (<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">file.exists</span>(TIME_CSV_PATH)) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Clear out everything except row and column names.</span>
</span></span><span style="display:flex;"><span>    empty <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">as.data.frame</span>(<span style="color:#d2a8ff;font-weight:bold">matrix</span>(nrow<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(zips_deduped), ncol<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities)<span style="color:#a5d6ff">+1</span>))
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">names</span>(empty) <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">&#39;zip&#39;</span>, largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>name)
</span></span><span style="display:flex;"><span>    empty<span style="color:#ff7b72;font-weight:bold">$</span>zip <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>zip
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># This represents our current state.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">write.csv</span>(empty, TIME_CSV_PATH, row.names<span style="color:#ff7b72;font-weight:bold">=</span>F)
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Read in our current state in case we are starting over.</span>
</span></span><span style="display:flex;"><span>hqs_to_zips_time <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">read.csv</span>(TIME_CSV_PATH)
</span></span><span style="display:flex;"><span>hqs_to_zips_time<span style="color:#ff7b72;font-weight:bold">$</span>zip <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">sprintf</span>(<span style="color:#a5d6ff">&#39;%05d&#39;</span>, hqs_to_zips_time<span style="color:#ff7b72;font-weight:bold">$</span>zip)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Sanity check: If we have any times = 0, set them to NA so that we re-request them.</span>
</span></span><span style="display:flex;"><span>hqs_to_zips_time[hqs_to_zips_time <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#79c0ff">NA</span>
</span></span></code></pre></div><p>With that file created, we can start making requests to MapQuest&rsquo;s Route Matrix. For each zip code, we are going to request travel times for its 10 closest HQs. We&rsquo;ll save our time estimates data frame every 250 zip codes. Also, we&rsquo;re going to randomize the order of the zip codes so we fill out our data set more evenly as we go. That way we can generate maps during the process or otherwise inspect the data as we go.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Now we start requesting travel times from MapQuest.</span>
</span></span><span style="display:flex;"><span>requests_until_save <span style="color:#ff7b72;font-weight:bold">&lt;-</span> ZIPS_BETWEEN_SAVE
</span></span><span style="display:flex;"><span>col_count <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">ncol</span>(hqs_to_zips_time)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Randomize the zip code order so we fill in the map uniformly as we get more data.</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># This will enable us to check on our data over time and make sure it looks right.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (zip_idx <span style="color:#ff7b72">in</span> <span style="color:#d2a8ff;font-weight:bold">sample</span>(<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(zips_deduped))) {
</span></span><span style="display:flex;"><span>    z <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>zip[zip_idx]
</span></span><span style="display:flex;"><span>    z_lat <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[zip_idx]
</span></span><span style="display:flex;"><span>    z_lon <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[zip_idx]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Find PODs for this zip that are in the rank range.</span>
</span></span><span style="display:flex;"><span>    which_hqs <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">which</span>(
</span></span><span style="display:flex;"><span>        hqs_to_zips_rank[,zip_idx] <span style="color:#ff7b72;font-weight:bold">&gt;=</span> HQ_RANK_MIN <span style="color:#ff7b72;font-weight:bold">&amp;</span>
</span></span><span style="display:flex;"><span>        hqs_to_zips_rank[,zip_idx] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> HQ_RANK_MAX
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># We&#39;re only interested in records that aren&#39;t filled in yet.</span>
</span></span><span style="display:flex;"><span>    na_pods <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">is.na</span>(hqs_to_zips_time[zip_idx, which_hqs<span style="color:#a5d6ff">+1</span>])
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#d2a8ff;font-weight:bold">length</span>(hqs_to_zips_time[zip_idx,<span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">:</span>col_count][na_pods]) <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">1</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">next</span>
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Request this block of PODs and fill them all in.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">print</span>(<span style="color:#d2a8ff;font-weight:bold">sprintf</span>(<span style="color:#a5d6ff">&#39;requesting: zip=%s rank=[%d-%d]&#39;</span>, z, HQ_RANK_MIN, HQ_RANK_MAX))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Construct a comma-delimited string of lat/lons containing the locations of our</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># HQs We will use this for our MapQuest requests below: for each zip code, we</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># make one request for its distance to every HQ in our range.</span>
</span></span><span style="display:flex;"><span>    hq_locations <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">paste</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#d2a8ff;font-weight:bold">sprintf</span>(<span style="color:#a5d6ff">&#34;&#39;%f,%f&#39;&#34;</span>, largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat[which_hqs], largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long[which_hqs]),
</span></span><span style="display:flex;"><span>        collapse <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;, &#39;</span>
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># TODO: make sure we are requesting from location 1 to 2:n only</span>
</span></span><span style="display:flex;"><span>    request_json <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">URLencode</span>(<span style="color:#d2a8ff;font-weight:bold">sprintf</span>(
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;{allToAll: false, locations: [&#39;%f,%f&#39;, %s]}&#34;</span>,
</span></span><span style="display:flex;"><span>        z_lat,
</span></span><span style="display:flex;"><span>        z_lon,
</span></span><span style="display:flex;"><span>        hq_locations
</span></span><span style="display:flex;"><span>    ))
</span></span><span style="display:flex;"><span>    url <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">sprintf</span>(MAPQUEST_API_URL, MAPQUEST_API_KEY, request_json)
</span></span><span style="display:flex;"><span>    result <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">fromJSON</span>(<span style="color:#d2a8ff;font-weight:bold">getURL</span>(url))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># If we get back 0s, they should be NA. Otherwise they&#39;ll mess up our</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># rankings and region drawing later.</span>
</span></span><span style="display:flex;"><span>    result<span style="color:#ff7b72;font-weight:bold">$</span>time[result<span style="color:#ff7b72;font-weight:bold">$</span>time <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#79c0ff">NA</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    hqs_to_zips_time[zip_idx, which_hqs<span style="color:#a5d6ff">+1</span>] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> result<span style="color:#ff7b72;font-weight:bold">$</span>time[2<span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">length</span>(result<span style="color:#ff7b72;font-weight:bold">$</span>distance)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># See if we should save our current state.</span>
</span></span><span style="display:flex;"><span>    requests_until_save <span style="color:#ff7b72;font-weight:bold">&lt;-</span> requests_until_save <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (requests_until_save <span style="color:#ff7b72;font-weight:bold">&lt;</span> <span style="color:#a5d6ff">1</span>) {
</span></span><span style="display:flex;"><span>        <span style="color:#d2a8ff;font-weight:bold">print</span>(<span style="color:#a5d6ff">&#39;saving current state&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#d2a8ff;font-weight:bold">write.csv</span>(hqs_to_zips_time, TIME_CSV_PATH, row.names<span style="color:#ff7b72;font-weight:bold">=</span>F)
</span></span><span style="display:flex;"><span>        requests_until_save <span style="color:#ff7b72;font-weight:bold">&lt;-</span> ZIPS_BETWEEN_SAVE
</span></span><span style="display:flex;"><span>    }
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Final save once we are done.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">write.csv</span>(hqs_to_zips_time, TIME_CSV_PATH, row.names<span style="color:#ff7b72;font-weight:bold">=</span>F)
</span></span></code></pre></div><p>Now we generate our ranks based on travel time instead of distance. We have to be a bit more careful this time, as we might have incomplete data. We don&rsquo;t want pairs with travel time of NA showing up in the rankings.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Rank HQs by their distance to each unique zip code location.</span>
</span></span><span style="display:flex;"><span>hqs_to_zips_rank2 <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">matrix</span>(nrow<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities), ncol<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(zips_deduped))
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(zips_deduped)) {
</span></span><span style="display:flex;"><span>    not_na <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">is.na</span>(hqs_to_zips_time[i,<span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">ncol</span>(hqs_to_zips_time)])
</span></span><span style="display:flex;"><span>    hqs_to_zips_rank2[not_na,i] <span style="color:#ff7b72;font-weight:bold">&lt;-</span>
</span></span><span style="display:flex;"><span>        <span style="color:#d2a8ff;font-weight:bold">rank</span>(hqs_to_zips_time[i,<span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">ncol</span>(hqs_to_zips_time)][not_na], ties.method<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;first&#39;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We build our map for the Dallas, TX headquarter the same way as before.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Now we draw regions for which Dallas is one of the closest 3 HQs by time.</span>
</span></span><span style="display:flex;"><span>hq_idx <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">which</span>(largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>name <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;Dallas TX&#39;</span>)
</span></span><span style="display:flex;"><span>redundancy_levels <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>fill_alpha <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">0.15</span>, <span style="color:#a5d6ff">0.30</span>, <span style="color:#a5d6ff">0.45</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">map</span>(<span style="color:#a5d6ff">&#39;state&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">length</span>(redundancy_levels)) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Find every zip for which this HQ is within n in time rank.</span>
</span></span><span style="display:flex;"><span>    within_n <span style="color:#ff7b72;font-weight:bold">&lt;-</span> hqs_to_zips_rank2[hq_idx,] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> redundancy_levels[i]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Convex hull of zip code points.</span>
</span></span><span style="display:flex;"><span>    hull_order <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">chull</span>(
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_n],
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_n]
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    hull_x <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_n][hull_order]
</span></span><span style="display:flex;"><span>    hull_y <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_n][hull_order]
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">polygon</span>(hull_x, hull_y, border<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;blue&#39;</span>, col<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, fill_alpha[i]))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># The other HQs.</span>
</span></span><span style="display:flex;"><span>other_hqs <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities) <span style="color:#ff7b72;font-weight:bold">!=</span> hq_idx
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long[other_hqs],
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat[other_hqs],
</span></span><span style="display:flex;"><span>    pch <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">21</span>,
</span></span><span style="display:flex;"><span>    bg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0.4</span>, <span style="color:#a5d6ff">0.4</span>, <span style="color:#a5d6ff">0.4</span>, <span style="color:#a5d6ff">0.6</span>),
</span></span><span style="display:flex;"><span>    col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>    cex <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># This HQ.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long[hq_idx],
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat[hq_idx],
</span></span><span style="display:flex;"><span>    pch <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">21</span>,
</span></span><span style="display:flex;"><span>    bg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">.85</span>),
</span></span><span style="display:flex;"><span>    col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>    cex <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>This shows the regions for which Dallas is among the closest headquarters for 1, 2, and 3 level of redundancy. Compare this map to the one from the previous post, and you&rsquo;ll see that it conforms better to the highway system. For instance, it takes into account I-20 which moves east and west across Texas, instead of pushing up into the Dakotas.</p>
<p><img alt="Travel time-based regions for Dallas, TX" loading="lazy" src="/files/2014-06-27-preprocessing-for-routing-problems-part-2/plot1.png#center"></p>
<p>And now our map of the US, showing the regions for each HQ as the set of zip codes for which it is the closest.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Map of regions where every zip is served only by its closest HQ.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">map</span>(<span style="color:#a5d6ff">&#39;usa&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (hq_idx <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities)) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Find every zip for which this HQ is the closest.</span>
</span></span><span style="display:flex;"><span>    within_1 <span style="color:#ff7b72;font-weight:bold">&lt;-</span> hqs_to_zips_rank2[hq_idx,] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>    within_1<span style="color:#d2a8ff;font-weight:bold">[is.na</span>(within_1)] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> F
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Convex hull of zip code points.</span>
</span></span><span style="display:flex;"><span>    hull_order <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">chull</span>(
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_1],
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_1]
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    hull_x <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_1][hull_order]
</span></span><span style="display:flex;"><span>    hull_y <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_1][hull_order]
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">polygon</span>(
</span></span><span style="display:flex;"><span>        hull_x,
</span></span><span style="display:flex;"><span>        hull_y,
</span></span><span style="display:flex;"><span>        border <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>        col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0.25</span>)
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># All HQs</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long,
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat,
</span></span><span style="display:flex;"><span>    pch <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">21</span>,
</span></span><span style="display:flex;"><span>    bg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">.75</span>),
</span></span><span style="display:flex;"><span>    col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>    cex <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>This gives us our new map. If we compare this with the original, it should better reflect the topology of the highway system. It also looks a bit less jagged.</p>
<p><img alt="All regions with redundancy level of 1" loading="lazy" src="/files/2014-06-27-preprocessing-for-routing-problems-part-2/plot2.png#center"></p>
<p>Exercises for the reader:</p>
<ul>
<li>Some of these regions overlap, even though they are supposed to be only composed of zip codes for which a given HQ is the closest. Why is that?</li>
<li>Say we want to limit our driver to given maximum travel times. Based on our data from MapQuest, draw concentric circles representing approximate 3, 5, and 7 hour travel time regions.</li>
</ul>
<div class="footnotes" role="doc-endnotes">
<hr>
<ol>
<li id="fn:1">
<p>If you need it, you can find that code <a href="https://gist.github.com/ryanjoneil/adfb2449370902f4ee3d">here</a>.&#160;<a href="#fnref:1" class="footnote-backref" role="doc-backlink">&#x21a9;&#xfe0e;</a></p>
</li>
</ol>
</div>
]]></content:encoded>
    </item>
    <item>
      <title>🗺️ Preprocessing for Routing Problems - Part 1</title>
      <link>https://ryanjoneil.dev/posts/2014-05-28-preprocessing-for-routing-problems-part-1/</link>
      <pubDate>Wed, 28 May 2014 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2014-05-28-preprocessing-for-routing-problems-part-1/</guid>
      <description>Preprocessing techniques for splitting space into regions which are closest to a set of points.</description>
      <content:encoded><![CDATA[<p>Consider an instance of the <a href="https://en.wikipedia.org/wiki/Vehicle_routing_problem">vehicle routing problem</a> in which we have drivers that are geographically distributed, each in a unique location. Our goal is to deliver goods or services to a set of destinations at the lowest cost. It does not matter to our customers which driver goes to which destination, so long as the deliveries are made.</p>
<p>One can think of this problem as a collection of <a href="https://en.wikipedia.org/wiki/Travelling_salesman_problem">travelling salesman problems</a>, where there are multiple salespeople in different locations and a shared set of destinations. We attempt to find the minimum cost schedule for all salespeople that visits all destinations, where each salesman can technically go anywhere.</p>
<p>We believe that sending a driver farther will result in increased cost. But, given a particularly good tour, we might do that anyway. On the other hand, there are plenty of assignments we would never consider. It would be madness to send a driver from Los Angeles to New York if we already have another person stationed near there. Thus there are a large number of scenarios that may be possible, but that we will never pursue.</p>
<p>Our ultimate goal is to construct a model that finds an optimal (or near-optimal) schedule. Before we get to that, we have a bit of preprocessing to do. We would like to create regions for our drivers that make some bit of sense, balancing constraints on travel time with redundant coverage of our customers. Once we have these regions, we will know where we can allow our drivers to go in the final schedule.</p>
<p>Let&rsquo;s get started in R. We&rsquo;ll assume that we have drivers stationed at our regional headquarters in the 25 largest US cities by population. We assume that every possible customer address will be in some five digit zip code in the continental US. We pull this information out of the standard R data sets and pare down to only unique locations, fixing a couple errors in the data along the way.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(datasets)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(zipcode)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">data</span>(zipcode)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Alexandria, VA is not in Normandy, France.</span>
</span></span><span style="display:flex;"><span>zipcode[zipcode<span style="color:#ff7b72;font-weight:bold">$</span>zip<span style="color:#ff7b72;font-weight:bold">==</span><span style="color:#a5d6ff">&#39;22350&#39;</span>, <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">&#39;latitude&#39;</span>, <span style="color:#a5d6ff">&#39;longitude&#39;</span>)] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">38.863930</span>, <span style="color:#a5d6ff">-77.055547</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># New York City, NY is not in Kyrgyzstan.</span>
</span></span><span style="display:flex;"><span>zipcode<span style="color:#ff7b72;font-weight:bold">$</span>longitude[zipcode<span style="color:#ff7b72;font-weight:bold">$</span>zip<span style="color:#ff7b72;font-weight:bold">==</span><span style="color:#a5d6ff">&#39;10200&#39;</span>] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#ff7b72;font-weight:bold">-</span>zipcode<span style="color:#ff7b72;font-weight:bold">$</span>longitude[zipcode<span style="color:#ff7b72;font-weight:bold">$</span>zip<span style="color:#ff7b72;font-weight:bold">==</span><span style="color:#a5d6ff">&#39;10200&#39;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Pare down to zip codes in the continental US.</span>
</span></span><span style="display:flex;"><span>states_continental <span style="color:#ff7b72;font-weight:bold">&lt;-</span> state.abb[<span style="color:#ff7b72;font-weight:bold">!</span>(state.abb <span style="color:#ff7b72;font-weight:bold">%in%</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">&#39;AK&#39;</span>, <span style="color:#a5d6ff">&#39;HI&#39;</span>))]
</span></span><span style="display:flex;"><span>zips_continental <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">subset</span>(zipcode, state <span style="color:#ff7b72;font-weight:bold">%in%</span> states_continental)
</span></span><span style="display:flex;"><span>zips_deduped <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_continental[<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">duplicated</span>(zips_continental[, <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">&#39;latitude&#39;</span>, <span style="color:#a5d6ff">&#39;longitude&#39;</span>)]), ]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Geographic information for top 25 cities in the country.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(maps)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">data</span>(us.cities)
</span></span><span style="display:flex;"><span>largest_cities <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">subset</span>(
</span></span><span style="display:flex;"><span>    us.cities<span style="color:#d2a8ff;font-weight:bold">[order</span>(us.cities<span style="color:#ff7b72;font-weight:bold">$</span>pop, decreasing<span style="color:#ff7b72;font-weight:bold">=</span>T),][1<span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#a5d6ff">25</span>,],
</span></span><span style="display:flex;"><span>    select <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">&#39;name&#39;</span>, <span style="color:#a5d6ff">&#39;lat&#39;</span>, <span style="color:#a5d6ff">&#39;long&#39;</span>)
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>With this information we can get some sense of what we&rsquo;re up against. We generate a map off all the zip code locations in blue and our driver locations in red.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Plot our corporate headquarters and every unique zip code location.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">map</span>(<span style="color:#a5d6ff">&#39;state&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude, zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude, pch<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">21</span>, col<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">.5</span>), cex<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0.1</span>)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long, largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat, pch<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">21</span>, bg<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">.75</span>), col<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;black&#39;</span>, cex<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1.5</span>)
</span></span></code></pre></div><p><img alt="Zip code and driver locations" loading="lazy" src="/files/2014-05-28-preprocessing-for-routing-problems-part-1/plot1.png#center"></p>
<p>So how do we go about assigning zip codes to our drivers? One option is to draw circles of a given radius around our drivers and increase that radius until we have the coverage we need.</p>
<p><img alt="Radius-based regions" loading="lazy" src="/files/2014-05-28-preprocessing-for-routing-problems-part-1/plot2.png#center"></p>
<p>On second thought, that doesn&rsquo;t work so well. By the time we have large enough radius, there will be so much overlap the assignments won&rsquo;t make much sense. It would be better if we started by assigning each zip code to the driver that is physically closest. We could then start introducing redundancy into our data by adding the second closest driver, and so on.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Euclidean distance from each HQ to each zip code.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">library</span>(SpatialTools)
</span></span><span style="display:flex;"><span>zips_to_hqs_dist <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">dist2</span>(
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">matrix</span>(<span style="color:#d2a8ff;font-weight:bold">c</span>(zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude, zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude), ncol<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">2</span>),
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">matrix</span>(<span style="color:#d2a8ff;font-weight:bold">c</span>(largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long, largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat), ncol<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">2</span>)
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Rank HQs by their distance to each unique zip code location.</span>
</span></span><span style="display:flex;"><span>hqs_to_zips_rank <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">matrix</span>(nrow<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities), ncol<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(zips_deduped))
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(zips_deduped)) {
</span></span><span style="display:flex;"><span>    hqs_to_zips_rank[,i] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">rank</span>(zips_to_hqs_dist[i,], ties.method<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;first&#39;</span>)
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Let&rsquo;s see what this looks like on the map. The following shows what the region for the Dallas, TX driver would be if she were only allowed to visit zip codes for which she is the closest, second closest, and third closest. We map these as polygons using the convex hull of their respective zip code locations.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Now we draw regions for which Dallas is one of the closest 3 HQs.</span>
</span></span><span style="display:flex;"><span>hq_idx <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">which</span>(largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>name <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;Dallas TX&#39;</span>)
</span></span><span style="display:flex;"><span>redundancy_levels <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>fill_alpha <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">0.15</span>, <span style="color:#a5d6ff">0.30</span>, <span style="color:#a5d6ff">0.45</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">map</span>(<span style="color:#a5d6ff">&#39;state&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">length</span>(redundancy_levels)) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Find every zip for which this HQ is within n in distance rank.</span>
</span></span><span style="display:flex;"><span>    within_n <span style="color:#ff7b72;font-weight:bold">&lt;-</span> hqs_to_zips_rank[hq_idx,] <span style="color:#ff7b72;font-weight:bold">&lt;=</span> redundancy_levels[i]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Convex hull of zip code points.</span>
</span></span><span style="display:flex;"><span>    hull_order <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">chull</span>(
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_n],
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_n]
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    hull_x <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_n][hull_order]
</span></span><span style="display:flex;"><span>    hull_y <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_n][hull_order]
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">polygon</span>(hull_x, hull_y, border<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;blue&#39;</span>, col<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, fill_alpha[i]))
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># The other HQs.</span>
</span></span><span style="display:flex;"><span>other_hqs <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities) <span style="color:#ff7b72;font-weight:bold">!=</span> hq_idx
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long[other_hqs],
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat[other_hqs],
</span></span><span style="display:flex;"><span>    pch <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">21</span>,
</span></span><span style="display:flex;"><span>    bg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0.4</span>, <span style="color:#a5d6ff">0.4</span>, <span style="color:#a5d6ff">0.4</span>, <span style="color:#a5d6ff">0.6</span>),
</span></span><span style="display:flex;"><span>    col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>    cex <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># This HQ.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long[hq_idx],
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat[hq_idx],
</span></span><span style="display:flex;"><span>    pch <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">21</span>,
</span></span><span style="display:flex;"><span>    bg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">.85</span>),
</span></span><span style="display:flex;"><span>    col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>    cex <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p><img alt="Euclidean distance-based regions for Dallas, TX" loading="lazy" src="/files/2014-05-28-preprocessing-for-routing-problems-part-1/plot3.png#center"></p>
<p>This makes a bit more sense. If we enforce a redundancy level of 1, then every zip code has exactly one person assigned to it. As we increase that redundancy level, we have more options in terms of driver assignment. And our optimization model will grow correspondingly in size.</p>
<p>The following produces a map of all our regions where each zip code is served only by its closest driver.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Map of regions where every zip is served only by its closest HQ.</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">map</span>(<span style="color:#a5d6ff">&#39;usa&#39;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (hq_idx <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">nrow</span>(largest_cities)) {
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Find every zip for which this HQ is the closest.</span>
</span></span><span style="display:flex;"><span>    within_1 <span style="color:#ff7b72;font-weight:bold">&lt;-</span> hqs_to_zips_rank[hq_idx,] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>    within_1<span style="color:#d2a8ff;font-weight:bold">[is.na</span>(within_1)] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> F
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Convex hull of zip code points.</span>
</span></span><span style="display:flex;"><span>    hull_order <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">chull</span>(
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_1],
</span></span><span style="display:flex;"><span>        zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_1]
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    hull_x <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>longitude[within_1][hull_order]
</span></span><span style="display:flex;"><span>    hull_y <span style="color:#ff7b72;font-weight:bold">&lt;-</span> zips_deduped<span style="color:#ff7b72;font-weight:bold">$</span>latitude[within_1][hull_order]
</span></span><span style="display:flex;"><span>    <span style="color:#d2a8ff;font-weight:bold">polygon</span>(
</span></span><span style="display:flex;"><span>        hull_x,
</span></span><span style="display:flex;"><span>        hull_y,
</span></span><span style="display:flex;"><span>        border <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>        col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0.25</span>)
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># All HQs</span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">points</span>(
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>long,
</span></span><span style="display:flex;"><span>    largest_cities<span style="color:#ff7b72;font-weight:bold">$</span>lat,
</span></span><span style="display:flex;"><span>    pch <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">21</span>,
</span></span><span style="display:flex;"><span>    bg <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#d2a8ff;font-weight:bold">rgb</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">.75</span>),
</span></span><span style="display:flex;"><span>    col <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;black&#39;</span>,
</span></span><span style="display:flex;"><span>    cex <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.5</span>
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p><img alt="All regions with redundancy level of 1" loading="lazy" src="/files/2014-05-28-preprocessing-for-routing-problems-part-1/plot4.png#center"></p>
<p>This is a good start. Our preprocessing step gives us a reasonable level of control over the assignments of drivers before we begin optimizing. So what&rsquo;s missing?</p>
<p>One immediately apparent failure is that these regions are based on Euclidean distance. Travel time is not a simple function of that. It would be much better if we could create regions using estimated time, drawing them based on topology of the highway system. We&rsquo;ll explore techniques for doing so in the next post.</p>
]]></content:encoded>
    </item>
    <item>
      <title>⭕ Chebyshev Centers of Polygons with Gurobi</title>
      <link>https://ryanjoneil.dev/posts/2014-02-03-chebyshev-centers-of-polygons-with-gurobi/</link>
      <pubDate>Mon, 03 Feb 2014 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2014-02-03-chebyshev-centers-of-polygons-with-gurobi/</guid>
      <description>Finding the maximum area inscribed circle inside a polygon.</description>
      <content:encoded><![CDATA[<p><em>Note: This post was written before Gurobi supported nonlinear optimization. It has been updated to work with Python 3.</em></p>
<p>A common problem in handling geometric data is determining the center of a given polygon. This is not quite so easy as it sounds as there is not a single definition of center that makes sense in all cases. For instance, sometimes computing the center of a polygon&rsquo;s bounding box may be sufficient. In some instances this may give a point on an edge (consider a right triangle). If the given polygon is non-convex, that point may not even be inside or on its boundary.</p>
<p>This post looks at computing Chebyshev centers for arbitrary convex polygons. We employ <a href="https://cvxopt.org/examples/book/centers.html">essentially the same model</a> as in Boyd &amp; Vandenberghe&rsquo;s <a href="https://www.stanford.edu/~boyd/cvxbook/">Convex Optimization</a> text, but using Gurobi instead of CVXOPT.</p>
<p>Consider a polygon defined by the intersection of a finite number of half-spaces, $Au \le b$. We assume we are given the set of vertices, $V$, in clockwise order around the polygon. $E$ is the set of edges connecting these vertices. Each edge in $E$ defines a boundary of the half-space $a_i^\intercal u \le b_i$</p>
<p><img alt="Intersection of half-spaces" loading="lazy" src="/files/2014-02-03-chebyshev-centers-of-polygons-with-gurobi/intersection-of-half-spaces.svg#center"></p>
<p>$$
V = {(1,1), (2,5), (5,4), (6,2), (4,1)}\\
E = {((1,1),(2,5)), ((2,5),(5,4)), ((5,4),(6,2)), ((6,2),(4,1)), ((4,1),(1,1))}
$$</p>
<p>The Chebyshev center of this polygon is the center point $(x, y)$ of the maximum radius inscribed circle. That is, if we can find the largest circle that will fit inside our polygon without going outside its boundary, its center is the point we are looking for. Our decision variables are the center $(x, y)$ and the maximum inscribed radius, $r$.</p>
<p>In order to do this, we consider the edges independently. The long line segment below shows an arbitrary edge, $a_i^\intercal u \le b_i$. The short line connected to it is orthogonal in the direction $a$. $(x, y)$ satisfies the inequality.</p>
<p><img alt="Inequality" loading="lazy" src="/files/2014-02-03-chebyshev-centers-of-polygons-with-gurobi/inequality.svg#center"></p>
<p>The shortest distance from $(x, y)$ will be in the direction of $a$. We&rsquo;ll call this distance $r$. If we were to move the edge so it had the same slope but went through $(x, y)$, its distance from $a_i^\intercal u = b_i$ would be $r||a_i||_2$. Thus we can add a constraint of the form $a_i&rsquo;u + r||a_i||_2 \le b_i$ for each edge and maximize the value of $r$ as our objective function.</p>
<p>$$
\begin{align*}
&amp; \text{max}  &amp;&amp; r \\
&amp; \text{s.t.} &amp;&amp; (y_i-y_j)x + (x_j-x_i)y + r\sqrt{(x_j-x_i)^2 + (y_j-y_i)^2} \le (y_i-y_j)x_i + (x_j-x_i)y_i \\
&amp;             &amp;&amp; \quad \forall \quad ((x_i,y_i), (x_j,y_j)) \in E \\
\end{align*}
$$</p>
<p>As this is linear, we can solve it using any LP solver. The following code does so with Gurobi.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">gurobipy</span> <span style="color:#ff7b72">import</span> Model, GRB
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">math</span> <span style="color:#ff7b72">import</span> sqrt
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>vertices <span style="color:#ff7b72;font-weight:bold">=</span> [(<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">1</span>), (<span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">5</span>), (<span style="color:#a5d6ff">5</span>,<span style="color:#a5d6ff">4</span>), (<span style="color:#a5d6ff">6</span>,<span style="color:#a5d6ff">2</span>), (<span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">1</span>)]
</span></span><span style="display:flex;"><span>edges <span style="color:#ff7b72;font-weight:bold">=</span> zip(vertices, vertices[<span style="color:#a5d6ff">1</span>:] <span style="color:#ff7b72;font-weight:bold">+</span> [vertices[<span style="color:#a5d6ff">0</span>]])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m <span style="color:#ff7b72;font-weight:bold">=</span> Model()
</span></span><span style="display:flex;"><span>r <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVar()
</span></span><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(lb<span style="color:#ff7b72;font-weight:bold">=-</span>GRB<span style="color:#ff7b72;font-weight:bold">.</span>INFINITY)
</span></span><span style="display:flex;"><span>y <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(lb<span style="color:#ff7b72;font-weight:bold">=-</span>GRB<span style="color:#ff7b72;font-weight:bold">.</span>INFINITY)
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>update()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (x1, y1), (x2, y2) <span style="color:#ff7b72;font-weight:bold">in</span> edges:
</span></span><span style="display:flex;"><span>    dx <span style="color:#ff7b72;font-weight:bold">=</span> x2 <span style="color:#ff7b72;font-weight:bold">-</span> x1
</span></span><span style="display:flex;"><span>    dy <span style="color:#ff7b72;font-weight:bold">=</span> y2 <span style="color:#ff7b72;font-weight:bold">-</span> y1
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr((dx<span style="color:#ff7b72;font-weight:bold">*</span>y <span style="color:#ff7b72;font-weight:bold">-</span> dy<span style="color:#ff7b72;font-weight:bold">*</span>x) <span style="color:#ff7b72;font-weight:bold">+</span> (r <span style="color:#ff7b72;font-weight:bold">*</span> sqrt(dx<span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span> <span style="color:#ff7b72;font-weight:bold">+</span> dy<span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>)) <span style="color:#ff7b72;font-weight:bold">&lt;=</span> dx<span style="color:#ff7b72;font-weight:bold">*</span>y1 <span style="color:#ff7b72;font-weight:bold">-</span> dy<span style="color:#ff7b72;font-weight:bold">*</span>x1)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>setObjective(r, GRB<span style="color:#ff7b72;font-weight:bold">.</span>MAXIMIZE)
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>optimize()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;r = </span><span style="color:#a5d6ff">%.04f</span><span style="color:#a5d6ff">&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> r<span style="color:#ff7b72;font-weight:bold">.</span>x)
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;(x, y) = (</span><span style="color:#a5d6ff">%.04f</span><span style="color:#a5d6ff">, </span><span style="color:#a5d6ff">%.04f</span><span style="color:#a5d6ff">)&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (x<span style="color:#ff7b72;font-weight:bold">.</span>x, y<span style="color:#ff7b72;font-weight:bold">.</span>x))
</span></span></code></pre></div><p>The model output shows our center and its maximum inscribed radius.</p>
<p>$$
r = 1.7466\\
(x, y) = (3.2370, 2.7466)
$$</p>
<p><img alt="Center" loading="lazy" src="/files/2014-02-03-chebyshev-centers-of-polygons-with-gurobi/center.svg#center"></p>
<p>Question for the reader: in certain circumstances, such as rectangles, the Chebyshev center is ambiguous. How might one get around this ambiguity?</p>
]]></content:encoded>
    </item>
    <item>
      <title>✂️ Network Splitting</title>
      <link>https://ryanjoneil.dev/posts/2013-07-29-network-splitting/</link>
      <pubDate>Mon, 29 Jul 2013 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2013-07-29-network-splitting/</guid>
      <description>Splitting large networks into fully connected sub-networks. Also, Las Vegas!</description>
      <content:encoded><![CDATA[<p><em>Note: A reader pointed out that <a href="https://en.wikipedia.org/wiki/Disjoint-set_data_structure">Union-Find</a> is a very efficient way to accomplish this task. Start there if you have the same problem!</em></p>
<p>Last week, Paul Rubin wrote an excellent post on <a href="https://orinanobworld.blogspot.com/2013/07/extracting-connected-graph.html">Extracting a Connected Graph</a> from an existing graph. Lately I&rsquo;ve been performing related functions on data from <a href="https://www.openstreetmap.org/">OpenStreetMap</a>, though without access to a solver. In my case I&rsquo;m taking in arbitrary network data and splitting it into disconnected sub-networks. I thought it might be a good case study to show an algorithmic way doing this and  some of the performance issues I ran into.</p>
<p>A small example can be seen below. This shows a road network around the Las Vegas strip. There is one main (weakly) connected network in black. The roads highlighted in red are disconnected from the main network. We want code that will split these into connected sub-networks.</p>
<p><img alt="Disconnected sub-networks" loading="lazy" src="/files/2013-07-29-network-splitting/disconnected-subnetworks.png#center"></p>
<p>Say we have data that looks like the following. Instead of nodes, the numbers in quotes represent edges. Think of these as streets.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-json" data-lang="json"><span style="display:flex;"><span>{
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;0&#34;</span>: [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;1&#34;</span>: [<span style="color:#a5d6ff">9248</span>, <span style="color:#a5d6ff">9249</span>, <span style="color:#a5d6ff">9250</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;2&#34;</span>: [<span style="color:#a5d6ff">589</span>, <span style="color:#a5d6ff">9665</span>, <span style="color:#a5d6ff">9667</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;3&#34;</span>: [<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">6</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;4&#34;</span>: [<span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">6</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;5&#34;</span>: [<span style="color:#a5d6ff">588</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#7ee787">&#34;6&#34;</span>: [<span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    <span style="color:#f85149">...</span>
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>Our basic strategy is the following:</p>
<ol>
<li>Start with every edge alone in its own subnetwork.</li>
<li>For each connection, merge the networks of the source and destination edges.</li>
</ol>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#!/usr/bin/env python3</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">json</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">sys</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">class</span> <span style="color:#f0883e;font-weight:bold">hset</span>(set):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;A hashable set. Note that it only hashes by the pointer, and not by the elements.&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">__hash__</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> hash(id(self))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">__cmp__</span>(self, other):
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> cmp(id(self), id(other))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>        inputfile <span style="color:#ff7b72;font-weight:bold">=</span> sys<span style="color:#ff7b72;font-weight:bold">.</span>argv[<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">except</span>:
</span></span><span style="display:flex;"><span>        print <span style="color:#a5d6ff">&#39;usage: </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff"> network.json&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> sys<span style="color:#ff7b72;font-weight:bold">.</span>argv[<span style="color:#a5d6ff">0</span>]
</span></span><span style="display:flex;"><span>        sys<span style="color:#ff7b72;font-weight:bold">.</span>exit()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), <span style="color:#a5d6ff">&#39;parsing json input&#39;</span>)
</span></span><span style="display:flex;"><span>    connections <span style="color:#ff7b72;font-weight:bold">=</span> json<span style="color:#ff7b72;font-weight:bold">.</span>load(open(inputfile))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    edge_to_net <span style="color:#ff7b72;font-weight:bold">=</span> {} <span style="color:#8b949e;font-style:italic"># Edge ID -&gt; set([edges that are in the same network])</span>
</span></span><span style="display:flex;"><span>    nets <span style="color:#ff7b72;font-weight:bold">=</span> set()     <span style="color:#8b949e;font-style:italic"># Set of known networks</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), <span style="color:#a5d6ff">&#39;detecting disconnected subgraphs&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> i, (from_edge, to_set) <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(connections<span style="color:#ff7b72;font-weight:bold">.</span>iteritems()):
</span></span><span style="display:flex;"><span>        from_edge <span style="color:#ff7b72;font-weight:bold">=</span> int(from_edge)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>            from_net <span style="color:#ff7b72;font-weight:bold">=</span> edge_to_net[from_edge]
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">except</span> <span style="color:#f0883e;font-weight:bold">KeyError</span>:
</span></span><span style="display:flex;"><span>            from_net <span style="color:#ff7b72;font-weight:bold">=</span> edge_to_net[from_edge] <span style="color:#ff7b72;font-weight:bold">=</span> hset([from_edge])
</span></span><span style="display:flex;"><span>            nets<span style="color:#ff7b72;font-weight:bold">.</span>add(from_net)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> (i<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>) <span style="color:#ff7b72;font-weight:bold">%</span> (<span style="color:#a5d6ff">25</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">1000</span>):
</span></span><span style="display:flex;"><span>            print time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), <span style="color:#a5d6ff">&#39;</span><span style="color:#a5d6ff">%d</span><span style="color:#a5d6ff"> edges processed / </span><span style="color:#a5d6ff">%d</span><span style="color:#a5d6ff"> current subnets&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (i<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>, len(nets))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> to <span style="color:#ff7b72;font-weight:bold">in</span> to_set:
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>                to_net <span style="color:#ff7b72;font-weight:bold">=</span> edge_to_net[to]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic"># If we get here, merge the to_net into the from_net.</span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">if</span> to_net <span style="color:#ff7b72;font-weight:bold">is</span> <span style="color:#ff7b72;font-weight:bold">not</span> from_net:
</span></span><span style="display:flex;"><span>                    to_net<span style="color:#ff7b72;font-weight:bold">.</span>update(from_net)
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">for</span> e <span style="color:#ff7b72;font-weight:bold">in</span> from_net:
</span></span><span style="display:flex;"><span>                        edge_to_net[e] <span style="color:#ff7b72;font-weight:bold">=</span> to_net
</span></span><span style="display:flex;"><span>                    nets<span style="color:#ff7b72;font-weight:bold">.</span>remove(from_net)
</span></span><span style="display:flex;"><span>                    from_net <span style="color:#ff7b72;font-weight:bold">=</span> to_net
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">except</span> <span style="color:#f0883e;font-weight:bold">KeyError</span>:
</span></span><span style="display:flex;"><span>                from_net<span style="color:#ff7b72;font-weight:bold">.</span>add(to)
</span></span><span style="display:flex;"><span>                edge_to_net[to] <span style="color:#ff7b72;font-weight:bold">=</span> from_net
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), len(nets), <span style="color:#a5d6ff">&#39;subnets found&#39;</span>)
</span></span></code></pre></div><p>We run this against the network pictured above and it works reasonably quickly, finishing in about 7 seconds:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Mon Jul 29 12:22:38 2013 parsing json input
</span></span><span style="display:flex;"><span>Mon Jul 29 12:22:38 2013 detecting disconnected subgraphs
</span></span><span style="display:flex;"><span>Mon Jul 29 12:22:38 2013 25000 edges processed / 1970 current subnets
</span></span><span style="display:flex;"><span>Mon Jul 29 12:22:44 2013 50000 edges processed / 124 current subnets
</span></span><span style="display:flex;"><span>Mon Jul 29 12:22:45 2013 60 subnets found
</span></span></code></pre></div><p>However, when run against a road network for an entire city, the process continues for several hours. What is the issue?&lt;</p>
<p>The inefficiency occurs from lines 46 to 50. In this we are frequently removing references to every element in a large set. Instead, it would be better to remove as few references as possible. Therefore, instead of merging <code>from_net</code> into <code>to_net</code>, we will determine which network is the smaller of the two and marge that one into the larger one. Note that this does not necessarily change the worst case time complexity of the algorithm, but it should make the code fast enough to be useful. The new version appears below.`</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># !/usr/bin/env python</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">json</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">sys</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">class</span> <span style="color:#f0883e;font-weight:bold">hset</span>(set):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;A hashable set. Note that it only hashes by the pointer, and not by the elements.&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">__hash__</span>(self):
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> hash(id(self))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">__cmp__</span>(self, other):
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> cmp(id(self), id(other))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>        inputfile <span style="color:#ff7b72;font-weight:bold">=</span> sys<span style="color:#ff7b72;font-weight:bold">.</span>argv[<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">except</span>:
</span></span><span style="display:flex;"><span>        print(<span style="color:#a5d6ff">&#39;usage: </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff"> network.json&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> sys<span style="color:#ff7b72;font-weight:bold">.</span>argv[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>        sys<span style="color:#ff7b72;font-weight:bold">.</span>exit()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), <span style="color:#a5d6ff">&#39;parsing json input&#39;</span>)
</span></span><span style="display:flex;"><span>    connections <span style="color:#ff7b72;font-weight:bold">=</span> json<span style="color:#ff7b72;font-weight:bold">.</span>load(open(inputfile))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    edge_to_net <span style="color:#ff7b72;font-weight:bold">=</span> {} <span style="color:#8b949e;font-style:italic"># Edge ID -&gt; set([edges that are in the same network])</span>
</span></span><span style="display:flex;"><span>    nets <span style="color:#ff7b72;font-weight:bold">=</span> set()     <span style="color:#8b949e;font-style:italic"># Set of known networks</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), <span style="color:#a5d6ff">&#39;detecting disconnected subgraphs&#39;</span>)
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> i, (from_edge, to_set) <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(connections<span style="color:#ff7b72;font-weight:bold">.</span>iteritems()):
</span></span><span style="display:flex;"><span>        from_edge <span style="color:#ff7b72;font-weight:bold">=</span> int(from_edge)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>            from_net <span style="color:#ff7b72;font-weight:bold">=</span> edge_to_net[from_edge]
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">except</span> <span style="color:#f0883e;font-weight:bold">KeyError</span>:
</span></span><span style="display:flex;"><span>            from_net <span style="color:#ff7b72;font-weight:bold">=</span> edge_to_net[from_edge] <span style="color:#ff7b72;font-weight:bold">=</span> hset([from_edge])
</span></span><span style="display:flex;"><span>            nets<span style="color:#ff7b72;font-weight:bold">.</span>add(from_net)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> (i<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>) <span style="color:#ff7b72;font-weight:bold">%</span> (<span style="color:#a5d6ff">25</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">1000</span>):
</span></span><span style="display:flex;"><span>            print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), <span style="color:#a5d6ff">&#39;</span><span style="color:#a5d6ff">%d</span><span style="color:#a5d6ff"> edges processed / </span><span style="color:#a5d6ff">%d</span><span style="color:#a5d6ff"> current subnets&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (i<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>, len(nets)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> to <span style="color:#ff7b72;font-weight:bold">in</span> to_set:
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>                to_net <span style="color:#ff7b72;font-weight:bold">=</span> edge_to_net[to]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic"># If we get here, merge the to_net into the from_net.</span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">if</span> to_net <span style="color:#ff7b72;font-weight:bold">is</span> <span style="color:#ff7b72;font-weight:bold">not</span> from_net:
</span></span><span style="display:flex;"><span>                    <span style="color:#8b949e;font-style:italic"># Update references to and remove the smaller set for speed.</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">if</span> len(to_net) <span style="color:#ff7b72;font-weight:bold">&lt;</span> len(from_net):
</span></span><span style="display:flex;"><span>                        smaller, larger <span style="color:#ff7b72;font-weight:bold">=</span> to_net, from_net
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>                        smaller, larger <span style="color:#ff7b72;font-weight:bold">=</span> from_net, to_net
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    larger<span style="color:#ff7b72;font-weight:bold">.</span>update(smaller)
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">for</span> e <span style="color:#ff7b72;font-weight:bold">in</span> smaller:
</span></span><span style="display:flex;"><span>                        edge_to_net[e] <span style="color:#ff7b72;font-weight:bold">=</span> larger
</span></span><span style="display:flex;"><span>                    nets<span style="color:#ff7b72;font-weight:bold">.</span>remove(smaller)
</span></span><span style="display:flex;"><span>                    edge_to_net[to] <span style="color:#ff7b72;font-weight:bold">=</span> larger
</span></span><span style="display:flex;"><span>                    from_net <span style="color:#ff7b72;font-weight:bold">=</span> larger
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">except</span> <span style="color:#f0883e;font-weight:bold">KeyError</span>:
</span></span><span style="display:flex;"><span>                from_net<span style="color:#ff7b72;font-weight:bold">.</span>add(to)
</span></span><span style="display:flex;"><span>                edge_to_net[to] <span style="color:#ff7b72;font-weight:bold">=</span> from_net
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(time<span style="color:#ff7b72;font-weight:bold">.</span>asctime(), len(nets), <span style="color:#a5d6ff">&#39;subnets found&#39;</span>)
</span></span></code></pre></div><p>Indeed, this is significantly faster. And on very large networks it runs in minutes instead of hours or days. On the small test case used for this post, it runs in under a second. While this could probably be done faster, that&rsquo;s actually good enough for right now.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Mon Jul 29 12:39:55 2013 parsing json input
</span></span><span style="display:flex;"><span>Mon Jul 29 12:39:55 2013 detecting disconnected subgraphs
</span></span><span style="display:flex;"><span>Mon Jul 29 12:39:55 2013 25000 edges processed / 1970 current subnets
</span></span><span style="display:flex;"><span>Mon Jul 29 12:39:55 2013 50000 edges processed / 124 current subnets
</span></span><span style="display:flex;"><span>Mon Jul 29 12:39:55 2013 60 subnets found
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🏖️ Langrangian Relaxation with Gurobi</title>
      <link>https://ryanjoneil.dev/posts/2012-09-22-lagrangian-relaxation-with-gurobi/</link>
      <pubDate>Sat, 22 Sep 2012 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2012-09-22-lagrangian-relaxation-with-gurobi/</guid>
      <description>Solving integer programs with Lagrangian relaxation and Gurobi.</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3 and the 2nd edition of &ldquo;Integer Programming&rdquo; by Laurence Wolsey.</em></p>
<p>We&rsquo;ve been studying Lagrangian Relaxation (LR) in the Advanced Topics in Combinatorial Optimization course I&rsquo;m taking this term, and I had some difficulty finding a simple example covering its application. In case anyone else finds it useful, I&rsquo;m posting a Python version for solving the <a href="https://en.wikipedia.org/wiki/Generalized_assignment_problem">Generalized Assignment Problem</a> (GAP). This won&rsquo;t discuss the theory of LR at all, just give example code using Gurobi.</p>
<h2 id="generalized-assignment">Generalized assignment</h2>
<p>The GAP as defined by <a href="https://onlinelibrary.wiley.com/doi/book/10.1002/9781119606475">Wolsey</a> consists of a maximization problem subject to a set of set packing constraints followed by a set of knapsack constraints.</p>
<p>$$
\begin{align*}
&amp; \text{max}  &amp;&amp; \sum_i \sum_j c_{ij} x_{ij} \\
&amp; \text{s.t.} &amp;&amp; \sum_j x_{ij} \leq 1             &amp;&amp; \forall i \\
&amp;             &amp;&amp; \sum_i a_{ij} x_{ij} \leq b_{ij} &amp;&amp; \forall j \\
&amp;             &amp;&amp; x_{ij} \in {0, 1}
\end{align*}
$$</p>
<h3 id="naive-model">Naive model</h3>
<p>A naive version of this model using Gurobi might look like the following.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#!/usr/bin/env python</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># This is the GAP per Wolsey, pg 208.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">gurobipy</span> <span style="color:#ff7b72">import</span> Model, GRB, quicksum <span style="color:#ff7b72">as</span> qsum
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m <span style="color:#ff7b72;font-weight:bold">=</span> Model(<span style="color:#a5d6ff">&#34;GAP per Wolsey&#34;</span>)
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>modelSense <span style="color:#ff7b72;font-weight:bold">=</span> GRB<span style="color:#ff7b72;font-weight:bold">.</span>MAXIMIZE
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>setParam(<span style="color:#a5d6ff">&#34;OutputFlag&#34;</span>, <span style="color:#79c0ff">False</span>)  <span style="color:#8b949e;font-style:italic"># turns off solver chatter</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>b <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">15</span>]
</span></span><span style="display:flex;"><span>c <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">1</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">12</span>, <span style="color:#a5d6ff">12</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">3</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>a <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">14</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">7</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">12</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">15</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">12</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># x[i][j] = 1 if i is assigned to j</span>
</span></span><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> [[m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(vtype<span style="color:#ff7b72;font-weight:bold">=</span>GRB<span style="color:#ff7b72;font-weight:bold">.</span>BINARY) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> row] <span style="color:#ff7b72">for</span> row <span style="color:#ff7b72;font-weight:bold">in</span> c]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># sum j: x_ij &lt;= 1 for all i</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> x_i <span style="color:#ff7b72;font-weight:bold">in</span> x:
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(sum(x_i) <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># sum i: a_ij * x_ij &lt;= b[j] for all j</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> j, b_j <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(b):
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(qsum(a[i][j] <span style="color:#ff7b72;font-weight:bold">*</span> x_i[j] <span style="color:#ff7b72">for</span> i, x_i <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(x)) <span style="color:#ff7b72;font-weight:bold">&lt;=</span> b_j)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># max sum i,j: c_ij * x_ij</span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>setObjective(
</span></span><span style="display:flex;"><span>    qsum(qsum(c_ij <span style="color:#ff7b72;font-weight:bold">*</span> x_ij <span style="color:#ff7b72">for</span> c_ij, x_ij <span style="color:#ff7b72;font-weight:bold">in</span> zip(c_i, x_i)) <span style="color:#ff7b72">for</span> c_i, x_i <span style="color:#ff7b72;font-weight:bold">in</span> zip(c, x))
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>optimize()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Pull solution out of m.</span>
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;z = </span><span style="color:#a5d6ff">{</span>m<span style="color:#ff7b72;font-weight:bold">.</span>objVal<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#34;x = [&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> x_i <span style="color:#ff7b72;font-weight:bold">in</span> x:
</span></span><span style="display:flex;"><span>    print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;  </span><span style="color:#a5d6ff">{</span>[<span style="color:#a5d6ff">1</span> <span style="color:#ff7b72">if</span> x_ij<span style="color:#ff7b72;font-weight:bold">.</span>x <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0.5</span> <span style="color:#ff7b72">else</span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72">for</span> x_ij <span style="color:#ff7b72;font-weight:bold">in</span> x_i]<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#34;]&#34;</span>)
</span></span></code></pre></div><p>The solver quickly finds the following optimal solution of this toy problem.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-text" data-lang="text"><span style="display:flex;"><span>z = 46.0
</span></span><span style="display:flex;"><span>x = [
</span></span><span style="display:flex;"><span>  [0, 1, 0]
</span></span><span style="display:flex;"><span>  [0, 1, 0]
</span></span><span style="display:flex;"><span>  [1, 0, 0]
</span></span><span style="display:flex;"><span>  [0, 0, 1]
</span></span><span style="display:flex;"><span>  [0, 0, 0]
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><h3 id="lagrangian-model">Lagrangian model</h3>
<p>There are two sets of constraints we can dualize. It can be beneficial to apply Lagrangian Relaxation against problems composed of knapsack constraints, so we will dualize the set packing ones.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># sum j: x_ij &lt;= 1 for all i</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> x_i <span style="color:#ff7b72;font-weight:bold">in</span> x:
</span></span><span style="display:flex;"><span>    model<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(sum(x_i) <span style="color:#ff7b72;font-weight:bold">&lt;=</span> <span style="color:#a5d6ff">1</span>)
</span></span></code></pre></div><p>We replace these with a new set of variables, <code>penalties</code>, which take the values of the slacks on the set packing constraints. We then modify the objective function, adding Lagrangian multipliers times these penalties.</p>
<p>Instead of optimizing once, we do so iteratively. An important consideration is we may get nothing more than a dual bound from this process. Any integer solution is not guaranteed to be primal feasible unless it satisfies complementary slackness conditions &ndash; for each dualized constraint either its multiplier or penalty must be zero.</p>
<p>We then set the initial multiplier values to 2 and use sub-gradient optimization with a step size of <code>1 / (iteration #)</code> to adjust them.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#!/usr/bin/env python</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># This is the GAP per Wolsey, pg 208, using Lagrangian Relaxation.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">gurobipy</span> <span style="color:#ff7b72">import</span> Model, GRB, quicksum <span style="color:#ff7b72">as</span> qsum
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m <span style="color:#ff7b72;font-weight:bold">=</span> Model(<span style="color:#a5d6ff">&#34;GAP per Wolsey with Lagrangian Relaxation&#34;</span>)
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>modelSense <span style="color:#ff7b72;font-weight:bold">=</span> GRB<span style="color:#ff7b72;font-weight:bold">.</span>MAXIMIZE
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>setParam(<span style="color:#a5d6ff">&#34;OutputFlag&#34;</span>, <span style="color:#79c0ff">False</span>)  <span style="color:#8b949e;font-style:italic"># turns off solver chatter</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>b <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">15</span>]
</span></span><span style="display:flex;"><span>c <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">1</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">12</span>, <span style="color:#a5d6ff">12</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">3</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>a <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">14</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">7</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">12</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">15</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">12</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># x[i][j] = 1 if i is assigned to j</span>
</span></span><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> [[m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(vtype<span style="color:#ff7b72;font-weight:bold">=</span>GRB<span style="color:#ff7b72;font-weight:bold">.</span>BINARY) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> row] <span style="color:#ff7b72">for</span> row <span style="color:#ff7b72;font-weight:bold">in</span> c]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># As stated, the GAP has these following constraints. We dualize these into</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># penalties instead, using variables so we can easily extract their values.</span>
</span></span><span style="display:flex;"><span>penalties <span style="color:#ff7b72;font-weight:bold">=</span> [m<span style="color:#ff7b72;font-weight:bold">.</span>addVar() <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> x]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Dualized constraints: sum j: x_ij &lt;= 1 for all i</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> p, x_i <span style="color:#ff7b72;font-weight:bold">in</span> zip(penalties, x):
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(p <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">-</span> sum(x_i))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># sum i: a_ij * x_ij &lt;= b[j] for all j</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> j, b_j <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(b):
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addConstr(qsum(a[i][j] <span style="color:#ff7b72;font-weight:bold">*</span> x_i[j] <span style="color:#ff7b72">for</span> i, x_i <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(x)) <span style="color:#ff7b72;font-weight:bold">&lt;=</span> b_j)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># u[i] = Lagrangian Multiplier for the set packing constraint i</span>
</span></span><span style="display:flex;"><span>u <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#a5d6ff">2.0</span>] <span style="color:#ff7b72;font-weight:bold">*</span> len(x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Re-optimize until either we have run a certain number of iterations</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># or complementary slackness conditions apply.</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> k <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">101</span>):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># max sum i,j: c_ij * x_ij</span>
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>setObjective(
</span></span><span style="display:flex;"><span>        qsum(
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># Original objective function</span>
</span></span><span style="display:flex;"><span>            sum(c_ij <span style="color:#ff7b72;font-weight:bold">*</span> x_ij <span style="color:#ff7b72">for</span> c_ij, x_ij <span style="color:#ff7b72;font-weight:bold">in</span> zip(c_i, x_i))
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">for</span> c_i, x_i <span style="color:#ff7b72;font-weight:bold">in</span> zip(c, x)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72;font-weight:bold">+</span> qsum(
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># Penalties for dualized constraints</span>
</span></span><span style="display:flex;"><span>            u_j <span style="color:#ff7b72;font-weight:bold">*</span> p_j
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">for</span> u_j, p_j <span style="color:#ff7b72;font-weight:bold">in</span> zip(u, penalties)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>optimize()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(
</span></span><span style="display:flex;"><span>        <span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;iteration </span><span style="color:#a5d6ff">{</span>k<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">: z = </span><span style="color:#a5d6ff">{</span>m<span style="color:#ff7b72;font-weight:bold">.</span>objVal<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">, u = </span><span style="color:#a5d6ff">{</span>u<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">, penalties = </span><span style="color:#a5d6ff">{</span>[p<span style="color:#ff7b72;font-weight:bold">.</span>x <span style="color:#ff7b72">for</span> p <span style="color:#ff7b72;font-weight:bold">in</span> penalties]<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Test for complementary slackness</span>
</span></span><span style="display:flex;"><span>    stop <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">True</span>
</span></span><span style="display:flex;"><span>    eps <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">10e-6</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> u_i, p_i <span style="color:#ff7b72;font-weight:bold">in</span> zip(u, penalties):
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> abs(u_i) <span style="color:#ff7b72;font-weight:bold">&gt;</span> eps <span style="color:#ff7b72;font-weight:bold">and</span> abs(p_i<span style="color:#ff7b72;font-weight:bold">.</span>x) <span style="color:#ff7b72;font-weight:bold">&gt;</span> eps:
</span></span><span style="display:flex;"><span>            stop <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#79c0ff">False</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> stop:
</span></span><span style="display:flex;"><span>        print(<span style="color:#a5d6ff">&#34;primal feasible &amp; optimal&#34;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>        s <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.0</span> <span style="color:#ff7b72;font-weight:bold">/</span> k
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(len(x)):
</span></span><span style="display:flex;"><span>            u[i] <span style="color:#ff7b72;font-weight:bold">=</span> max(u[i] <span style="color:#ff7b72;font-weight:bold">-</span> s <span style="color:#ff7b72;font-weight:bold">*</span> (penalties[i]<span style="color:#ff7b72;font-weight:bold">.</span>x), <span style="color:#a5d6ff">0.0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Pull solution out of m.</span>
</span></span><span style="display:flex;"><span>print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;z = </span><span style="color:#a5d6ff">{</span>m<span style="color:#ff7b72;font-weight:bold">.</span>objVal<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#34;x = [&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> x_i <span style="color:#ff7b72;font-weight:bold">in</span> x:
</span></span><span style="display:flex;"><span>    print(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#34;  </span><span style="color:#a5d6ff">{</span>[<span style="color:#a5d6ff">1</span> <span style="color:#ff7b72">if</span> x_ij<span style="color:#ff7b72;font-weight:bold">.</span>x <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0.5</span> <span style="color:#ff7b72">else</span> <span style="color:#a5d6ff">0</span> <span style="color:#ff7b72">for</span> x_ij <span style="color:#ff7b72;font-weight:bold">in</span> x_i]<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#34;</span>)
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#34;]&#34;</span>)
</span></span></code></pre></div><p>Again, the example converges very quickly to an optimal solution.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>iteration 1: z = 48.0, u = [2.0, 2.0, 2.0, 2.0, 2.000], penalties = [0.0, 0.0, 0.0, 0.0, 1.0]
</span></span><span style="display:flex;"><span>iteration 2: z = 47.0, u = [2.0, 2.0, 2.0, 2.0, 1.000], penalties = [0.0, 0.0, 0.0, 0.0, 1.0]
</span></span><span style="display:flex;"><span>iteration 3: z = 46.5, u = [2.0, 2.0, 2.0, 2.0, 0.500], penalties = [0.0, 0.0, 0.0, 0.0, 1.0]
</span></span><span style="display:flex;"><span>iteration 4: z = 46.2, u = [2.0, 2.0, 2.0, 2.0, 0.167], penalties = [0.0, 0.0, 0.0, 0.0, 1.0]
</span></span><span style="display:flex;"><span>iteration 5: z = 46.0, u = [2.0, 2.0, 2.0, 2.0, 0.000], penalties = [0.0, 0.0, 0.0, 0.0, 1.0]
</span></span><span style="display:flex;"><span>primal feasible &amp; optimal
</span></span><span style="display:flex;"><span>z = 46.0
</span></span><span style="display:flex;"><span>x = [
</span></span><span style="display:flex;"><span>  [0, 1, 0]
</span></span><span style="display:flex;"><span>  [0, 1, 0]
</span></span><span style="display:flex;"><span>  [1, 0, 0]
</span></span><span style="display:flex;"><span>  [0, 0, 1]
</span></span><span style="display:flex;"><span>  [0, 0, 0]
</span></span><span style="display:flex;"><span>]
</span></span></code></pre></div><p>Exercise for the reader: change the script to dualize the knapsack constraints instead of the set packing constraints. What is the result of this change in terms of convergence?</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2012-09-22-lagrangian-relaxation-with-gurobi/gap.py"><code>gap.py</code></a></li>
<li><a href="/files/2012-09-22-lagrangian-relaxation-with-gurobi/gap-lagrangian.py"><code>gap-lagrangian.py</code></a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>🔲 Normal Magic Squares</title>
      <link>https://ryanjoneil.dev/posts/2012-01-13-normal-magic-squares/</link>
      <pubDate>Fri, 13 Jan 2012 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2012-01-13-normal-magic-squares/</guid>
      <description>An integer programming formulation of the normal magic squares problem.</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3 and <a href="https://github.com/scipopt/PySCIPOpt">PySCIPOpt</a>. The original version used Python 2 and <a href="https://pythonhosted.org/python-zibopt/">python-zibopt</a>. It has also been edited for clarity.</em></p>
<p>As a followup to the <a href="../2012-01-12-magic-squares-and-big-ms/">last post</a>, I created <a href="/files/2012-01-13-normal-magic-squares/normal-magic-square.py">another SCIP example</a> for finding Normal Magic Squares. This is similar to <a href="https://github.com/CPMpy/cpmpy/blob/master/examples/quickstart_sudoku.ipynb">solving a Sudoku problem</a>, except that here the number of binary variables depends on the square size. In the case of Sudoku, each cell has 9 binary variables &ndash; one for each potential value it might take. For a normal magic square, there are $n^2$ possible values for each cell, $n^2$ cells, and one variable representing the row, column, and diagonal sums. This makes a total of $n^4$ binary variables and one continuous variables in the model.</p>
<p>However, there are no big-Ms.</p>
<p>I think the neat part of this code is in this section:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Construct an expression for each cell that is the sum of</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># its binary variables with their associated coefficients.</span>
</span></span><span style="display:flex;"><span>sums <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> row <span style="color:#ff7b72;font-weight:bold">in</span> matrix:
</span></span><span style="display:flex;"><span>    sums_row <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> cell <span style="color:#ff7b72;font-weight:bold">in</span> row:
</span></span><span style="display:flex;"><span>        sums_row<span style="color:#ff7b72;font-weight:bold">.</span>append(sum((i <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>) <span style="color:#ff7b72;font-weight:bold">*</span> x <span style="color:#ff7b72">for</span> i, x <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(cell)))
</span></span><span style="display:flex;"><span>    sums<span style="color:#ff7b72;font-weight:bold">.</span>append(sums_row)
</span></span></code></pre></div><p>It creates sums of the $n^2$ variables for each cell with their appropriate coefficients ($1$ to $n^2$) and stores those expressions to make the subsequent constraint creation simpler.</p>
<p>Another interesting exercise for the reader: Change <a href="/files/2012-01-13-normal-magic-squares/normal-magic-square.py">the code</a> to minimize the sum of each column. How does that impact the solution time?</p>
]]></content:encoded>
    </item>
    <item>
      <title>🔲 Magic Squares and Big-Ms</title>
      <link>https://ryanjoneil.dev/posts/2012-01-12-magic-squares-and-big-ms/</link>
      <pubDate>Thu, 12 Jan 2012 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2012-01-12-magic-squares-and-big-ms/</guid>
      <description>An integer programming formulation of the magic squares problem.</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3 and <a href="https://github.com/scipopt/PySCIPOpt">PySCIPOpt</a>. The original version used Python 2 and <a href="https://pythonhosted.org/python-zibopt/">python-zibopt</a>. It has also been edited for clarity.</em></p>
<p>Back in October of 2011, I started toying with a model for finding <a href="https://en.wikipedia.org/wiki/Magic_square">magic squares</a> using SCIP. This is a fun modeling exercise and a challenging problem. First one constructs a square matrix of integer-valued variables.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">pyscipopt</span> <span style="color:#ff7b72">import</span> Model
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># [...snip...]</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m <span style="color:#ff7b72;font-weight:bold">=</span> Model()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>matrix <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(size):
</span></span><span style="display:flex;"><span>    row <span style="color:#ff7b72;font-weight:bold">=</span> [m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(vtype<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;I&#34;</span>, lb<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">1</span>) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(size)]
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> x <span style="color:#ff7b72;font-weight:bold">in</span> row:
</span></span><span style="display:flex;"><span>        m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(x <span style="color:#ff7b72;font-weight:bold">&lt;=</span> M)
</span></span><span style="display:flex;"><span>    matrix<span style="color:#ff7b72;font-weight:bold">.</span>append(row)
</span></span></code></pre></div><p>Then one adds the following constraints:</p>
<ul>
<li>All variables ≥ 1.</li>
<li>All rows, columns, and the diagonal sum to the same value.</li>
<li>All variables take different values.</li>
</ul>
<p>The first two constraints are trivial to implement, and relatively easy for the solver. What I do is add a single extra variable then set it equal to the sums of each row, column, and the diagonal.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>sum_val <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(vtype<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;M&#34;</span>)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(size):
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(sum(matrix[i]) <span style="color:#ff7b72;font-weight:bold">==</span> sum_val)
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(sum(matrix[j][i] <span style="color:#ff7b72">for</span> j <span style="color:#ff7b72;font-weight:bold">in</span> range(size)) <span style="color:#ff7b72;font-weight:bold">==</span> sum_val)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(sum(matrix[i][i] <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(size)) <span style="color:#ff7b72;font-weight:bold">==</span> sum_val)
</span></span></code></pre></div><p>It&rsquo;s the third that messes things up. You can think of this as saying, for every possible pair of integer-valued variables $x$ and $y$:</p>
<p>$$ x \ge y + 1 \quad \text{or} \quad x \le y - 1 $$</p>
<p>Why is this hard? Because we can&rsquo;t add both constraints to the model. That would make it infeasible. Instead, we add write them in such a way that exactly one will be active for any any given solution. This requires, for each pair of variables, an additional binary variable $z$ and a (possibly big) constant $M$. Thus we reformulate the above as:</p>
<p>$$
x \ge (y + 1) - M z \
x \le (y - 1) + M (1-z) \
z \in {0,1}
$$</p>
<p>In code this looks like:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">itertools</span> <span style="color:#ff7b72">import</span> chain
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>all_vars <span style="color:#ff7b72;font-weight:bold">=</span> list(chain(<span style="color:#ff7b72;font-weight:bold">*</span>matrix))
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> i, x <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(all_vars):
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> y <span style="color:#ff7b72;font-weight:bold">in</span> all_vars[i<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>:]:
</span></span><span style="display:flex;"><span>        z <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(vtype<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;B&#34;</span>)
</span></span><span style="display:flex;"><span>        m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(x <span style="color:#ff7b72;font-weight:bold">&gt;=</span> y <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">-</span> M<span style="color:#ff7b72;font-weight:bold">*</span>z)
</span></span><span style="display:flex;"><span>        m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(x <span style="color:#ff7b72;font-weight:bold">&lt;=</span> y <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">+</span> M<span style="color:#ff7b72;font-weight:bold">*</span>(<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">-</span>z))
</span></span></code></pre></div><p>However, <a href="https://orinanobworld.blogspot.com/2011/07/perils-of-big-m.html">here be dragons</a>. We may not know how big (or small) to make $M$. Generally we want it as small as possible to make the LP relaxation of our integer programming model tighter. Different values of $M$ have unpredictable effects on solution time.</p>
<p>Which brings us to an interesting idea:</p>
<p>SCIP now supports bilinear constraints. This means that I can make $M$ a variable in the above model.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">sys</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>    M <span style="color:#ff7b72;font-weight:bold">=</span> int(sys<span style="color:#ff7b72;font-weight:bold">.</span>argv[<span style="color:#a5d6ff">2</span>])
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">except</span> <span style="color:#f0883e;font-weight:bold">IndexError</span>:
</span></span><span style="display:flex;"><span>    M <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(vtype<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#34;M&#34;</span>, lb<span style="color:#ff7b72;font-weight:bold">=</span>size <span style="color:#ff7b72;font-weight:bold">*</span> size)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assert</span> M <span style="color:#ff7b72;font-weight:bold">&gt;=</span> size <span style="color:#ff7b72;font-weight:bold">*</span> size
</span></span></code></pre></div><p>The magic square model linked to in this post provides both options. The first command line argument it requires is the matrix size. The second one, $M$, is optional. If not given, it leaves $M$ up to the solver.</p>
<p>An interesting exercise for the reader: Change <a href="/files/2012-01-12-magic-squares-and-big-ms/magic-square.py">the code</a> to search for a <em>minimal</em> magic square, which minimizes either the value of $M$ or the sums of the columns, rows, and diagonal.</p>
]]></content:encoded>
    </item>
    <item>
      <title>⏳️ Know Your Time Complexities - Part 2</title>
      <link>https://ryanjoneil.dev/posts/2011-11-25-know-your-time-complexities-part-2/</link>
      <pubDate>Fri, 25 Nov 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-11-25-know-your-time-complexities-part-2/</guid>
      <description>More on the importance of time complexity in basic programming</description>
      <content:encoded><![CDATA[<p>In response to <a href="../2011-10-25-know-your-time-complexities/">this</a> post, <a href="https://en.wikipedia.org/wiki/Structure_and_Interpretation_of_Computer_Programs">Ben Bitdiddle</a> inquires:</p>
<blockquote>
<p>I understand the concept of using a companion set to remove duplicates from a list while preserving the order of its elements. But what should I do if these elements are composed of smaller pieces? For instance, say I am generating <a href="https://en.wikipedia.org/wiki/Combination">combinations</a> of numbers in which order is unimportant. How do I make a set recognize that <code>[1,2,3]</code> is the same as <code>[3,2,1]</code> in this case?</p>
</blockquote>
<p>There are a couple points that should help here.</p>
<p>While lists are unhashable and therefore cannot be put into sets, tuples are perfectly capable of this. Therefore I cannot do this.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>s <span style="color:#ff7b72;font-weight:bold">=</span> set()
</span></span><span style="display:flex;"><span>s<span style="color:#ff7b72;font-weight:bold">.</span>add([<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">3</span>])
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Traceback (most recent call last):
</span></span><span style="display:flex;"><span> File &#34;&lt;stdin&gt;&#34;, line 1, in &lt;module&gt;
</span></span><span style="display:flex;"><span>TypeError: unhashable type: &#39;list&#39;
</span></span></code></pre></div><p>But this works just fine <em>(extra space added for emphasis of tuple parentheses)</em>.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>s<span style="color:#ff7b72;font-weight:bold">.</span>add( (<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">3</span>) )
</span></span></code></pre></div><p><code>(3,2,1)</code> and <code>(1,2,3)</code> may not hash to the same thing, but tuples are easily sortable. If I sort them before adding them to a set, they look the same.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>tuple(sorted( (<span style="color:#a5d6ff">3</span>,<span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">1</span>) ))
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>)
</span></span></code></pre></div><p>If I want to be a little fancier, I can user <a href="https://docs.python.org/3/library/itertools.html#itertools.combinations"><code>itertools.combinations</code></a>. The following generates all unique 3-digit combinations of integers from 1 to 4:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">itertools</span> <span style="color:#ff7b72">import</span> combinations
</span></span><span style="display:flex;"><span>list(combinations(range(<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">5</span>), <span style="color:#a5d6ff">3</span>))
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>[(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>), (<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>), (<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">4</span>), (<span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">4</span>)]
</span></span></code></pre></div><p>Now say I want to only find those that match some condition. I can add a filter to return, say, only those 3-digit combinations of integers from 1 to 6 that multiply to a number divisible by 10:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>list(filter(
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">lambda</span> x: <span style="color:#ff7b72;font-weight:bold">not</span> (x[<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72;font-weight:bold">*</span>x[<span style="color:#a5d6ff">1</span>]<span style="color:#ff7b72;font-weight:bold">*</span>x[<span style="color:#a5d6ff">2</span>]) <span style="color:#ff7b72;font-weight:bold">%</span> <span style="color:#a5d6ff">10</span>,
</span></span><span style="display:flex;"><span>    combinations(range(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">7</span>), <span style="color:#a5d6ff">3</span>)
</span></span><span style="display:flex;"><span>))
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>[(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">5</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">6</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">5</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">6</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">6</span>),
</span></span><span style="display:flex;"><span> (<span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">6</span>)]
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>⏳️ Know Your Time Complexities</title>
      <link>https://ryanjoneil.dev/posts/2011-10-25-know-your-time-complexities/</link>
      <pubDate>Tue, 25 Oct 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-10-25-know-your-time-complexities/</guid>
      <description>The importance of time complexity in basic programming</description>
      <content:encoded><![CDATA[<p>This is based on a lightning talk I gave at the LA PyLadies October Hackathon.</p>
<p>I&rsquo;m actually not going to go into anything much resembling algorithmic complexity here. What I&rsquo;d like to do is present a common performance anti-pattern that I see from novice programmers about once every year or so. If I can prevent one person from committing this error, this post will have achieved its goal. I&rsquo;d also like to show how an intuitive understanding of time required by operations in relation to the size of data they operate on can be helpful.</p>
<p>Say you have a Big List of Things. It doesn&rsquo;t particularly matter what these things are. Often they might be objects or dictionaries of denormalized data. In this example we&rsquo;ll use numbers. Let&rsquo;s generate a list of 1 million integers, each randomly chosen from the first 100 thousand natural numbers:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>choices <span style="color:#ff7b72;font-weight:bold">=</span> range(<span style="color:#a5d6ff">100000</span>)
</span></span><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> [random<span style="color:#ff7b72;font-weight:bold">.</span>choice(choices) <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">1000000</span>)]
</span></span></code></pre></div><p>Now say you want to remove (or aggregate, or structure) duplicate data while keeping them <em>in order of appearance</em>. Intuitively, this seems simple enough. A first solution might involve creating a new empty list, iterating over x, and only appending those items that are not already in the new list.</p>
<h2 id="the-bad-way">The Bad Way</h2>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>order <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> x:
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> i <span style="color:#ff7b72;font-weight:bold">not</span> <span style="color:#ff7b72;font-weight:bold">in</span> order:
</span></span><span style="display:flex;"><span>        order<span style="color:#ff7b72;font-weight:bold">.</span>append(i)
</span></span></code></pre></div><p>Try running this. What&rsquo;s wrong with it?</p>
<p>The issue is the conditional on line 3. In the worst case, it could look at every item in the order list for each item in x. If the list is big, as it is in our example, that wastes a lot of cycles. We can reason that we can improve the performance of our code by replacing this conditional with something faster.</p>
<h2 id="the-good-way">The Good Way</h2>
<p>Given that sets have near constant time for membership tests, one solution is to create a companion data structure, which we&rsquo;ll call seen. Being a set, it doesn&rsquo;t care about the order of the items, but it will allow us to test for membership quickly.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>order <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>seen <span style="color:#ff7b72;font-weight:bold">=</span> set()
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> x:
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> i <span style="color:#ff7b72;font-weight:bold">not</span> <span style="color:#ff7b72;font-weight:bold">in</span> seen:
</span></span><span style="display:flex;"><span>        seen<span style="color:#ff7b72;font-weight:bold">.</span>add(i)
</span></span><span style="display:flex;"><span>        order<span style="color:#ff7b72;font-weight:bold">.</span>append(i)
</span></span></code></pre></div><p>Now try running this. Better?</p>
<p>Not that this is the best way to perform this particular action. If you aren&rsquo;t familiar with it, take a look at the <a href="http://docs.python.org/library/itertools.html#itertools.groupby"><code>groupby</code></a> function from <code>itertools</code>, which is what I will sometimes reach for in a case like this.</p>
]]></content:encoded>
    </item>
    <item>
      <title>🎰 Deterministic vs. Stochastic Simulation</title>
      <link>https://ryanjoneil.dev/posts/2011-06-11-deterministic-vs-stochastic-simulation/</link>
      <pubDate>Sat, 11 Jun 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-06-11-deterministic-vs-stochastic-simulation/</guid>
      <description>The importance of randomness in simulations</description>
      <content:encoded><![CDATA[<p>I find I have to build simulations with increasing frequency in my work and life. Usually this indicates I&rsquo;m faced with one of the following situations:</p>
<ul>
<li>The need for a quick estimate regarding the quantitative behavior of some situation.</li>
<li>The desire to verify the result of a computation or assumption.</li>
<li>A situation which is too complex or random to effectively model or understand.</li>
</ul>
<p>Anyone familiar at all with simulation will recognize the last item as the motivating force of the entire field. Simulation models tend to take over when systems become so complex that understanding them is prohibitive in cost and time or entirely infeasible. In a simulation, the modeler can focus on individual interactions between entities while still hoping for useful output in the form of descriptive statistics.</p>
<p>As such, simulations are nearly always stochastic. The output of a simulation, whether it be the mean time to service upon entering a queue or the number of fish alive in a pond, is determined by a number of random inputs. It is estimated by looking at a sample of the entire, often infinite, problem space and therefore must be described in terms of mean and variance.</p>
<p>For me, simulation building usually follows a process roughly like this:</p>
<ul>
<li>Work with a domain expert to understand the process under study.</li>
<li>Convert this process into a deterministic simulation (no randomness).</li>
<li>Verify the output of the deterministic simulation.</li>
<li>Anlyze the inputs of the simulation to determine their probability distributions.</li>
<li>Convert the deterministic simulation to a stochastic simulation.</li>
</ul>
<p>The reason for creating a simulation without randomness first is that it can be difficult or impossible to verify its correctness otherwise. Thus one may focus on the simulation logic first before analyzing and adding sources of randomness.</p>
<p>Where the procedure breaks down is after the third step. Domain experts are often happy to share their knowledge about systems to aid in designing simulations, and typically can understand the resulting abstractions. They are also invaluable in verifying simulation output. However, they are unlikely to understand why it is necessary to add randomness to a system that they already perceive as functional. Further, doing so can be just as difficult and time consuming as the initial model development and therefore requires justification.</p>
<p>This can be a quandary for the model builder. How does one communicate the need to incorporate randomness to decision makers who lack understanding of probability? It is trivially easy to construct simulations that use the same input parameters but yield drastically different outputs. Consider the code below, which simulates two events occurring and counts the number of times event b happens before event a.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">sim_stochastic</span>(event_a_lambda, event_b_lambda):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Returns 0 if event A arrives first, 1 if event B arrives first</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Calculate next arrival time for each event randomly.</span>
</span></span><span style="display:flex;"><span>    event_a_arrival <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>expovariate(event_a_lambda)
</span></span><span style="display:flex;"><span>    event_b_arrival <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>expovariate(event_b_lambda)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0.0</span> <span style="color:#ff7b72">if</span> event_a_arrival <span style="color:#ff7b72;font-weight:bold">&lt;=</span> event_b_arrival <span style="color:#ff7b72">else</span> <span style="color:#a5d6ff">1.0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">sim_deterministic</span>(event_a_lambda, event_b_lambda):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Returns 0 if event A arrives first, 1 if event B arrives first</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Calculate next arrival time for each event deterministically.</span>
</span></span><span style="display:flex;"><span>    event_a_arrival <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.0</span> <span style="color:#ff7b72;font-weight:bold">/</span> event_a_lambda
</span></span><span style="display:flex;"><span>    event_b_arrival <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1.0</span> <span style="color:#ff7b72;font-weight:bold">/</span> event_b_lambda
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> <span style="color:#a5d6ff">0.0</span> <span style="color:#ff7b72">if</span> event_a_arrival <span style="color:#ff7b72;font-weight:bold">&lt;=</span> event_b_arrival <span style="color:#ff7b72">else</span> <span style="color:#a5d6ff">1.0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    event_a_lambda <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0.3</span>
</span></span><span style="display:flex;"><span>    event_b_lambda <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0.5</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    repetitions <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">10000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> sim <span style="color:#ff7b72;font-weight:bold">in</span> (sim_stochastic, sim_deterministic):
</span></span><span style="display:flex;"><span>        output <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>            sim(event_a_lambda, event_b_lambda)
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(repetitions)
</span></span><span style="display:flex;"><span>        ]
</span></span><span style="display:flex;"><span>        event_b_first <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">100.0</span> <span style="color:#ff7b72;font-weight:bold">*</span> (sum(output) <span style="color:#ff7b72;font-weight:bold">/</span> len(output))
</span></span><span style="display:flex;"><span>        print(<span style="color:#a5d6ff">&#39;event b is first </span><span style="color:#a5d6ff">%0.1f%%</span><span style="color:#a5d6ff"> of the time&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> event_b_first)
</span></span></code></pre></div><p>Both simulations use the same input parameter, but the second one is essentially wrong as b will always happen first. In the stochastic version, we use exponential distributions for the inputs and obtain an output that verifies our basic understanding of these distributions.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>event b is first 63.0% of the time
</span></span><span style="display:flex;"><span>event b is first 100.0% of the time
</span></span></code></pre></div><p>How about you? How do you discuss the need to model a random world with decision makers?</p>
]]></content:encoded>
    </item>
    <item>
      <title>🔮 NetworkX and Python Futures</title>
      <link>https://ryanjoneil.dev/posts/2011-05-19-networkx-and-python-futures/</link>
      <pubDate>Thu, 19 May 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-05-19-networkx-and-python-futures/</guid>
      <description>Solve graph problems on multiple cores NetworkX and Python futures</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with <a href="https://networkx.org/">NetworkX</a> and for clarity.</em></p>
<p>It&rsquo;s possible this will turn out like the day when Python 2.5 introduced <a href="https://docs.python.org/release/2.5/whatsnew/pep-342.html">coroutines</a>. At the time I was very excited. I spent several hours trying to convince my coworkers we should immediately abandon all our existing Java infrastructure and port it to finite state machines implemented using Python coroutines. After a day of hand waving over a proof of concept, we put that idea aside and went about our lives.</p>
<p>Soon after, I left for a Python shop, but in the next half decade I still never found a good place to use this interesting feature.</p>
<p>But it doesn&rsquo;t feel like that.</p>
<p>As I come to terms more with switching to Python 3.2, the <a href="https://docs.python.org/py3k/library/concurrent.futures.html">futures</a> module seems similarly exciting. I wish I&rsquo;d had it years ago, and it&rsquo;s almost reason in itself to upgrade from Python 2.7. <em>Who cares if none of your libraries have been ported yet?</em></p>
<p>This library lets you take any function and distribute it over a process pool. To test that out, we&rsquo;ll generate a bunch of random graphs and iterate over all their <a href="https://en.wikipedia.org/wiki/Clique_(graph_theory)">cliques</a>.</p>
<h2 id="code">Code</h2>
<p>First, let&rsquo;s generate some test data using the <a href="https://networkx.org/documentation/stable/reference/generated/networkx.generators.random_graphs.gnm_random_graph.html"><code>dense_gnm_random_graph</code></a> function. Our data includes 1000 random graphs, each with 100 nodes and 100 * 100 edges.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">networkx</span> <span style="color:#ff7b72">as</span> <span style="color:#ff7b72">nx</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>n <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">100</span>
</span></span><span style="display:flex;"><span>graphs <span style="color:#ff7b72;font-weight:bold">=</span> [nx<span style="color:#ff7b72;font-weight:bold">.</span>dense_gnm_random_graph(n, n<span style="color:#ff7b72;font-weight:bold">*</span>n) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">1000</span>)]
</span></span></code></pre></div><p>Now we write a function iterate over all cliques in a given graph. NetworkX provides a <code>find_cliques</code> function which returns a generator. Iterating over them ensures we will run through the entire process of finding all cliques for a graph.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">iterate_cliques</span>(g):
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> nx<span style="color:#ff7b72;font-weight:bold">.</span>find_cliques(g):
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">pass</span>
</span></span></code></pre></div><p>Now we just define two functions, one for running in serial and one for running in parallel using <code>futures</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">concurrent</span> <span style="color:#ff7b72">import</span> futures
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">serial_test</span>(graphs):
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> g <span style="color:#ff7b72;font-weight:bold">in</span> graphs:
</span></span><span style="display:flex;"><span>        iterate_cliques(g)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">parallel_test</span>(graphs, max_workers):
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">with</span> futures<span style="color:#ff7b72;font-weight:bold">.</span>ProcessPoolExecutor(max_workers<span style="color:#ff7b72;font-weight:bold">=</span>max_workers) <span style="color:#ff7b72">as</span> executor:
</span></span><span style="display:flex;"><span>        executor<span style="color:#ff7b72;font-weight:bold">.</span>map(iterate_cliques, graphs)
</span></span></code></pre></div><p>Our <code>__main__</code> simply generates the random graphs, samples from them, times both functions, and write CSV data to standard output.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">csv</span> <span style="color:#ff7b72">import</span> writer
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">sys</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    out <span style="color:#ff7b72;font-weight:bold">=</span> writer(sys<span style="color:#ff7b72;font-weight:bold">.</span>stdout)
</span></span><span style="display:flex;"><span>    out<span style="color:#ff7b72;font-weight:bold">.</span>writerow([<span style="color:#a5d6ff">&#39;num graphs&#39;</span>, <span style="color:#a5d6ff">&#39;serial time&#39;</span>, <span style="color:#a5d6ff">&#39;parallel time&#39;</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    n <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">100</span>
</span></span><span style="display:flex;"><span>    graphs <span style="color:#ff7b72;font-weight:bold">=</span> [nx<span style="color:#ff7b72;font-weight:bold">.</span>dense_gnm_random_graph(n, n<span style="color:#ff7b72;font-weight:bold">*</span>n) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">1000</span>)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Run with a number of different randomly generated graphs</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> num_graphs <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">50</span>, <span style="color:#a5d6ff">1001</span>, <span style="color:#a5d6ff">50</span>):
</span></span><span style="display:flex;"><span>        sample <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>choices(graphs, k <span style="color:#ff7b72;font-weight:bold">=</span> num_graphs)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        start <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>        serial_test(sample)
</span></span><span style="display:flex;"><span>        serial_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time() <span style="color:#ff7b72;font-weight:bold">-</span> start
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        start <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time()
</span></span><span style="display:flex;"><span>        parallel_test(sample, <span style="color:#a5d6ff">16</span>)
</span></span><span style="display:flex;"><span>        parallel_time <span style="color:#ff7b72;font-weight:bold">=</span> time<span style="color:#ff7b72;font-weight:bold">.</span>time() <span style="color:#ff7b72;font-weight:bold">-</span> start
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        out<span style="color:#ff7b72;font-weight:bold">.</span>writerow([num_graphs, serial_time, parallel_time])
</span></span></code></pre></div><p>The output of this script shows that we get a fairly linear speedup to this code with little effort.</p>
<p><img alt="Speedup" loading="lazy" src="/files/2011-05-19-networkx-and-python-futures/speedup.png#center"></p>
<p>I ran this on a machine with 8 cores and hyperthreading. Eyeballing the chart, it looks like the speedup is roughly 5x. My system monitor shows spikes on CPU usage across cores whenever the parallel test runs.</p>
<p><img alt="CPU usage" loading="lazy" src="/files/2011-05-19-networkx-and-python-futures/cpu.png#center"></p>
<h2 id="resources">Resources</h2>
<ul>
<li>Output <a href="/files/2011-05-19-networkx-and-python-futures/data.csv">data</a></li>
<li>Full <a href="/files/2011-05-19-networkx-and-python-futures/iterate-cliques.py">source listing</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>👉 Affine Scaling in R</title>
      <link>https://ryanjoneil.dev/posts/2011-04-27-affine-scaling-in-r/</link>
      <pubDate>Wed, 27 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-27-affine-scaling-in-r/</guid>
      <description>Affine scaling for interior point optimization in R</description>
      <content:encoded><![CDATA[<p>I recently stumbled across an implementation of the <a href="https://demonstrations.wolfram.com/AffineScalingInteriorPointMethod/">affine scaling</a> <a href="https://en.wikipedia.org/wiki/Interior_point_method">interior point method</a> for solving linear programs that I&rsquo;d coded up in R once upon a time. I&rsquo;m posting it here in case anyone else finds it useful. There&rsquo;s not a whole lot of thought given to efficiency or numerical stability, just a demonstration of the basic algorithm. Still, sometimes that&rsquo;s exactly what one wants.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>solve.affine <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#ff7b72">function</span>(A, rc, x, tolerance<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">10</span>^<span style="color:#a5d6ff">-7</span>, R<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">0.999</span>) {
</span></span><span style="display:flex;"><span>  <span style="color:#8b949e;font-style:italic"># Affine scaling method</span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">while</span> (T) {
</span></span><span style="display:flex;"><span>    X_diag <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">diag</span>(x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Compute (A * X_diag^2 * A^t)-1 using Cholesky factorization.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># This is responsible for scaling the original problem matrix.</span>
</span></span><span style="display:flex;"><span>    q <span style="color:#ff7b72;font-weight:bold">&lt;-</span> A <span style="color:#ff7b72;font-weight:bold">%*%</span> X_diag<span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span> <span style="color:#ff7b72;font-weight:bold">%*%</span> <span style="color:#d2a8ff;font-weight:bold">t</span>(A)
</span></span><span style="display:flex;"><span>    q_inv <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">chol2inv</span>(<span style="color:#d2a8ff;font-weight:bold">chol</span>(q))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># lambda = q * A * X_diag^2 * c</span>
</span></span><span style="display:flex;"><span>    lambda <span style="color:#ff7b72;font-weight:bold">&lt;-</span> q_inv <span style="color:#ff7b72;font-weight:bold">%*%</span> A <span style="color:#ff7b72;font-weight:bold">%*%</span> X_diag^2 <span style="color:#ff7b72;font-weight:bold">%*%</span> rc
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># c - A^t * lambda is used repeatedly</span>
</span></span><span style="display:flex;"><span>    foo <span style="color:#ff7b72;font-weight:bold">&lt;-</span> rc <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#d2a8ff;font-weight:bold">t</span>(A) <span style="color:#ff7b72;font-weight:bold">%*%</span> lambda
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># We converge as s goes to zero</span>
</span></span><span style="display:flex;"><span>    s <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">sqrt</span>(<span style="color:#d2a8ff;font-weight:bold">sum</span>((X_diag <span style="color:#ff7b72;font-weight:bold">%*%</span> foo)^2))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Compute new x</span>
</span></span><span style="display:flex;"><span>    x <span style="color:#ff7b72;font-weight:bold">&lt;-</span> (x <span style="color:#ff7b72;font-weight:bold">+</span> R <span style="color:#ff7b72;font-weight:bold">*</span> X_diag^2 <span style="color:#ff7b72;font-weight:bold">%*%</span> foo <span style="color:#ff7b72;font-weight:bold">/</span> s)[,]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># If s is within our tolerance, stop.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (<span style="color:#d2a8ff;font-weight:bold">abs</span>(s) <span style="color:#ff7b72;font-weight:bold">&lt;</span> tolerance) <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>  x
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>This function accepts a matrix <code>A</code> which contains all technological coefficients for an LP, a vector <code>rc</code> containing its reduced costs, and an initial point <code>x</code> interior to the LP&rsquo;s feasible region. Optional arguments to the function include a tolerance, for detecting when the method is within an acceptable distance from the optimal point, and a value for <code>R</code>, which must be strictly between 0 and 1 and controls scaling.</p>
<p>The method works by rescaling the matrix <code>A</code> around the current solution <code>x</code>. It then computes a new <code>x</code> such that it remains feasible and interior, which is why <code>R</code> cannot be 0 or 1. It requires a feasible interior point to start and only projects to other feasible interior points, so the right hand side of the LP is not required (it is implicit from the starting point). The shadow prices for each iteration are captured in the vector lambda, so the gap between primal and dual solutions is easy to compute.</p>
<p>We run this function against a 3x3 LP with a known solution:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>max z = 5x1 + 4x2 + 3x3
</span></span><span style="display:flex;"><span>st      2x1 + 3x2 +  x3 &lt;=  5
</span></span><span style="display:flex;"><span>        4x1 +  x2 + 2x3 &lt;= 11
</span></span><span style="display:flex;"><span>        3x1 + 4x2 + 2x3 &lt;=  8
</span></span><span style="display:flex;"><span>        x1, x2, x3 &gt;= 0
</span></span></code></pre></div><p>The optimal solution to this LP is:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>z  = 13
</span></span><span style="display:flex;"><span>x1 =  2
</span></span><span style="display:flex;"><span>x2 =  0
</span></span><span style="display:flex;"><span>x3 =  1
</span></span></code></pre></div><p>This problem can be run against the affine scaling function by defining A with all necessary slack variables, and using an arbitrary feasible interior point:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>A <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">matrix</span>(<span style="color:#d2a8ff;font-weight:bold">c</span>(
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">3</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">0</span>,<span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">0</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">0</span>,
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">3</span>,<span style="color:#a5d6ff">4</span>,<span style="color:#a5d6ff">2</span>,<span style="color:#a5d6ff">0</span>,<span style="color:#a5d6ff">0</span>,<span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>), nrow<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">3</span>, byrow<span style="color:#ff7b72;font-weight:bold">=</span>T)
</span></span><span style="display:flex;"><span>rc <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>x  <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">c</span>(<span style="color:#a5d6ff">0.5</span>, <span style="color:#a5d6ff">0.5</span>, <span style="color:#a5d6ff">0.5</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">7.5</span>, <span style="color:#a5d6ff">3.5</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>solution <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">solve.affine</span>(A, rc, x)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">print</span>(solution)
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">print</span>(<span style="color:#d2a8ff;font-weight:bold">sum</span>(solution <span style="color:#ff7b72;font-weight:bold">*</span> rc))
</span></span></code></pre></div><p>This provides an output vector that is very close to the optimal primal solution shown above. Since interior point methods converge asymptotically to optimal solutions, it is important to note that we can only ever get (extremely) close to our final optimal objective and decision variable values.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">print</span>(solution)
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">1.999998e+00</span> <span style="color:#a5d6ff">4.268595e-07</span> <span style="color:#a5d6ff">1.000002e+00</span> <span style="color:#a5d6ff">1.280579e-06</span> <span style="color:#a5d6ff">1.000005e+00</span>
</span></span><span style="display:flex;"><span>[6] <span style="color:#a5d6ff">1.280579e-06</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">print</span>(<span style="color:#d2a8ff;font-weight:bold">sum</span>(solution <span style="color:#ff7b72;font-weight:bold">*</span> rc))
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">13.00000</span>
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: Transpiler</title>
      <link>https://ryanjoneil.dev/posts/2011-04-18-reformed-japhs-transpiler/</link>
      <pubDate>Wed, 20 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-18-reformed-japhs-transpiler/</guid>
      <description>Scheme to Python transpiler</description>
      <content:encoded><![CDATA[<p><em>Note: This post was edited for clarity.</em></p>
<p>For the final JAPH in this series, I implemented a simple transpiler that converts a small subset of <a href="https://www.scheme.org/">Scheme</a> programs to equivalent Python programs. It starts with a Scheme program that prints <code>'just another scheme hacker'</code>.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-scheme" data-lang="scheme"><span style="display:flex;"><span>(<span style="color:#ff7b72">define </span>(<span style="color:#d2a8ff;font-weight:bold">output</span> <span style="color:#79c0ff">x</span>)
</span></span><span style="display:flex;"><span>    (<span style="color:#ff7b72">if </span>(null? <span style="color:#79c0ff">x</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>        (<span style="color:#ff7b72">begin </span>(display (car <span style="color:#79c0ff">x</span>))
</span></span><span style="display:flex;"><span>                (<span style="color:#ff7b72">if </span>(null? (cdr <span style="color:#79c0ff">x</span>))
</span></span><span style="display:flex;"><span>                    (display <span style="color:#a5d6ff">&#34;\n&#34;</span>)
</span></span><span style="display:flex;"><span>                    (<span style="color:#ff7b72">begin </span>(display <span style="color:#a5d6ff">&#34; &#34;</span>)
</span></span><span style="display:flex;"><span>                            (<span style="color:#d2a8ff;font-weight:bold">output</span> (cdr <span style="color:#79c0ff">x</span>)))))))
</span></span><span style="display:flex;"><span>(<span style="color:#d2a8ff;font-weight:bold">output</span> (list <span style="color:#a5d6ff">&#34;just&#34;</span> <span style="color:#a5d6ff">&#34;another&#34;</span> <span style="color:#a5d6ff">&#34;scheme&#34;</span> <span style="color:#a5d6ff">&#34;hacker&#34;</span>))
</span></span></code></pre></div><p>The program then tokenizes that Scheme source, parses the token stream, and converts that into Python 3.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">output</span>(x):
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> x:
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#34;&#34;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>        print(x[<span style="color:#a5d6ff">0</span>], end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> x[<span style="color:#a5d6ff">1</span>:]:
</span></span><span style="display:flex;"><span>            print(<span style="color:#a5d6ff">&#34;</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#34;</span>, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#a5d6ff">&#34; &#34;</span>, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>            output(x[<span style="color:#a5d6ff">1</span>:])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>output([<span style="color:#a5d6ff">&#34;just&#34;</span>, <span style="color:#a5d6ff">&#34;another&#34;</span>, <span style="color:#a5d6ff">&#34;python&#34;</span>, <span style="color:#a5d6ff">&#34;hacker&#34;</span>])
</span></span></code></pre></div><p>Finally it executes the resulting Python string using <code>exec</code>. Obfuscation is left as an exercise for the reader.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">re</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">tokenize</span>(input):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;Tokenizes an input stream into a list of recognizable tokens&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    token_res <span style="color:#ff7b72;font-weight:bold">=</span> (
</span></span><span style="display:flex;"><span>        <span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;\(&#39;</span>,      <span style="color:#8b949e;font-style:italic"># open paren -&gt; starts expression</span>
</span></span><span style="display:flex;"><span>        <span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;\)&#39;</span>,      <span style="color:#8b949e;font-style:italic"># close paren -&gt; ends expression</span>
</span></span><span style="display:flex;"><span>        <span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;&#34;[^&#34;]*&#34;&#39;</span>, <span style="color:#8b949e;font-style:italic"># quoted string (don&#39;t support \&#34; yet)</span>
</span></span><span style="display:flex;"><span>        <span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;[\w?]+&#39;</span>   <span style="color:#8b949e;font-style:italic"># atom</span>
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> re<span style="color:#ff7b72;font-weight:bold">.</span>findall(<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;(&#39;</span> <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#39;|&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(token_res) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#39;)&#39;</span>, input)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">parse</span>(stream):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;Parses a token stream into a syntax tree&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> stream:
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># Build a list of arguments (possibly expressions) at this level</span>
</span></span><span style="display:flex;"><span>        args <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">while</span> <span style="color:#79c0ff">True</span>:
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># Get the next token</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">try</span>:
</span></span><span style="display:flex;"><span>                x <span style="color:#ff7b72;font-weight:bold">=</span> stream<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">except</span> <span style="color:#f0883e;font-weight:bold">IndexError</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">return</span> args
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># ( and ) control the level of the tree we&#39;re at</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">if</span> x <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;(&#39;</span>:
</span></span><span style="display:flex;"><span>                args<span style="color:#ff7b72;font-weight:bold">.</span>append(parse(stream))
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">elif</span> x <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;)&#39;</span>:
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">return</span> args
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>                args<span style="color:#ff7b72;font-weight:bold">.</span>append(x)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">compile</span>(tree):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;Compiles an Scheme Abstract Syntax Tree into near-Python&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">compile_expr</span>(indent, expr):
</span></span><span style="display:flex;"><span>        indent <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        lines <span style="color:#ff7b72;font-weight:bold">=</span> [] <span style="color:#8b949e;font-style:italic"># these will have [(indent, statement), ...] structure</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">while</span> expr:
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># Two options: expr is a string like &#34;&#39;&#34; or it is a list</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">if</span> isinstance(expr, str):
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">return</span> [(
</span></span><span style="display:flex;"><span>                    indent,
</span></span><span style="display:flex;"><span>                    expr<span style="color:#ff7b72;font-weight:bold">.</span>replace(<span style="color:#a5d6ff">&#39;scheme&#39;</span>, <span style="color:#a5d6ff">&#39;python&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>replace(<span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#39;</span>, <span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\\</span><span style="color:#a5d6ff">n&#39;</span>)
</span></span><span style="display:flex;"><span>                )]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>                start <span style="color:#ff7b72;font-weight:bold">=</span> expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">if</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;define&#39;</span>:
</span></span><span style="display:flex;"><span>                    signature <span style="color:#ff7b72;font-weight:bold">=</span> expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((indent,
</span></span><span style="display:flex;"><span>                        <span style="color:#a5d6ff">&#39;def </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">(</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">):&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (
</span></span><span style="display:flex;"><span>                            signature[<span style="color:#a5d6ff">0</span>],
</span></span><span style="display:flex;"><span>                            <span style="color:#a5d6ff">&#39;, &#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(signature[<span style="color:#a5d6ff">1</span>:])
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>                    ))
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">while</span> expr:
</span></span><span style="display:flex;"><span>                        lines<span style="color:#ff7b72;font-weight:bold">.</span>extend(compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;if&#39;</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#8b949e;font-style:italic"># We don&#39;t support multi-clause conditionals yet</span>
</span></span><span style="display:flex;"><span>                    clause <span style="color:#ff7b72;font-weight:bold">=</span> compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((indent, <span style="color:#a5d6ff">&#39;if </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">:&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> clause))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    if_true_lines <span style="color:#ff7b72;font-weight:bold">=</span> compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))
</span></span><span style="display:flex;"><span>                    if_false_lines <span style="color:#ff7b72;font-weight:bold">=</span> compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>extend(if_true_lines)
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((indent, <span style="color:#a5d6ff">&#39;else:&#39;</span>))
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>extend(if_false_lines)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;null?&#39;</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#8b949e;font-style:italic"># Only supports conditionals of the form (null? foo)</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">if</span> isinstance(expr[<span style="color:#a5d6ff">0</span>], str):
</span></span><span style="display:flex;"><span>                        condition <span style="color:#ff7b72;font-weight:bold">=</span> expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>                        condition <span style="color:#ff7b72;font-weight:bold">=</span> compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">return</span> [(indent, <span style="color:#a5d6ff">&#39;not </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> condition)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;begin&#39;</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#8b949e;font-style:italic"># This is just a series of statements, so don&#39;t indent</span>
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">while</span> expr:
</span></span><span style="display:flex;"><span>                        lines<span style="color:#ff7b72;font-weight:bold">.</span>extend(compile_expr(indent<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;display&#39;</span>:
</span></span><span style="display:flex;"><span>                    arguments <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">while</span> expr:
</span></span><span style="display:flex;"><span>                        arguments<span style="color:#ff7b72;font-weight:bold">.</span>append(
</span></span><span style="display:flex;"><span>                            compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((
</span></span><span style="display:flex;"><span>                        indent,
</span></span><span style="display:flex;"><span>                        <span style="color:#a5d6ff">&#34;print(</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">, end=&#39;&#39;)&#34;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (<span style="color:#a5d6ff">&#39;, &#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(arguments))
</span></span><span style="display:flex;"><span>                    ))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;car&#39;</span>:
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((indent, <span style="color:#a5d6ff">&#39;</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">[0]&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;cdr&#39;</span>:
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((indent, <span style="color:#a5d6ff">&#39;</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">[1:]&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">elif</span> start <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;list&#39;</span>:
</span></span><span style="display:flex;"><span>                    arguments <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">while</span> expr:
</span></span><span style="display:flex;"><span>                        arguments<span style="color:#ff7b72;font-weight:bold">.</span>append(
</span></span><span style="display:flex;"><span>                            compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((indent, <span style="color:#a5d6ff">&#39;[</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">]&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> <span style="color:#a5d6ff">&#39;, &#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(arguments)))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>                    <span style="color:#8b949e;font-style:italic"># Assume this is a function call</span>
</span></span><span style="display:flex;"><span>                    arguments <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">while</span> expr:
</span></span><span style="display:flex;"><span>                        arguments<span style="color:#ff7b72;font-weight:bold">.</span>append(
</span></span><span style="display:flex;"><span>                            compile_expr(indent, expr<span style="color:#ff7b72;font-weight:bold">.</span>pop(<span style="color:#a5d6ff">0</span>))[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>                        )
</span></span><span style="display:flex;"><span>                    lines<span style="color:#ff7b72;font-weight:bold">.</span>append((
</span></span><span style="display:flex;"><span>                        indent,
</span></span><span style="display:flex;"><span>                        <span style="color:#a5d6ff">&#34;</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">(</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff">)&#34;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (start, <span style="color:#a5d6ff">&#39;, &#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(arguments))
</span></span><span style="display:flex;"><span>                    ))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> lines
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> [compile_expr(<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>, expr) <span style="color:#ff7b72">for</span> expr <span style="color:#ff7b72;font-weight:bold">in</span> tree]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    scheme <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        (define (output x)
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">            (if (null? x)
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">                &#34;&#34;
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">                (begin (display (car x))
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">                       (if (null? (cdr x))
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">                           (display &#34;</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">                           (begin (display &#34; &#34;)
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">                                  (output (cdr x)))))))
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">        (output (list &#34;just&#34; &#34;another&#34; &#34;scheme&#34; &#34;hacker&#34;))
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    python <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> expr <span style="color:#ff7b72;font-weight:bold">in</span> compile(parse(tokenize(scheme))):
</span></span><span style="display:flex;"><span>        python <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join([(<span style="color:#a5d6ff">&#39; &#39;</span> <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#a5d6ff">4</span> <span style="color:#ff7b72;font-weight:bold">*</span> x[<span style="color:#a5d6ff">0</span>]) <span style="color:#ff7b72;font-weight:bold">+</span> x[<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72">for</span> x <span style="color:#ff7b72;font-weight:bold">in</span> expr]) <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\n\n</span><span style="color:#a5d6ff">&#39;</span>
</span></span><span style="display:flex;"><span>    exec(python)
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: Turing Machine</title>
      <link>https://ryanjoneil.dev/posts/2011-04-18-reformed-japhs-turing-machine/</link>
      <pubDate>Mon, 18 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-18-reformed-japhs-turing-machine/</guid>
      <description>Python obfuscation with a Turing machine</description>
      <content:encoded><![CDATA[<p><em>Note: This post was edited for clarity.</em></p>
<p>This JAPH uses a <a href="https://en.wikipedia.org/wiki/Turing_machine">Turing machine</a>. The machine accepts any string that ends in <code>'\n'</code> and allows side effects. This lets us print the value of the tape as it encounters each character. While the idea of using lambda functions as side effects in a Turing machine is a little bizarre on many levels, we work with what we have. And Python is multi-paradigmatic, so what the heck.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">re</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">turing</span>(tape, transitions):
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># The tape input comes in as a string.  We approximate an infinite</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># length tape via a hash, so we need to convert this to {index: value}</span>
</span></span><span style="display:flex;"><span>    tape_hash <span style="color:#ff7b72;font-weight:bold">=</span> {i: x <span style="color:#ff7b72">for</span> i, x <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(tape)}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Start at 0 using our transition matrix</span>
</span></span><span style="display:flex;"><span>    index <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span>    state <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">while</span> <span style="color:#79c0ff">True</span>:
</span></span><span style="display:flex;"><span>        value <span style="color:#ff7b72;font-weight:bold">=</span> tape_hash<span style="color:#ff7b72;font-weight:bold">.</span>get(index, <span style="color:#a5d6ff">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># This is a modified Turing machine: it uses regexen</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># and has side effects.  Oh well, I needed IO.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> rule <span style="color:#ff7b72;font-weight:bold">in</span> transitions[state]:
</span></span><span style="display:flex;"><span>            regex, next, direction, new_value, side_effect <span style="color:#ff7b72;font-weight:bold">=</span> rule
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">if</span> re<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#ff7b72">match</span>(regex, value):
</span></span><span style="display:flex;"><span>                <span style="color:#8b949e;font-style:italic"># Terminal states</span>
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">if</span> new_value <span style="color:#ff7b72;font-weight:bold">in</span> (<span style="color:#a5d6ff">&#39;YES&#39;</span>, <span style="color:#a5d6ff">&#39;NO&#39;</span>):
</span></span><span style="display:flex;"><span>                    <span style="color:#ff7b72">return</span> new_value
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>                tape_hash[index] <span style="color:#ff7b72;font-weight:bold">=</span> new_value
</span></span><span style="display:flex;"><span>                side_effect(value)
</span></span><span style="display:flex;"><span>                index <span style="color:#ff7b72;font-weight:bold">+=</span> direction
</span></span><span style="display:flex;"><span>                state <span style="color:#ff7b72;font-weight:bold">=</span> next
</span></span><span style="display:flex;"><span>                <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">assert</span> <span style="color:#a5d6ff">&#39;YES&#39;</span> <span style="color:#ff7b72;font-weight:bold">==</span> turing(<span style="color:#a5d6ff">&#39;just another python hacker</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#39;</span>, [
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># This Turing machine recognizes the language of strings that end in \n.</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Regex rule, next state, left/right = -1/+1, new value, side effect.</span>
</span></span><span style="display:flex;"><span>    [ <span style="color:#8b949e;font-style:italic"># State 0:</span>
</span></span><span style="display:flex;"><span>        [<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^[a-z ]$&#39;</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#39;&#39;</span>, <span style="color:#ff7b72">lambda</span> x: print(x, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)],
</span></span><span style="display:flex;"><span>        [<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^\n$&#39;</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#39;&#39;</span>, <span style="color:#ff7b72">lambda</span> x: print(x, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)],
</span></span><span style="display:flex;"><span>        [<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^.*$&#39;</span>, <span style="color:#a5d6ff">0</span>, <span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#39;NO&#39;</span>, <span style="color:#79c0ff">None</span>],
</span></span><span style="display:flex;"><span>    ],
</span></span><span style="display:flex;"><span>    [ <span style="color:#8b949e;font-style:italic"># State 1:</span>
</span></span><span style="display:flex;"><span>        [<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^$&#39;</span>, <span style="color:#a5d6ff">1</span>, <span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">&#39;YES&#39;</span>, <span style="color:#79c0ff">None</span>]
</span></span><span style="display:flex;"><span>    ]
</span></span><span style="display:flex;"><span>])
</span></span></code></pre></div><p>Obfuscation again consists of converting the above code into lambda functions using Y combinators. This is a nice programming exercise, so I&rsquo;ve left it out of this post in case anyone wants to try. The Turing machine has to return <code>'YES'</code> to indicate that it accepts the string, thus the assertion. Our final obfuscated JAPH is a single expression.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">assert</span><span style="color:#a5d6ff">&#39;&#39;&#39;YES&#39;&#39;&#39;</span><span style="color:#ff7b72;font-weight:bold">==</span>(<span style="color:#ff7b72">lambda</span> g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg)))(<span style="color:#ff7b72">lambda</span> f:g(
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">lambda</span> arg: f(f)(arg))))(<span style="color:#ff7b72">lambda</span> f: <span style="color:#ff7b72">lambda</span> q:[(<span style="color:#ff7b72">lambda</span> g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span>
</span></span><span style="display:flex;"><span>arg:f(f)(arg)))(<span style="color:#ff7b72">lambda</span> f: g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))))(<span style="color:#ff7b72">lambda</span> f: <span style="color:#ff7b72">lambda</span> x:(x
</span></span><span style="display:flex;"><span>[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">and</span> __import__(<span style="color:#a5d6ff">&#39;re&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#ff7b72">match</span>(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">1</span>])<span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>:]
</span></span><span style="display:flex;"><span>,x[<span style="color:#a5d6ff">1</span>]]))) ([q[<span style="color:#a5d6ff">3</span>][q[<span style="color:#a5d6ff">1</span>]],q[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72;font-weight:bold">.</span>get(q[<span style="color:#a5d6ff">0</span>],<span style="color:#a5d6ff">&#39;&#39;</span>)])[<span style="color:#a5d6ff">4</span>](q[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72;font-weight:bold">.</span>get(q[<span style="color:#a5d6ff">0</span>],<span style="color:#a5d6ff">&#39;&#39;</span>)), (<span style="color:#ff7b72">lambda</span>
</span></span><span style="display:flex;"><span>g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))) (<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))))(
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">lambda</span> f:<span style="color:#ff7b72">lambda</span> x:(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">and</span> __import__(<span style="color:#a5d6ff">&#39;re&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#ff7b72">match</span>(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>],x
</span></span><span style="display:flex;"><span>[<span style="color:#a5d6ff">1</span>])<span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>:],x[<span style="color:#a5d6ff">1</span>]])))([q[<span style="color:#a5d6ff">3</span>][q[<span style="color:#a5d6ff">1</span>]],q[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72;font-weight:bold">.</span>get(q[<span style="color:#a5d6ff">0</span>],<span style="color:#a5d6ff">&#39;&#39;</span>)])[<span style="color:#a5d6ff">3</span>]<span style="color:#ff7b72">if</span>(<span style="color:#ff7b72">lambda</span>
</span></span><span style="display:flex;"><span>g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))) (<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))))(
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">lambda</span> f:<span style="color:#ff7b72">lambda</span> x:(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72;font-weight:bold">and</span> __import__(<span style="color:#a5d6ff">&#39;re&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#ff7b72">match</span>(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>],x[
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">1</span>]) <span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>:],x[<span style="color:#a5d6ff">1</span>]])))([q[<span style="color:#a5d6ff">3</span>][q[<span style="color:#a5d6ff">1</span>]],q[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72;font-weight:bold">.</span>get(q[<span style="color:#a5d6ff">0</span>],<span style="color:#a5d6ff">&#39;&#39;</span>)])[<span style="color:#a5d6ff">3</span>]<span style="color:#ff7b72;font-weight:bold">in</span>(<span style="color:#a5d6ff">&#39;YES&#39;</span>,
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;NO&#39;</span>)<span style="color:#ff7b72">else</span> f([q[<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72;font-weight:bold">+</span>(<span style="color:#ff7b72">lambda</span> g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg)))(<span style="color:#ff7b72">lambda</span> f:g
</span></span><span style="display:flex;"><span>(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))))(<span style="color:#ff7b72">lambda</span> f:<span style="color:#ff7b72">lambda</span> x:(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72;font-weight:bold">and</span> __import__(
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;re&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#ff7b72">match</span>(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">1</span>])<span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>:], x[<span style="color:#a5d6ff">1</span>]])))([q[<span style="color:#a5d6ff">3</span>][q[<span style="color:#a5d6ff">1</span>]], q[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72;font-weight:bold">.</span>
</span></span><span style="display:flex;"><span>get(q[<span style="color:#a5d6ff">0</span>],<span style="color:#a5d6ff">&#39;&#39;</span>)])[<span style="color:#a5d6ff">2</span>],(<span style="color:#ff7b72">lambda</span> g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg: f(f)(arg)))(<span style="color:#ff7b72">lambda</span> f:
</span></span><span style="display:flex;"><span>g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg))))(<span style="color:#ff7b72">lambda</span> f:<span style="color:#ff7b72">lambda</span> x:(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72;font-weight:bold">and</span> __import__
</span></span><span style="display:flex;"><span>(<span style="color:#a5d6ff">&#39;re&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#ff7b72">match</span>(x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">1</span>])<span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">1</span>:], x[<span style="color:#a5d6ff">1</span>]])))([q[<span style="color:#a5d6ff">3</span>][q[<span style="color:#a5d6ff">1</span>]],q[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72;font-weight:bold">.</span>
</span></span><span style="display:flex;"><span>get(q[<span style="color:#a5d6ff">0</span>],<span style="color:#a5d6ff">&#39;&#39;</span>)])[<span style="color:#a5d6ff">1</span>],q[<span style="color:#a5d6ff">2</span>],q[<span style="color:#a5d6ff">3</span>]])][<span style="color:#a5d6ff">1</span>])([<span style="color:#a5d6ff">0</span>,<span style="color:#a5d6ff">0</span>,{i:x <span style="color:#ff7b72">for</span> i,x <span style="color:#ff7b72;font-weight:bold">in</span> enumerate(<span style="color:#a5d6ff">&#39;just &#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;another python hacker</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#39;</span>)}, [[[<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^[a-z ]$&#39;</span>,<span style="color:#a5d6ff">0</span>,<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">&#39;&#39;</span>,<span style="color:#ff7b72">lambda</span> x:print(x,end<span style="color:#ff7b72;font-weight:bold">=</span>
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;&#39;</span>)], [<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^\n$&#39;</span>,<span style="color:#a5d6ff">1</span>,<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">&#39;&#39;</span>,<span style="color:#ff7b72">lambda</span> x:print(x, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)],[<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;^.*$&#39;</span>,<span style="color:#a5d6ff">0</span>,<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">&#39;&#39;&#39;NO&#39;&#39;&#39;</span>,
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">lambda</span> x:<span style="color:#79c0ff">None</span>]], [[<span style="color:#79c0ff">r</span><span style="color:#a5d6ff">&#39;&#39;&#39;^$&#39;&#39;&#39;</span>,<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>,<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>,<span style="color:#a5d6ff">&#39;&#39;&#39;YES&#39;&#39;&#39;</span>, <span style="color:#ff7b72">lambda</span> x: <span style="color:#79c0ff">None</span> <span style="color:#ff7b72;font-weight:bold">or</span> <span style="color:#79c0ff">None</span>]]]])
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: Huffman Coding</title>
      <link>https://ryanjoneil.dev/posts/2011-04-14-reformed-japhs-huffman-coding/</link>
      <pubDate>Thu, 14 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-14-reformed-japhs-huffman-coding/</guid>
      <description>Python obfuscation and Huffman coding</description>
      <content:encoded><![CDATA[<p><em>Note: This post was edited for clarity.</em></p>
<p>At this point, tricking <code>python</code> into printing strings via indirect means got a little boring. So I switched to obfuscating fundamental computer science algorithms. Here&rsquo;s a JAPH that takes in a <a href="https://en.wikipedia.org/wiki/Huffman_coding">Huffman coded</a> version of <code>'just another python hacker'</code>, decodes, and prints it.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Build coding tree</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">build_tree</span>(scheme):
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> scheme<span style="color:#ff7b72;font-weight:bold">.</span>startswith(<span style="color:#a5d6ff">&#39;*&#39;</span>):
</span></span><span style="display:flex;"><span>        left, scheme <span style="color:#ff7b72;font-weight:bold">=</span> build_tree(scheme[<span style="color:#a5d6ff">1</span>:])
</span></span><span style="display:flex;"><span>        right, scheme <span style="color:#ff7b72;font-weight:bold">=</span> build_tree(scheme)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> (left, right), scheme
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">return</span> scheme[<span style="color:#a5d6ff">0</span>], scheme[<span style="color:#a5d6ff">1</span>:]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">decode</span>(tree, encoded):
</span></span><span style="display:flex;"><span>    ret <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    node <span style="color:#ff7b72;font-weight:bold">=</span> tree
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> direction <span style="color:#ff7b72;font-weight:bold">in</span> encoded:
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> direction <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;0&#39;</span>:
</span></span><span style="display:flex;"><span>            node <span style="color:#ff7b72;font-weight:bold">=</span> node[<span style="color:#a5d6ff">0</span>]
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>            node <span style="color:#ff7b72;font-weight:bold">=</span> node[<span style="color:#a5d6ff">1</span>]
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> isinstance(node, str):
</span></span><span style="display:flex;"><span>            ret <span style="color:#ff7b72;font-weight:bold">+=</span> node
</span></span><span style="display:flex;"><span>            node <span style="color:#ff7b72;font-weight:bold">=</span> tree
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> ret
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tree <span style="color:#ff7b72;font-weight:bold">=</span> build_tree(<span style="color:#a5d6ff">&#39;*****ju*sp*er***yct* h**ka*no&#39;</span>)[<span style="color:#a5d6ff">0</span>]
</span></span><span style="display:flex;"><span>print(
</span></span><span style="display:flex;"><span>    decode(tree, bin(<span style="color:#a5d6ff">10627344201836243859174935587</span>)<span style="color:#ff7b72;font-weight:bold">.</span>lstrip(<span style="color:#a5d6ff">&#39;0b&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>zfill(<span style="color:#a5d6ff">103</span>))
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The decoding tree is like a LISP-style sequence of pairs. <code>'*'</code> represents a branch in the tree while other characters are leaf nodes. This looks like the following.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>(
</span></span><span style="display:flex;"><span>    (
</span></span><span style="display:flex;"><span>        (
</span></span><span style="display:flex;"><span>            (
</span></span><span style="display:flex;"><span>                (<span style="color:#a5d6ff">&#39;j&#39;</span>, <span style="color:#a5d6ff">&#39;u&#39;</span>), 
</span></span><span style="display:flex;"><span>                (<span style="color:#a5d6ff">&#39;s&#39;</span>, <span style="color:#a5d6ff">&#39;p&#39;</span>)
</span></span><span style="display:flex;"><span>            ), 
</span></span><span style="display:flex;"><span>            (<span style="color:#a5d6ff">&#39;e&#39;</span>, <span style="color:#a5d6ff">&#39;r&#39;</span>)
</span></span><span style="display:flex;"><span>        ), 
</span></span><span style="display:flex;"><span>        (
</span></span><span style="display:flex;"><span>            (
</span></span><span style="display:flex;"><span>                (<span style="color:#a5d6ff">&#39;y&#39;</span>, <span style="color:#a5d6ff">&#39;c&#39;</span>), 
</span></span><span style="display:flex;"><span>                <span style="color:#a5d6ff">&#39;t&#39;</span>
</span></span><span style="display:flex;"><span>            ), 
</span></span><span style="display:flex;"><span>            (<span style="color:#a5d6ff">&#39; &#39;</span>, <span style="color:#a5d6ff">&#39;h&#39;</span>)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    ), 
</span></span><span style="display:flex;"><span>    (
</span></span><span style="display:flex;"><span>        (<span style="color:#a5d6ff">&#39;k&#39;</span>, <span style="color:#a5d6ff">&#39;a&#39;</span>), 
</span></span><span style="display:flex;"><span>        (<span style="color:#a5d6ff">&#39;n&#39;</span>, <span style="color:#a5d6ff">&#39;o&#39;</span>)
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The actual Huffman coded version of our favorite string gets about 50% smaller represented in base-2.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>0000000001000100101011010111011101010111001000110110000110100001010111111110011001111010100110000100011
</span></span></code></pre></div><p>There&rsquo;s a catch here, which is that this is hard to obfuscate unless we turn it into a single expression. This means that we have to convert <code>build_tree</code> and <code>decode</code> into lambda functions. Unfortunately, they are recursive and lambda functions recurse naturally. Fortunately, we can use <a href="https://code.activestate.com/recipes/576366-y-combinator/">Y combinators</a> to get around the problem. These are worth some study since they will pop up again in future JAPHs.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>Y <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72">lambda</span> g: (
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">lambda</span> f: g(<span style="color:#ff7b72">lambda</span> arg: f(f)(arg))) (<span style="color:#ff7b72">lambda</span> f: g(<span style="color:#ff7b72">lambda</span> arg: f(f)(arg))
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>build_tree <span style="color:#ff7b72;font-weight:bold">=</span> Y(
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">lambda</span> f: <span style="color:#ff7b72">lambda</span> scheme: (
</span></span><span style="display:flex;"><span>        (f(scheme[<span style="color:#a5d6ff">1</span>:])[<span style="color:#a5d6ff">0</span>], f(f(scheme[<span style="color:#a5d6ff">1</span>:])[<span style="color:#a5d6ff">1</span>])[<span style="color:#a5d6ff">0</span>]),
</span></span><span style="display:flex;"><span>        f(f(scheme[<span style="color:#a5d6ff">1</span>:])[<span style="color:#a5d6ff">1</span>])[<span style="color:#a5d6ff">1</span> ]
</span></span><span style="display:flex;"><span>    ) <span style="color:#ff7b72">if</span> scheme<span style="color:#ff7b72;font-weight:bold">.</span>startswith(<span style="color:#a5d6ff">&#39;*&#39;</span>) <span style="color:#ff7b72">else</span> (scheme[<span style="color:#a5d6ff">0</span>], scheme[<span style="color:#a5d6ff">1</span>:])
</span></span><span style="display:flex;"><span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>decode <span style="color:#ff7b72;font-weight:bold">=</span> Y(<span style="color:#ff7b72">lambda</span> f: <span style="color:#ff7b72">lambda</span> x: x[<span style="color:#a5d6ff">3</span>]<span style="color:#ff7b72;font-weight:bold">+</span>x[<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> x[<span style="color:#a5d6ff">2</span>] <span style="color:#ff7b72">else</span> (
</span></span><span style="display:flex;"><span>    f([x[<span style="color:#a5d6ff">0</span>], x[<span style="color:#a5d6ff">0</span>], x[<span style="color:#a5d6ff">2</span>], x[<span style="color:#a5d6ff">3</span>]<span style="color:#ff7b72;font-weight:bold">+</span>x[<span style="color:#a5d6ff">1</span>]]) <span style="color:#ff7b72">if</span> isinstance(x[<span style="color:#a5d6ff">1</span>], str) <span style="color:#ff7b72">else</span> (
</span></span><span style="display:flex;"><span>        f([x[<span style="color:#a5d6ff">0</span>], x[<span style="color:#a5d6ff">1</span>][<span style="color:#a5d6ff">0</span>], x[<span style="color:#a5d6ff">2</span>][<span style="color:#a5d6ff">1</span>:], x[<span style="color:#a5d6ff">3</span>]]) <span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">2</span>][<span style="color:#a5d6ff">0</span>] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;0&#39;</span> <span style="color:#ff7b72">else</span> (
</span></span><span style="display:flex;"><span>            f([x[<span style="color:#a5d6ff">0</span>], x[<span style="color:#a5d6ff">1</span>][<span style="color:#a5d6ff">1</span>], x[<span style="color:#a5d6ff">2</span>][<span style="color:#a5d6ff">1</span>:], x[<span style="color:#a5d6ff">3</span>]])
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>tree <span style="color:#ff7b72;font-weight:bold">=</span> build_tree(<span style="color:#a5d6ff">&#39;*****ju*sp*er***yct* h**ka*no&#39;</span>)[<span style="color:#a5d6ff">0</span>]
</span></span><span style="display:flex;"><span>print(
</span></span><span style="display:flex;"><span>    decode([
</span></span><span style="display:flex;"><span>        tree,
</span></span><span style="display:flex;"><span>        tree,
</span></span><span style="display:flex;"><span>        bin(<span style="color:#a5d6ff">10627344201836243859174935587</span>)<span style="color:#ff7b72;font-weight:bold">.</span>lstrip(<span style="color:#a5d6ff">&#39;0b&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>zfill(<span style="color:#a5d6ff">103</span>), <span style="color:#a5d6ff">&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    ])
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The final version is a condensed (and expanded, oddly) version of the above.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print((<span style="color:#ff7b72">lambda</span> t,e,s:(<span style="color:#ff7b72">lambda</span> g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg)))(<span style="color:#ff7b72">lambda</span> f:
</span></span><span style="display:flex;"><span>g(<span style="color:#ff7b72">lambda</span> arg: f(f)(arg))))(<span style="color:#ff7b72">lambda</span> f:<span style="color:#ff7b72">lambda</span> x: x[<span style="color:#a5d6ff">3</span>]<span style="color:#ff7b72;font-weight:bold">+</span>x[<span style="color:#a5d6ff">1</span>]<span style="color:#ff7b72">if</span> <span style="color:#ff7b72;font-weight:bold">not</span> x[<span style="color:#a5d6ff">2</span>]<span style="color:#ff7b72">else</span> f([
</span></span><span style="display:flex;"><span>x[<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">2</span>],x[<span style="color:#a5d6ff">3</span>]<span style="color:#ff7b72;font-weight:bold">+</span>x[<span style="color:#a5d6ff">1</span>]])<span style="color:#ff7b72">if</span> isinstance(x[<span style="color:#a5d6ff">1</span>],str)<span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">1</span>][<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">2</span>]
</span></span><span style="display:flex;"><span>[<span style="color:#a5d6ff">1</span>:],x[<span style="color:#a5d6ff">3</span>]])<span style="color:#ff7b72">if</span> x[<span style="color:#a5d6ff">2</span>][<span style="color:#a5d6ff">0</span>]<span style="color:#ff7b72;font-weight:bold">==</span><span style="color:#a5d6ff">&#39;0&#39;</span><span style="color:#ff7b72">else</span> f([x[<span style="color:#a5d6ff">0</span>],x[<span style="color:#a5d6ff">1</span>][<span style="color:#a5d6ff">1</span>],x[<span style="color:#a5d6ff">2</span>][<span style="color:#a5d6ff">1</span>:],x[<span style="color:#a5d6ff">3</span>]]))([t,t,e,s])
</span></span><span style="display:flex;"><span>)((<span style="color:#ff7b72">lambda</span> g:(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(arg)))(<span style="color:#ff7b72">lambda</span> f:g(<span style="color:#ff7b72">lambda</span> arg:f(f)(
</span></span><span style="display:flex;"><span>arg))))(<span style="color:#ff7b72">lambda</span> f:<span style="color:#ff7b72">lambda</span> p:((f(p[<span style="color:#a5d6ff">1</span>:])[<span style="color:#a5d6ff">0</span>],f(f(p[<span style="color:#a5d6ff">1</span>:])[<span style="color:#a5d6ff">1</span>])[<span style="color:#a5d6ff">0</span>]),f(f(p[<span style="color:#a5d6ff">1</span>:])[<span style="color:#a5d6ff">1</span>])[
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">1</span>])<span style="color:#ff7b72">if</span> p<span style="color:#ff7b72;font-weight:bold">.</span>startswith(<span style="color:#a5d6ff">&#39;*&#39;</span>)<span style="color:#ff7b72">else</span>(p[<span style="color:#a5d6ff">0</span>],p[<span style="color:#a5d6ff">1</span>:]))(<span style="color:#a5d6ff">&#39;*****ju*sp*er***yct* h**ka*no&#39;</span>)[
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">0</span>],bin(<span style="color:#a5d6ff">10627344201836243859179756385</span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">4820798</span>)<span style="color:#ff7b72;font-weight:bold">.</span>lstrip(<span style="color:#a5d6ff">&#39;0b&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>zfill(<span style="color:#a5d6ff">103</span>),<span style="color:#a5d6ff">&#39;&#39;</span>))
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: Rolling Effect</title>
      <link>https://ryanjoneil.dev/posts/2011-04-11-reformed-japhs-rolling-effect/</link>
      <pubDate>Mon, 11 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-11-reformed-japhs-rolling-effect/</guid>
      <description>Python obfuscation with a cute visual effect</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3.12. It may not work with different versions.</em></p>
<p>Here&rsquo;s a JAPH composed solely for effect. For each letter in <code>'just another python hacker'</code> it loops over each the characters <code>' abcdefghijklmnopqrstuvwxyz'</code>, printing each. Between characters it pauses for 0.05 seconds, backing up and moving on to the next if it hasn&rsquo;t reached the desired one yet. This achieves a sort of rolling effect by which the final string appears on our screen over time.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">string</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">sys</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">time</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>letters <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39; &#39;</span> <span style="color:#ff7b72;font-weight:bold">+</span> string<span style="color:#ff7b72;font-weight:bold">.</span>ascii_lowercase
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> l <span style="color:#ff7b72;font-weight:bold">in</span> <span style="color:#a5d6ff">&#39;just another python hacker&#39;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> x <span style="color:#ff7b72;font-weight:bold">in</span> letters:
</span></span><span style="display:flex;"><span>        print(x, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>        sys<span style="color:#ff7b72;font-weight:bold">.</span>stdout<span style="color:#ff7b72;font-weight:bold">.</span>flush()
</span></span><span style="display:flex;"><span>        time<span style="color:#ff7b72;font-weight:bold">.</span>sleep(<span style="color:#a5d6ff">0.05</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> x <span style="color:#ff7b72;font-weight:bold">==</span> l:
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>            print(<span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\b</span><span style="color:#a5d6ff">&#39;</span>, end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print()
</span></span></code></pre></div><p>We locate and print each letter in the string with a list comprehension.  At the end we have an extra line of code (the eval statement) that gives us our newline.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>[[(<span style="color:#ff7b72">lambda</span> x,l:str(print(x,end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;&#39;</span>))<span style="color:#ff7b72;font-weight:bold">+</span>str(__import__(print<span style="color:#ff7b72;font-weight:bold">.</span>
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">__doc__</span>[print<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#79c0ff">__doc__</span><span style="color:#ff7b72;font-weight:bold">.</span>index(<span style="color:#a5d6ff">&#39;stdout&#39;</span>) <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">4</span>:print<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#79c0ff">__doc__</span><span style="color:#ff7b72;font-weight:bold">.</span>
</span></span><span style="display:flex;"><span>index(<span style="color:#a5d6ff">&#39;stdout&#39;</span>)<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>])<span style="color:#ff7b72;font-weight:bold">.</span>stdout<span style="color:#ff7b72;font-weight:bold">.</span>flush()) <span style="color:#ff7b72;font-weight:bold">+</span> str(__import__(<span style="color:#a5d6ff">&#39;&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>
</span></span><span style="display:flex;"><span>join(reversed(<span style="color:#a5d6ff">&#39;emit&#39;</span>)))<span style="color:#ff7b72;font-weight:bold">.</span>sleep(<span style="color:#a5d6ff">0o5</span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">1.01</span><span style="color:#ff7b72;font-weight:bold">/</span><span style="color:#a5d6ff">0x64</span>))<span style="color:#ff7b72;font-weight:bold">+</span>str(print(
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\b</span><span style="color:#a5d6ff">&#39;</span>,end<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x09</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>strip())<span style="color:#ff7b72">if</span> x<span style="color:#ff7b72;font-weight:bold">!=</span>l <span style="color:#ff7b72">else</span><span style="color:#a5d6ff">&#39;*&amp;#&#39;</span>))(x1,l1)<span style="color:#ff7b72">for</span> x1
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">in</span>(<span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x20</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>getattr(__import__(type(<span style="color:#a5d6ff">&#39;phear&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#79c0ff">__name__</span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;in&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;g&#39;</span>),dir(__import__(type(<span style="color:#a5d6ff">&#39;snarf&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#79c0ff">__name__</span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;ing&#39;</span>))[<span style="color:#a5d6ff">15</span>]))
</span></span><span style="display:flex;"><span>[:(<span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x20</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>getattr(__import__(type(<span style="color:#a5d6ff">&#39;smear&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#79c0ff">__name__</span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;in&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;g&#39;</span>),dir(__import__(type(<span style="color:#a5d6ff">&#39;slurp&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span><span style="color:#79c0ff">__name__</span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;ing&#39;</span>))[<span style="color:#a5d6ff">15</span>]))
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">.</span>index(l1)<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>]]<span style="color:#ff7b72">for</span> l1 <span style="color:#ff7b72;font-weight:bold">in</span><span style="color:#a5d6ff">&#39;&#39;&#39;just another python hacker&#39;&#39;&#39;</span>]
</span></span><span style="display:flex;"><span>eval(<span style="color:#a5d6ff">&#39;&#39;&#39;</span><span style="color:#79c0ff">\x20\x09</span><span style="color:#a5d6ff">eval(&#34;</span><span style="color:#79c0ff">\x20\x09</span><span style="color:#a5d6ff">eval(&#39;</span><span style="color:#79c0ff">\x20</span><span style="color:#a5d6ff"> print()&#39;)&#34;)&#39;&#39;&#39;</span>)
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: ROT13</title>
      <link>https://ryanjoneil.dev/posts/2011-04-06-reformed-japhs-rot13/</link>
      <pubDate>Wed, 06 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-06-reformed-japhs-rot13/</guid>
      <description>Python obfuscation using ROT13 encoding</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3.12. It may not work with different versions.</em></p>
<p>No series of JAPHs would be complete without <a href="https://en.wikipedia.org/wiki/ROT13">ROT13</a>. This is the example through which aspiring Perl programmers learn to use <code>tr</code> and its synonym <code>y</code>. In Perl the basic ROT13 JAPH starts as:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-perl" data-lang="perl"><span style="display:flex;"><span><span style="color:#79c0ff">$foo</span> <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;whfg nabgure crey unpxre&#39;</span>;
</span></span><span style="display:flex;"><span><span style="color:#79c0ff">$foo</span> <span style="color:#ff7b72;font-weight:bold">=~</span> y<span style="color:#79c0ff">/a-z/</span>n<span style="color:#ff7b72;font-weight:bold">-</span>za<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#79c0ff">m</span><span style="color:#f85149">/;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">print</span> <span style="color:#79c0ff">$foo</span>;
</span></span></code></pre></div><p>Python has nothing quite so elegant in its default namespace. However, this does give us the opportunity to explore a little used aspect of strings: the translate method. If we construct a dictionary of ordinals we can accomplish the same thing with a touch more effort.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>table <span style="color:#ff7b72;font-weight:bold">=</span> {
</span></span><span style="display:flex;"><span>    ord(x): ord(y) <span style="color:#ff7b72">for</span> x, y <span style="color:#ff7b72;font-weight:bold">in</span> zip(
</span></span><span style="display:flex;"><span>        string<span style="color:#ff7b72;font-weight:bold">.</span>ascii_lowercase,
</span></span><span style="display:flex;"><span>        string<span style="color:#ff7b72;font-weight:bold">.</span>ascii_lowercase[<span style="color:#a5d6ff">13</span>:] <span style="color:#ff7b72;font-weight:bold">+</span> string<span style="color:#ff7b72;font-weight:bold">.</span>ascii_lowercase
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;whfg nabgure clguba unpxre&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>translate(table))
</span></span></code></pre></div><p>We obfuscate the construction of this translation dictionary and, for added measure, use <code>getattr</code> to find the <code>print</code> function off of <code>__builtins__</code>.  This will likely only work in Python 3.2, since the order of attributes on <code>__builtins__</code> matters.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>getattr(vars()[list(filter(<span style="color:#ff7b72">lambda</span> _:<span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x5f\x62</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">in</span> _,dir
</span></span><span style="display:flex;"><span>()))[<span style="color:#a5d6ff">0</span>]], dir(vars()[list(filter(<span style="color:#ff7b72">lambda</span> _:<span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x5f\x62</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">in</span>
</span></span><span style="display:flex;"><span>_, dir()))[<span style="color:#a5d6ff">0</span>]])[list(filter(<span style="color:#ff7b72">lambda</span> _:_ [<span style="color:#a5d6ff">1</span>]<span style="color:#ff7b72;font-weight:bold">.</span>startswith(
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x70\x72</span><span style="color:#a5d6ff">&#39;</span>),enumerate(dir(vars()[list(filter(<span style="color:#ff7b72">lambda</span> _:
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x5f\x62</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">in</span> _,dir()))[<span style="color:#a5d6ff">0</span>]]))))[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">0</span>]])(getattr(<span style="color:#a5d6ff">&#39;whfg &#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;&#39;&#39;nabgure clguba unpxre&#39;&#39;&#39;</span>, dir(<span style="color:#a5d6ff">&#39;0o52&#39;</span>)[<span style="color:#a5d6ff">0o116</span>])({ _:
</span></span><span style="display:flex;"><span>(_<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">0o124</span>) <span style="color:#ff7b72;font-weight:bold">%</span><span style="color:#a5d6ff">0o32</span> <span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">0o141</span> <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(<span style="color:#a5d6ff">0o141</span>, <span style="color:#a5d6ff">0o173</span>)}))
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: Ridiculous Anagram</title>
      <link>https://ryanjoneil.dev/posts/2011-04-03-reformed-japhs-ridiculous-anagram/</link>
      <pubDate>Sun, 03 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-03-reformed-japhs-ridiculous-anagram/</guid>
      <description>Python obfuscation using anagrams</description>
      <content:encoded><![CDATA[<p>Here&rsquo;s the second in my reformed JAPH series. It takes an anagram of <code>'just another python hacker'</code> and converts it prior to printing. It sorts the anagram by the indices of another string, in order of their associated characters. This is sort of like a pre-digested <a href="https://en.wikipedia.org/wiki/Schwartzian_transform">Schwartzian transform</a>.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>x <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;upjohn tehran hectors katy&#39;</span>
</span></span><span style="display:flex;"><span>y <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;1D0HG6JFO9P5ICKAM87B24NL3E&#39;</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(x[i] <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> sorted(range(len(x)), key<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#ff7b72">lambda</span> p: y[p])))
</span></span></code></pre></div><p>Obfuscation consists mostly of using silly machinations to construct the string we use to sort the anagram.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(<span style="color:#a5d6ff">&#39;&#39;&#39;upjohn tehran hectors katy&#39;&#39;&#39;</span>[_]<span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> sorted(range
</span></span><span style="display:flex;"><span>(<span style="color:#a5d6ff">26</span>),key<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#ff7b72">lambda</span> p:(hex(<span style="color:#a5d6ff">29</span>)[<span style="color:#a5d6ff">2</span>:]<span style="color:#ff7b72;font-weight:bold">.</span>upper()<span style="color:#ff7b72;font-weight:bold">+</span>str(<span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">4</span>)<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;HG&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>str(sum(
</span></span><span style="display:flex;"><span>range(<span style="color:#a5d6ff">4</span>)))<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;JFO&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>str((<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">2</span>)<span style="color:#ff7b72;font-weight:bold">**</span>(<span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">1</span>))<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;P&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>str(<span style="color:#a5d6ff">35</span><span style="color:#ff7b72;font-weight:bold">/</span><span style="color:#a5d6ff">7</span>)[:<span style="color:#a5d6ff">1</span>]<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;i.c.k.&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>replace(
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;.&#39;</span>,<span style="color:#a5d6ff">&#39;&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>upper()<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;AM&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>str(<span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">*</span>sum(range(<span style="color:#a5d6ff">5</span>))<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">3</span>)<span style="color:#ff7b72;font-weight:bold">+</span>hex(<span style="color:#a5d6ff">0o5444</span>)[<span style="color:#a5d6ff">2</span>:]<span style="color:#ff7b72;font-weight:bold">.</span>replace
</span></span><span style="display:flex;"><span>(<span style="color:#ff7b72;font-weight:bold">*</span><span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\x62</span><span style="color:#a5d6ff">|</span><span style="color:#79c0ff">\x42</span><span style="color:#a5d6ff">&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>split(<span style="color:#a5d6ff">&#39;|&#39;</span>))<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;NL&#39;</span><span style="color:#ff7b72;font-weight:bold">+</span>hex(<span style="color:#a5d6ff">0o076</span>)<span style="color:#ff7b72;font-weight:bold">.</span>split(<span style="color:#a5d6ff">&#39;x&#39;</span>)[<span style="color:#a5d6ff">1</span>]<span style="color:#ff7b72;font-weight:bold">.</span>upper())[p])))
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🐪 Reformed JAPHs: Alphabetic Indexing</title>
      <link>https://ryanjoneil.dev/posts/2011-04-01-reformed-japhs-alphabetic-indexing/</link>
      <pubDate>Fri, 01 Apr 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-04-01-reformed-japhs-alphabetic-indexing/</guid>
      <description>Python obfuscation</description>
      <content:encoded><![CDATA[<p><em>Note: This post was edited for clarity.</em></p>
<p>Many years ago, I was a Perl programmer. Then one day I became disillusioned at the progress of Perl 6 and decided to <a href="https://www.python.org/dev/peps/pep-0020/">import this</a>.</p>
<p>This seems to be a fairly common story for Perl to Python converts. While I haven&rsquo;t looked back much, there are a number of things I really miss about <code>perl</code> <em>(lower case intentional)</em>. I miss having value types in a dynamic language, magical and ill-advised use of <a href="https://www.foo.be/docs/tpj/issues/vol3_1/tpj0301-0003.html">cryptocontext</a>, and sometimes even <a href="https://web.archive.org/web/20040712204117/https://perldesignpatterns.com/?PseudoHash">pseudohashes</a> because they were inexcusably weird. A language that supports so many ideas out of the box enables an extended learning curve that lasts for <a href="https://web.archive.org/web/20020607034341/https://silver.sucs.org/~manic/humour/languages/perlhacker.htm">many years</a>. &ldquo;Perl itself is the game.&rdquo;</p>
<p>Most of all I think I miss writing Perl <a href="https://www.perlmonks.org/?node=Perl%20Poetry">poetry</a> and <a href="https://en.wikipedia.org/wiki/Just_another_Perl_hacker">JAPHs</a>. Sadly, I didn&rsquo;t keep any of those I wrote, and I&rsquo;m not competent enough with the language anymore to write interesting ones. At the time I was intentionally distancing myself from a model that was largely implicit and based on archaic systems internals and moving to one that was (supposedly) explicit and simple.</p>
<p>After switching to Python as my primary language, I used the following email signature in a nod to this change in orientation <em>(intended for Python 2)</em>:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>print <span style="color:#a5d6ff">&#39;just another python hacker&#39;</span>
</span></span></code></pre></div><p>Recently I&rsquo;ve been experimenting with writing JAPHs in Python. I think of these as &ldquo;reformed JAPHs.&rdquo; They accomplish the same purpose as programming exercises but in a more restricted context. In some ways they are more challenging. Creativity can be difficult in a narrowly defined landscape.</p>
<p>I have written a small series of reformed JAPHs which increase monotonically in complexity. Here is the first one, written in plain understandable Python 3.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">string</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>letters <span style="color:#ff7b72;font-weight:bold">=</span> string<span style="color:#ff7b72;font-weight:bold">.</span>ascii_lowercase <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">&#39; &#39;</span>
</span></span><span style="display:flex;"><span>indices <span style="color:#ff7b72;font-weight:bold">=</span> [
</span></span><span style="display:flex;"><span>     <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">20</span>, <span style="color:#a5d6ff">18</span>, <span style="color:#a5d6ff">19</span>, <span style="color:#a5d6ff">26</span>,  <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">13</span>, <span style="color:#a5d6ff">14</span>, <span style="color:#a5d6ff">19</span>, <span style="color:#a5d6ff">7</span>,  <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">17</span>, <span style="color:#a5d6ff">26</span>,
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">15</span>, <span style="color:#a5d6ff">24</span>, <span style="color:#a5d6ff">19</span>,  <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">14</span>, <span style="color:#a5d6ff">13</span>, <span style="color:#a5d6ff">26</span>,  <span style="color:#a5d6ff">7</span>,  <span style="color:#a5d6ff">0</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">10</span>,  <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">17</span>
</span></span><span style="display:flex;"><span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(letters[i] <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> indices))
</span></span></code></pre></div><p>This is fairly simple. Instead of explicitly embedding the string <code>'just another python hacker'</code> in the program, we assemble it using the index of its letters in the string <code>'abcdefghijklmnopqrstuvwxyz '</code>. We then obfuscate through a series of minor measures:</p>
<ul>
<li>Instead of calling the print function, we <code>import sys</code> and make a call to <code>sys.stdout.write</code>.</li>
<li>We assemble <code>string.lowercase + ' '</code> by joining together the character versions of its respective ordinal values (97 to 123 and 32).</li>
<li>We join together the integer indices using <code>'l'</code> and split that into a list.</li>
<li>We apply <code>'''</code> liberally and rely on the fact that <code>python</code> concatenates adjacent strings.</li>
</ul>
<p>Here&rsquo;s the obfuscated version:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span>eval(<span style="color:#a5d6ff">&#34;__import__(&#39;&#39;&#39;</span><span style="color:#79c0ff">\x73</span><span style="color:#a5d6ff">&#39;&#39;&#39;&#39;&#39;&#39;</span><span style="color:#79c0ff">\x79</span><span style="color:#a5d6ff">&#39;&#39;&#39;&#39;&#39;&#39;</span><span style="color:#79c0ff">\x73</span><span style="color:#a5d6ff">&#39;&#39;&#39;).sTdOuT&#34;</span><span style="color:#ff7b72;font-weight:bold">.</span>lower()
</span></span><span style="display:flex;"><span>)<span style="color:#ff7b72;font-weight:bold">.</span>write(<span style="color:#a5d6ff">&#39;&#39;</span><span style="color:#ff7b72;font-weight:bold">.</span>join(map(<span style="color:#ff7b72">lambda</span> _:(list(map(chr,range(<span style="color:#a5d6ff">97</span>,<span style="color:#a5d6ff">123</span>)))<span style="color:#ff7b72;font-weight:bold">+</span>[chr(
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">32</span>)])[int(_)],(<span style="color:#a5d6ff">&#39;&#39;&#39;9l20l18l19&#39;&#39;&#39;&#39;&#39;&#39;l26l0l13l14l19l7l4l17l26l15&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span><span style="color:#a5d6ff">&#39;&#39;&#39;l24l19l7l14l1&#39;&#39;&#39;&#39;&#39;&#39;3l26l7l0l2l10l4l17&#39;&#39;&#39;</span>)<span style="color:#ff7b72;font-weight:bold">.</span>split(<span style="color:#a5d6ff">&#39;l&#39;</span>)))<span style="color:#ff7b72;font-weight:bold">+</span><span style="color:#a5d6ff">&#39;</span><span style="color:#79c0ff">\n</span><span style="color:#a5d6ff">&#39;</span>,)
</span></span></code></pre></div><p>We could certainly do more, but that&rsquo;s where I left this one. Stay tuned for the next JAPH.</p>
]]></content:encoded>
    </item>
    <item>
      <title>📈 Simulating GDP Growth</title>
      <link>https://ryanjoneil.dev/posts/2011-02-23-simulating-gdp-growth/</link>
      <pubDate>Wed, 23 Feb 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-02-23-simulating-gdp-growth/</guid>
      <description>Writing and interpreting simulations about GDP growth in R</description>
      <content:encoded><![CDATA[<p>I hope you saw <a href="https://www.washingtonpost.com/wp-srv/special/business/china-growth/">&ldquo;China’s way to the top&rdquo;</a> on the Post&rsquo;s website recently. It&rsquo;s a very clear presentation of their statement and is certainly worth a look.</p>
<p>So say you&rsquo;re an economist and you actually do need to produce a realistic estimate of when China&rsquo;s GDP surpasses that of the USA. Can you use such an approach? Not really.  There are several simplifying assumptions the Post made that are perfectly reasonable.  However, if the goal is an analytical output from a highly random system such as GDP growth, one should not assume the inputs are fixed. <em>(I&rsquo;m not saying I have any gripe with their interactive. This post has a different purpose.)</em></p>
<p>Why is this? The short answer is that randomness in any system can change its output drastically from one run to the next. Even if the mean from a deterministic analysis is correct, it tells us nothing about the variance of our output. We really need a confidence interval of years when China is likely to overtake the USA.</p>
<p>We&rsquo;ll move in the great tradition of all simulation studies. First we pepare our input. A CSV of GDP in current US dollars for both countries from 1960 to 2009 is available from the World Bank <a href="https://data.worldbank.org/country/china">data</a> <a href="https://data.worldbank.org/country/united-states">files</a>. We read this into a data frame and calculate their growth rates year over year. Note that the first value for growth has to be NA.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>gdp <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">read.csv</span>(<span style="color:#a5d6ff">&#39;gdp.csv&#39;</span>)
</span></span><span style="display:flex;"><span>gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA.growth <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">rep</span>(<span style="color:#79c0ff">NA</span>, <span style="color:#d2a8ff;font-weight:bold">length</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA))
</span></span><span style="display:flex;"><span>gdp<span style="color:#ff7b72;font-weight:bold">$</span>China.growth <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">rep</span>(<span style="color:#79c0ff">NA</span>, <span style="color:#d2a8ff;font-weight:bold">length</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>China))
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">2</span><span style="color:#ff7b72;font-weight:bold">:</span><span style="color:#d2a8ff;font-weight:bold">length</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA)) {
</span></span><span style="display:flex;"><span>  gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA.growth[i] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">100</span> <span style="color:#ff7b72;font-weight:bold">*</span> (gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA[i] <span style="color:#ff7b72;font-weight:bold">-</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA[i<span style="color:#a5d6ff">-1</span>]) <span style="color:#ff7b72;font-weight:bold">/</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA[i<span style="color:#a5d6ff">-1</span>]
</span></span><span style="display:flex;"><span>  gdp<span style="color:#ff7b72;font-weight:bold">$</span>China.growth[i] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">100</span> <span style="color:#ff7b72;font-weight:bold">*</span> (gdp<span style="color:#ff7b72;font-weight:bold">$</span>China[i] <span style="color:#ff7b72;font-weight:bold">-</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>China[i<span style="color:#a5d6ff">-1</span>]) <span style="color:#ff7b72;font-weight:bold">/</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>China[i<span style="color:#a5d6ff">-1</span>]
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>We now analyze our inputs and assign probability distributions to the annual growth rates. In a full study this would involve comparing a number of different distributions and choosing the one that fits the input data best, but that&rsquo;s well beyond the scope of this post. Instead, we&rsquo;ll use the poor man&rsquo;s way out: plot histograms and visually verify what we hope to be true, that the distributions are normal.</p>
<p><img alt="GDP growth histogram for the USA" loading="lazy" src="/files/2011-02-23-simulating-gdp-growth/us-gdp-percent-growth-histogram.png#center"></p>
<p><img alt="GDP growth histogram for China" loading="lazy" src="/files/2011-02-23-simulating-gdp-growth/china-gdp-percent-growth-histogram.png#center"></p>
<p>And they pretty much are. That&rsquo;s good enough for our purposes. Now all we need are the distribution parameters, which are mean and standard deviation for normal distributions.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">mean</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA.growth[<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">is.na</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA.growth)])
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">7.00594</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">sd</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA.growth[<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">is.na</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA.growth)])
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">2.889808</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">mean</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>China.growth[<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">is.na</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>China.growth)])
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">9.90896</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">sd</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>China.growth[<span style="color:#ff7b72;font-weight:bold">!</span><span style="color:#d2a8ff;font-weight:bold">is.na</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>China.growth)])
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">10.5712</span><span style="color:#ff7b72;font-weight:bold">&lt;/</span>code<span style="color:#ff7b72;font-weight:bold">&gt;&lt;/</span>pre<span style="color:#ff7b72;font-weight:bold">&gt;</span>
</span></span></code></pre></div><p>Now our input analysis is done. These are the inputs:</p>
<p>$$
\begin{align*}
\text{USA Growth} &amp;\sim \mathcal{N}(7.00594, 2.889808^2)\\
\text{China Growth} &amp;\sim \mathcal{N}(9.90896, 10.5712^2)
\end{align*}
$$</p>
<p>This should make the advantage of such an approach much more obvious. Compare the standard deviations for the two countries. China is a lot more likely to have negative GDP growth in any given year. They&rsquo;re also more likely to have astronomical growth.</p>
<p>We now build and run our simulation study. The more times we run the simulation the tighter we can make our confidence interval <em>(to a point)</em>, so we&rsquo;ll pick a pretty big number somewhat arbitrarily. If we want to, we can be fairly scientific about determining how many iterations are necessary after we&rsquo;ve done some runs, but we have to start somewhere.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>repetitions <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#a5d6ff">10000</span>
</span></span></code></pre></div><p>This is the code for our simulation. For each iteration, it starts both countries at their 2009 GDPs. It then iterates, changing GDP randomly until China&rsquo;s GDP is at least the same value as the USA&rsquo;s. When that happens, it records the current year.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>results <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">rep</span>(<span style="color:#79c0ff">NA</span>, repetitions)
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">for</span> (i <span style="color:#ff7b72">in</span> <span style="color:#a5d6ff">1</span><span style="color:#ff7b72;font-weight:bold">:</span>repetitions) {
</span></span><span style="display:flex;"><span>  usa <span style="color:#ff7b72;font-weight:bold">&lt;-</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA<span style="color:#d2a8ff;font-weight:bold">[length</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>USA)]
</span></span><span style="display:flex;"><span>  china <span style="color:#ff7b72;font-weight:bold">&lt;-</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>China<span style="color:#d2a8ff;font-weight:bold">[length</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>China)]
</span></span><span style="display:flex;"><span>  year <span style="color:#ff7b72;font-weight:bold">&lt;-</span> gdp<span style="color:#ff7b72;font-weight:bold">$</span>Year<span style="color:#d2a8ff;font-weight:bold">[length</span>(gdp<span style="color:#ff7b72;font-weight:bold">$</span>Year)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>  <span style="color:#ff7b72">while</span> (<span style="color:#79c0ff">TRUE</span>) {
</span></span><span style="display:flex;"><span>    year <span style="color:#ff7b72;font-weight:bold">&lt;-</span> year <span style="color:#ff7b72;font-weight:bold">+</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    usa.growth <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">rnorm</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">7.00594</span>, <span style="color:#a5d6ff">2.889808</span>)
</span></span><span style="display:flex;"><span>    china.growth <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">rnorm</span>(<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">9.90896</span>, <span style="color:#a5d6ff">10.5712</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    usa <span style="color:#ff7b72;font-weight:bold">&lt;-</span> usa <span style="color:#ff7b72;font-weight:bold">*</span> (<span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">+</span> (usa.growth <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#a5d6ff">100</span>))
</span></span><span style="display:flex;"><span>    china <span style="color:#ff7b72;font-weight:bold">&lt;-</span> china <span style="color:#ff7b72;font-weight:bold">*</span> (<span style="color:#a5d6ff">1</span> <span style="color:#ff7b72;font-weight:bold">+</span> (china.growth <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#a5d6ff">100</span>))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">if</span> (china <span style="color:#ff7b72;font-weight:bold">&gt;=</span> usa) {
</span></span><span style="display:flex;"><span>      results[i] <span style="color:#ff7b72;font-weight:bold">&lt;-</span> year
</span></span><span style="display:flex;"><span>      <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>     }
</span></span><span style="display:flex;"><span>  }
</span></span><span style="display:flex;"><span>}
</span></span></code></pre></div><p>From the results vector we see that, given the data and assumptions for this model, China should surpass the USA in 2058. We also see that we can be 95% confident that the mean year this will happen is between 2057 and 2059. This is not quite the same as saying we are confident this will actually happen between those years. The result of our simulation is a probability distribution and we are discovering information about it.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">mean</span>(results)
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">2058.494</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">mean</span>(results) <span style="color:#ff7b72;font-weight:bold">+</span> (<span style="color:#d2a8ff;font-weight:bold">sd</span>(results) <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#d2a8ff;font-weight:bold">sqrt</span>(<span style="color:#d2a8ff;font-weight:bold">length</span>(results)) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#d2a8ff;font-weight:bold">qnorm</span>(<span style="color:#a5d6ff">0.025</span>))
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">2057.873</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#d2a8ff;font-weight:bold">mean</span>(results) <span style="color:#ff7b72;font-weight:bold">+</span> (<span style="color:#d2a8ff;font-weight:bold">sd</span>(results) <span style="color:#ff7b72;font-weight:bold">/</span> <span style="color:#d2a8ff;font-weight:bold">sqrt</span>(<span style="color:#d2a8ff;font-weight:bold">length</span>(results)) <span style="color:#ff7b72;font-weight:bold">*</span> <span style="color:#d2a8ff;font-weight:bold">qnorm</span>(<span style="color:#a5d6ff">0.975</span>))
</span></span><span style="display:flex;"><span>[1] <span style="color:#a5d6ff">2059.114</span><span style="color:#ff7b72;font-weight:bold">&lt;/</span>code<span style="color:#ff7b72;font-weight:bold">&gt;&lt;/</span>pre<span style="color:#ff7b72;font-weight:bold">&gt;</span>
</span></span></code></pre></div><p>So what&rsquo;s wrong with this model? Well, we had to make a number of assumptions:</p>
<ul>
<li>We assume we actually used the right data set. This was more of a how-to than a proper analysis, so that wasn&rsquo;t too much of a concern.</li>
<li>We assume future growth for the next 40-50 years resembles past growth from 1960-2009.  This is a bit ridiculous, of course, but that&rsquo;s the problem with forecasting.</li>
<li>*We assume growth is normally distributed and that we don&rsquo;t encounter heavy-tailed behaviors in our distributions. We assume each year&rsquo;s growth is independent of the year before it. See the last exercise.</li>
</ul>
<p>Here are some good simulation exercises if you&rsquo;re looking to do more:</p>
<ul>
<li>Note how the outputs are quite a bit different from the Post graphic. I expect that&rsquo;s largely due to the inclusion of data back to 1960. Try running the simulation for yourself using just the past 10, 20, and 30 years and see how that changes the result.&lt;</li>
<li>Write a simulation to determine the probability China&rsquo;s GDP surpasses the USA&rsquo;s in the next 25 years. Now plot the mean GDP and 95% confidence intervals for each country per year.</li>
<li>Assume that there are actually two distributions for growth for each country: one when the previous year had positive growth and another when it was negative. How does that change the output?</li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>🧐 Data Fitting 2a - Very, Very Simple Linear Regression in R</title>
      <link>https://ryanjoneil.dev/posts/2011-02-16-data-fitting-2a-very-very-simple-linear-regression-in-r/</link>
      <pubDate>Wed, 16 Feb 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-02-16-data-fitting-2a-very-very-simple-linear-regression-in-r/</guid>
      <description>Predict how much people like cats and dogs based on their ice cream preferences. Also, R.</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to include an example data file.</em></p>
<p>I thought it might be useful to follow up the <a href="../2011-02-15-data-fitting-2-very-very-simple-linear-regression-in-python/">last post</a> with another one showing the same examples in R.</p>
<p>R provides a function called <code>lm</code>, which is similar in spirit to <a href="https://numpy.org/">NumPy</a>&rsquo;s <code>linalg.lstsq</code>. As you&rsquo;ll see, <code>lm</code>&rsquo;s interface is a bit more tuned to the concepts of modeling.</p>
<p>We begin by reading in the <a href="/files/2011-02-16-data-fitting-2a-very-very-simple-linear-regression-in-r/example_data.csv">example CSV</a> into a data frame:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>responses <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">read.csv</span>(<span style="color:#a5d6ff">&#39;example_data.csv&#39;</span>)
</span></span><span style="display:flex;"><span>responses
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>  respondent vanilla.love strawberry.love chocolate.love dog.love cat.love
</span></span><span style="display:flex;"><span>1     Aylssa            9               4              9        9        9
</span></span><span style="display:flex;"><span>2       Ben8            8               6              4       10        4
</span></span><span style="display:flex;"><span>3         Cy            9               4              8        2        6
</span></span><span style="display:flex;"><span>4        Eva            3               7              9        4        6
</span></span><span style="display:flex;"><span>5        Lem            6               8              5        2        5
</span></span><span style="display:flex;"><span>6      Louis            4               5              3       10        3
</span></span></code></pre></div><p>A data frame is sort of like a matrix, but with named columns. That is, we can refer to entire columns using the dollar sign. We are now ready to run least squares. We&rsquo;ll create the model for predicting &ldquo;dog love.&rdquo;  To create the &ldquo;cat love&rdquo; model, simply use that column name instead:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>fit1 <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">lm</span>(
</span></span><span style="display:flex;"><span>  responses<span style="color:#ff7b72;font-weight:bold">$</span>dog.love <span style="color:#ff7b72;font-weight:bold">~</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>vanilla.love <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>                       responses<span style="color:#ff7b72;font-weight:bold">$</span>strawberry.love <span style="color:#ff7b72;font-weight:bold">+</span> 
</span></span><span style="display:flex;"><span>                       responses<span style="color:#ff7b72;font-weight:bold">$</span>chocolate.love
</span></span><span style="display:flex;"><span>)
</span></span></code></pre></div><p>The syntax for lm is a little off-putting at first.  This call tells it to create a model for &ldquo;dog love&rdquo; with respect to <em>(the ~)</em> a function of the form <em>offset + x1 * vanilla love + x2 * strawberry love + x3 * chocolate love</em>. Note that the offset is conveniently implied when using <code>lm</code>, so this is the same as the second model we created in Python. Now that we&rsquo;ve computed the coefficients for our &ldquo;dog love&rdquo; model, we can ask R about it:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">summary</span>(fit1)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Call:
</span></span><span style="display:flex;"><span>lm(formula = responses$dog.love ~ responses$vanilla.love + responses$strawberry.love + 
</span></span><span style="display:flex;"><span>    responses$chocolate.love)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Residuals:
</span></span><span style="display:flex;"><span>      1       2       3       4       5       6 
</span></span><span style="display:flex;"><span> 3.1827  2.9436 -4.5820  0.8069 -1.9856 -0.3657 
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Coefficients:
</span></span><span style="display:flex;"><span>                          Estimate Std. Error t value Pr(&gt;|t|)
</span></span><span style="display:flex;"><span>(Intercept)                20.9298    15.0654   1.389    0.299
</span></span><span style="display:flex;"><span>responses$vanilla.love     -0.2783     0.9934  -0.280    0.806
</span></span><span style="display:flex;"><span>responses$strawberry.love  -1.4314     1.5905  -0.900    0.463
</span></span><span style="display:flex;"><span>responses$chocolate.love   -0.7647     0.8214  -0.931    0.450
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Residual standard error: 4.718 on 2 degrees of freedom
</span></span><span style="display:flex;"><span>Multiple R-squared:  0.4206, Adjusted R-squared:  -0.4485 
</span></span><span style="display:flex;"><span>F-statistic: 0.484 on 3 and 2 DF,  p-value: 0.7272
</span></span></code></pre></div><p>This gives us quite a bit of information, including the coefficients for our &ldquo;dog love&rdquo; model and various error metrics. You can find the offset and coefficients under the Estimate column above. We quickly verify this using R&rsquo;s vectorized arithmetic:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#a5d6ff">20.9298</span> <span style="color:#ff7b72;font-weight:bold">-</span> 
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">0.2783</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>vanilla.love <span style="color:#ff7b72;font-weight:bold">-</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">1.4314</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>strawberry.love <span style="color:#ff7b72;font-weight:bold">-</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">0.7647</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>chocolate.love
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>[1]  5.8172  7.0562  6.5819  3.1928  3.9853 10.3655
</span></span></code></pre></div><p>You&rsquo;ll notice the model is essentially the same as the one we got from NumPy. Our next step is to add in the squared inputs. We do this by adding extra terms to the modeling formula. The <code>I()</code> function allows us to easily add additional operators to columns. That&rsquo;s how we accomplish the squaring. We could alternatively add squared input values to the data frame, but using <code>I()</code> is more convenient and natural.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span>fit2 <span style="color:#ff7b72;font-weight:bold">&lt;-</span> <span style="color:#d2a8ff;font-weight:bold">lm</span>(responses<span style="color:#ff7b72;font-weight:bold">$</span>dog.love <span style="color:#ff7b72;font-weight:bold">~</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>vanilla.love <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>  <span style="color:#d2a8ff;font-weight:bold">I</span>(responses<span style="color:#ff7b72;font-weight:bold">$</span>vanilla.love^2) <span style="color:#ff7b72;font-weight:bold">+</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>strawberry.love <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>  <span style="color:#d2a8ff;font-weight:bold">I</span>(responses<span style="color:#ff7b72;font-weight:bold">$</span>strawberry.love^2) <span style="color:#ff7b72;font-weight:bold">+</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>chocolate.love <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>  <span style="color:#d2a8ff;font-weight:bold">I</span>(responses<span style="color:#ff7b72;font-weight:bold">$</span>chocolate.love^2))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#d2a8ff;font-weight:bold">summary</span>(fit2)
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>Call:
</span></span><span style="display:flex;"><span>lm(formula = responses$dog.love ~ responses$vanilla.love + I(responses$vanilla.love^2) + 
</span></span><span style="display:flex;"><span>    responses$strawberry.love + I(responses$strawberry.love^2) + 
</span></span><span style="display:flex;"><span>    responses$chocolate.love + I(responses$chocolate.love^2))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Residuals:
</span></span><span style="display:flex;"><span>ALL 6 residuals are 0: no residual degrees of freedom!
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Coefficients: (1 not defined because of singularities)
</span></span><span style="display:flex;"><span>                               Estimate Std. Error t value Pr(&gt;|t|)
</span></span><span style="display:flex;"><span>(Intercept)                    -357.444        NaN     NaN      NaN
</span></span><span style="display:flex;"><span>responses$vanilla.love           72.444        NaN     NaN      NaN
</span></span><span style="display:flex;"><span>I(responses$vanilla.love^2)      -6.111        NaN     NaN      NaN
</span></span><span style="display:flex;"><span>responses$strawberry.love        59.500        NaN     NaN      NaN
</span></span><span style="display:flex;"><span>I(responses$strawberry.love^2)   -5.722        NaN     NaN      NaN
</span></span><span style="display:flex;"><span>responses$chocolate.love          7.000        NaN     NaN      NaN
</span></span><span style="display:flex;"><span>I(responses$chocolate.love^2)        NA         NA      NA       NA
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>Residual standard error: NaN on 0 degrees of freedom
</span></span><span style="display:flex;"><span>Multiple R-squared:      1, Adjusted R-squared:    NaN 
</span></span><span style="display:flex;"><span>F-statistic:   NaN on 5 and 0 DF,  p-value: NA
</span></span></code></pre></div><p>We can see that we get the same &ldquo;dog love&rdquo; model as produced by the third Python version of the last post. Again, we quickly verify that the output is the same (minus some rounding errors):</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-r" data-lang="r"><span style="display:flex;"><span><span style="color:#a5d6ff">-357.444</span> <span style="color:#ff7b72;font-weight:bold">+</span> 
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">72.444</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>vanilla.love <span style="color:#ff7b72;font-weight:bold">-</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">6.111</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>vanilla.love^2 <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">59.5</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>strawberry.love <span style="color:#ff7b72;font-weight:bold">-</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">5.722</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>strawberry.love^2 <span style="color:#ff7b72;font-weight:bold">+</span>
</span></span><span style="display:flex;"><span>  <span style="color:#a5d6ff">7</span> <span style="color:#ff7b72;font-weight:bold">*</span> responses<span style="color:#ff7b72;font-weight:bold">$</span>chocolate.love
</span></span></code></pre></div><div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>[1]  9.009 10.012  2.009  4.011  2.016 10.006
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>🧐 Data Fitting 2 - Very, Very Simple Linear Regression in Python</title>
      <link>https://ryanjoneil.dev/posts/2011-02-15-data-fitting-2-very-very-simple-linear-regression-in-python/</link>
      <pubDate>Tue, 15 Feb 2011 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2011-02-15-data-fitting-2-very-very-simple-linear-regression-in-python/</guid>
      <description>Predict how much people like cats and dogs based on their ice cream preferences. Also, Python and numpy.</description>
      <content:encoded><![CDATA[<p>This post is based on a memo I sent to some former colleagues at the Post. I&rsquo;ve edited it for use here since it fits well as the second in a series on simple data fitting techniques. If you&rsquo;re among the many enlightened individuals already using regression analysis, then this post is probably not for you. If you aren&rsquo;t, then hopefully this provides everything you need to develop rudimentary predictive models that yield surprising levels of accuracy.</p>
<h2 id="data">Data</h2>
<p>For purposes of a simple working example, we have collected six records of input data over three dimensions with the goal of predicting two outputs. The input data are:</p>
<p>$$
\begin{align*}
x_1 &amp;= \text{How much a respondent likes vanilla [0-10]}\\
x_2 &amp;= \text{How much a respondent likes strawberry [0-10]}\\
x_3 &amp;= \text{How much a respondent likes chocolate [0-10]}
\end{align*}
$$</p>
<p>Output data consist of:</p>
<p>$$
\begin{align*}
b_1 &amp;= \text{How much a respondent likes dogs [0-10]}\\
b_2 &amp;= \text{How much a respondent likes cats [0-10]}
\end{align*}
$$</p>
<p>Below are anonymous data collected from a random sample of people.</p>
<table>
  <thead>
      <tr>
          <th>respondent</th>
          <th style="text-align: right">vanilla ❤️</th>
          <th style="text-align: right">strawberry ❤️</th>
          <th style="text-align: right">chocolate ❤️</th>
          <th style="text-align: right">dog ❤️</th>
          <th style="text-align: right">cat ❤️</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Alyssa P Hacker</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">8</td>
      </tr>
      <tr>
          <td>Ben Bitdiddle</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">6</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">10</td>
          <td style="text-align: right">4</td>
      </tr>
      <tr>
          <td>Cy D. Fect</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">2</td>
          <td style="text-align: right">6</td>
      </tr>
      <tr>
          <td>Eva Lu Ator</td>
          <td style="text-align: right">3</td>
          <td style="text-align: right">7</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">6</td>
      </tr>
      <tr>
          <td>Lem E. Tweakit</td>
          <td style="text-align: right">6</td>
          <td style="text-align: right">8</td>
          <td style="text-align: right">5</td>
          <td style="text-align: right">2</td>
          <td style="text-align: right">5</td>
      </tr>
      <tr>
          <td>Louis Reasoner</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">5</td>
          <td style="text-align: right">3</td>
          <td style="text-align: right">10</td>
          <td style="text-align: right">3</td>
      </tr>
  </tbody>
</table>
<p>Our input is in three dimensions. Each output requires its own model, so we&rsquo;ll have one for dogs and one for cats. We&rsquo;re looking for functions, <code>dog(x)</code> and <code>cat(x)</code>, that can predict $b_1$ and $b_2$ based on given values of $x_1$, $x_2$, and $x_3$.</p>
<h2 id="model-1">Model 1</h2>
<p>For both models we want to find parameters that minimize their squared residuals (read: errors). There&rsquo;s a number of names for this. Optimization folks like to think of it as unconstrained quadratic optimization, but it&rsquo;s more common to call it least squares or linear regression. It&rsquo;s not necessary to entirely understand why for our purposes, but the function that minimizes these errors is:</p>
<p>$$\beta = ({A^t}A)^{-1}{A^t}b$$</p>
<p>This is implemented for you in the <code>numpy.linalg</code> Python package, which we&rsquo;ll use for examples. Much more information than you probably want can be found <a href="http://en.wikipedia.org/wiki/Least_squares">here</a>.</p>
<p>Below is a first stab at a Python version. It runs least squares against our input and output data exactly as they are. You can see the matrix $A$ and outputs $b_1$ and $b_2$ (dog and cat love, respectively) are represented just as they are in the table.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Version 1: No offset, no squared inputs</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">numpy</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>A <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>vstack([
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">4</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">8</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">3</span>]
</span></span><span style="display:flex;"><span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>b1 <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>array([<span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">10</span>])
</span></span><span style="display:flex;"><span>b2 <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>array([<span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">3</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;dog ❤️:&#39;</span>, numpy<span style="color:#ff7b72;font-weight:bold">.</span>linalg<span style="color:#ff7b72;font-weight:bold">.</span>lstsq(A, b1, rcond<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>)[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;cat ❤️:&#39;</span>, numpy<span style="color:#ff7b72;font-weight:bold">.</span>linalg<span style="color:#ff7b72;font-weight:bold">.</span>lstsq(A, b2, rcond<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>)[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Output:</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># dog ❤️: [0.72548294      0.53045642     -0.29952361]</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># cat ❤️: [2.36110929e-01  2.61934385e-05  6.26892476e-01]</span>
</span></span></code></pre></div><p>The resulting model is:</p>
<pre tabindex="0"><code>dog(x) = 0.72548294 * x1 + 0.53045642 * x2 - 0.29952361 * x3
cat(x) = 2.36110929e-01 * x1 + 2.61934385e-05 * x2 + 6.26892476e-01 * x3
</code></pre><p>The coefficients before our variables correspond to beta in the formula above. Errors between observed and predicted data, shown below, are calculated and summed. For these six records, <code>dog(x)</code> has a total error of 20.76 and <code>cat(x)</code> has 3.74. Not great.</p>
<table>
  <thead>
      <tr>
          <th>respondent</th>
          <th style="text-align: right">predicted b1</th>
          <th style="text-align: right">b1 error</th>
          <th style="text-align: right">predicted b2</th>
          <th style="text-align: right">b2 error</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Alyssa P Hacker</td>
          <td style="text-align: right">5.96</td>
          <td style="text-align: right">3.04</td>
          <td style="text-align: right">7.77</td>
          <td style="text-align: right">1.23</td>
      </tr>
      <tr>
          <td>Ben Bitdiddle</td>
          <td style="text-align: right">7.79</td>
          <td style="text-align: right">2.21</td>
          <td style="text-align: right">4.40</td>
          <td style="text-align: right">0.40</td>
      </tr>
      <tr>
          <td>Cy D. Fect</td>
          <td style="text-align: right">6.25</td>
          <td style="text-align: right">4.25</td>
          <td style="text-align: right">7.14</td>
          <td style="text-align: right">1.14</td>
      </tr>
      <tr>
          <td>Eva Lu Ator</td>
          <td style="text-align: right">3.19</td>
          <td style="text-align: right">0.81</td>
          <td style="text-align: right">6.35</td>
          <td style="text-align: right">0.35</td>
      </tr>
      <tr>
          <td>Lem E. Tweakit</td>
          <td style="text-align: right">7.10</td>
          <td style="text-align: right">5.10</td>
          <td style="text-align: right">4.55</td>
          <td style="text-align: right">0.45</td>
      </tr>
      <tr>
          <td>Louis Reasoner</td>
          <td style="text-align: right">4.66</td>
          <td style="text-align: right">5.34</td>
          <td style="text-align: right">2.83</td>
          <td style="text-align: right">0.17</td>
      </tr>
      <tr>
          <td>Total error:</td>
          <td style="text-align: right"></td>
          <td style="text-align: right">20.76</td>
          <td style="text-align: right"></td>
          <td style="text-align: right">3.74</td>
      </tr>
  </tbody>
</table>
<h2 id="model-2">Model 2</h2>
<p>One problem with this model is that <code>dog(x)</code> and <code>cat(x)</code> are forced to pass through the origin. <em>(Why is that?)</em> We can improve it somewhat if we add an offset. This amounts to prepending 1 to every row in $A$ and adding a constant to the resulting functions. You can see the very slight difference between the code for this model and that of the previous:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Version 2: Offset, no squared inputs</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">numpy</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>A <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>vstack([
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">4</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">8</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">9</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">5</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">3</span>]
</span></span><span style="display:flex;"><span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;dog ❤️:&#39;</span>, numpy<span style="color:#ff7b72;font-weight:bold">.</span>linalg<span style="color:#ff7b72;font-weight:bold">.</span>lstsq(A, b1, rcond<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>)[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;cat ❤️:&#39;</span>, numpy<span style="color:#ff7b72;font-weight:bold">.</span>linalg<span style="color:#ff7b72;font-weight:bold">.</span>lstsq(A, b2, rcond<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>)[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Output:</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># dog ❤️: [20.92975427  -0.27831197  -1.43135684  -0.76469017]</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># cat ❤️: [-0.31744124   0.25133547   0.02978098   0.63394765]</span>
</span></span></code></pre></div><p>This yields the seconds version of our models:</p>
<pre tabindex="0"><code>dog(x) = 20.92975427 - 0.27831197 * x1 - 1.43135684 * x2 - 0.76469017 * x3
cat(x) = -0.31744124 + 0.25133547 * x1 + 0.02978098 * x2 + 0.63394765 * x3
</code></pre><p>These models provide errors of 13.87 and 3.79.  A little better on the dog side, but still not quite usable.</p>
<table>
  <thead>
      <tr>
          <th>respondent</th>
          <th style="text-align: right">predicted b1</th>
          <th style="text-align: right">b1 error</th>
          <th style="text-align: right">predicted b2</th>
          <th style="text-align: right">b2 error</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Alyssa P Hacker</td>
          <td style="text-align: right">5.82</td>
          <td style="text-align: right">3.18</td>
          <td style="text-align: right">7.77</td>
          <td style="text-align: right">1.23</td>
      </tr>
      <tr>
          <td>Ben Bitdiddle</td>
          <td style="text-align: right">7.06</td>
          <td style="text-align: right">2.94</td>
          <td style="text-align: right">4.41</td>
          <td style="text-align: right">0.41</td>
      </tr>
      <tr>
          <td>Cy D. Fect</td>
          <td style="text-align: right">6.58</td>
          <td style="text-align: right">4.58</td>
          <td style="text-align: right">7.14</td>
          <td style="text-align: right">1.14</td>
      </tr>
      <tr>
          <td>Eva Lu Ator</td>
          <td style="text-align: right">3.19</td>
          <td style="text-align: right">0.81</td>
          <td style="text-align: right">6.35</td>
          <td style="text-align: right">0.35</td>
      </tr>
      <tr>
          <td>Lem E. Tweakit</td>
          <td style="text-align: right">3.99</td>
          <td style="text-align: right">1.99</td>
          <td style="text-align: right">4.60</td>
          <td style="text-align: right">0.40</td>
      </tr>
      <tr>
          <td>Louis Reasoner</td>
          <td style="text-align: right">10.37</td>
          <td style="text-align: right">0.37</td>
          <td style="text-align: right">2.74</td>
          <td style="text-align: right">0.26</td>
      </tr>
      <tr>
          <td>Total error:</td>
          <td style="text-align: right"></td>
          <td style="text-align: right">13.87</td>
          <td style="text-align: right"></td>
          <td style="text-align: right">3.79</td>
      </tr>
  </tbody>
</table>
<h2 id="model-3">Model 3</h2>
<p>The problem is that <code>dog(x)</code> and <code>cat(x)</code> are linear functions. Most observed data don&rsquo;t conform to straight lines. Take a moment and draw the line $f(x) = x$ and the curve $f(x) = x^2$. The former makes a poor approximation of the latter.</p>
<p>Most of the time, people just use squares of the input data to add curvature to their models. We do this in our next version of the code by just adding squares of the input row values to our $A$ matrix. Everything else is the same. (In reality, you can add any function of the input data you feel best models the data, if you understand it well enough.)</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Version 3: Offset with squared inputs</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">numpy</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>A <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>vstack([
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">9</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">4</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">9</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">8</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">6</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">4</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">9</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">4</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">8</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">7</span>, <span style="color:#a5d6ff">7</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">9</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">6</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">8</span>, <span style="color:#a5d6ff">8</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">5</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>],
</span></span><span style="display:flex;"><span>    [<span style="color:#a5d6ff">1</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">4</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">5</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">3</span>, <span style="color:#a5d6ff">3</span><span style="color:#ff7b72;font-weight:bold">**</span><span style="color:#a5d6ff">2</span>]
</span></span><span style="display:flex;"><span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>b1 <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>array([<span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">10</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">2</span>, <span style="color:#a5d6ff">10</span>])
</span></span><span style="display:flex;"><span>b2 <span style="color:#ff7b72;font-weight:bold">=</span> numpy<span style="color:#ff7b72;font-weight:bold">.</span>array([<span style="color:#a5d6ff">9</span>, <span style="color:#a5d6ff">4</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">6</span>, <span style="color:#a5d6ff">5</span>, <span style="color:#a5d6ff">3</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;dog ❤️:&#39;</span>, numpy<span style="color:#ff7b72;font-weight:bold">.</span>linalg<span style="color:#ff7b72;font-weight:bold">.</span>lstsq(A, b1, rcond<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>)[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>print(<span style="color:#a5d6ff">&#39;cat ❤️:&#39;</span>, numpy<span style="color:#ff7b72;font-weight:bold">.</span>linalg<span style="color:#ff7b72;font-weight:bold">.</span>lstsq(A, b2, rcond<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>)[<span style="color:#a5d6ff">0</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># dog ❤️: [1.29368307  7.03633306  -0.44795498  9.98093332</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#  -0.75689575  -19.00757486  1.52985734]</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># cat ❤️: [0.47945896  5.30866067  -0.39644128 -1.28704188</span>
</span></span><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic">#   0.12634295   -4.32392606  0.43081918]</span>
</span></span></code></pre></div><p>This gives us our final version of the model:</p>
<pre tabindex="0"><code>dog(x) = 1.29368307 + 7.03633306 * x1 - 0.44795498 * x1**2 + 9.98093332 * x2 - 0.75689575 * x2**2 - 19.00757486 * x3 + 1.52985734 * x3**2
cat(x) = 0.47945896 + 5.30866067 * x1 - 0.39644128 * x1**2 - 1.28704188 * x2 + 0.12634295 * x2**2 - 4.32392606 * x3 + 0.43081918 * x3**2
</code></pre><p>Adding curvature to our model eliminates all perceived error, at least within 1e-16. This may seem unbelievable, but when you consider that we only have six input records, it isn&rsquo;t really.</p>
<table>
  <thead>
      <tr>
          <th>respondent</th>
          <th style="text-align: right">predicted b1</th>
          <th style="text-align: right">b1 error</th>
          <th style="text-align: right">predicted b2</th>
          <th style="text-align: right">b2 error</th>
      </tr>
  </thead>
  <tbody>
      <tr>
          <td>Alyssa P Hacker</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">9</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Ben Bitdiddle</td>
          <td style="text-align: right">10</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Cy D. Fect</td>
          <td style="text-align: right">2</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">6</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Eva Lu Ator</td>
          <td style="text-align: right">4</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">6</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Lem E. Tweakit</td>
          <td style="text-align: right">2</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">5</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Louis Reasoner</td>
          <td style="text-align: right">10</td>
          <td style="text-align: right">0</td>
          <td style="text-align: right">3</td>
          <td style="text-align: right">0</td>
      </tr>
      <tr>
          <td>Total error:</td>
          <td style="text-align: right"></td>
          <td style="text-align: right">0</td>
          <td style="text-align: right"></td>
          <td style="text-align: right">0</td>
      </tr>
  </tbody>
</table>
<p>It should be fairly obvious how one can take this and extrapolate to much larger models. I hope this is useful and that least squares becomes an important part of your lives.</p>
]]></content:encoded>
    </item>
    <item>
      <title>🗳 Off the Cuff Voter Fraud Detection</title>
      <link>https://ryanjoneil.dev/posts/2010-11-30-off-the-cuff-voter-fraud-detection/</link>
      <pubDate>Tue, 30 Nov 2010 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2010-11-30-off-the-cuff-voter-fraud-detection/</guid>
      <description>Using the exponential distribution to interpret votes in a web survey</description>
      <content:encoded><![CDATA[<p>Consider this scenario: You run a contest that accepts votes from the general Internet population. In order to encourage user engagement, you record any and all votes into a database over several days, storing nothing more than the competitor voted for, when each vote is cast, and a cookie set on the voter&rsquo;s computer along with their apparent IP addresses. If a voter already has a recorded cookie set they are denied subsequent votes. This way you can avoid requiring site registration, a huge turnoff for your users. Simple enough.</p>
<p>Unfortunately, some of the competitors are wily and attached to the idea of winning. They go so far as programming or hiring bots to cast thousands of votes for them. Your manager wants to know which votes are real and which ones are fake Right Now. Given very limited time, and ignoring actions that you <em>could</em> have taken to avoid the problem, how can you tell apart sets of good votes from those that shouldn&rsquo;t be counted?</p>
<p>One quick-and-dirty option involves comparing histograms of <a href="http://www.ehow.com/how_5417319_calculate-interarrival-time.html">interarrival times</a> for sets of votes. Say you&rsquo;re concerned that all the votes during a particular period of time or from a given IP address might be fraudulent. Put all the vote times you&rsquo;re concerned about into a list, sort them, and compute their differences:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># times is a list of datetime instances from vote records</span>
</span></span><span style="display:flex;"><span>times<span style="color:#ff7b72;font-weight:bold">.</span>sort(reversed<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">True</span>)
</span></span><span style="display:flex;"><span>interarrivals <span style="color:#ff7b72;font-weight:bold">=</span> [y<span style="color:#ff7b72;font-weight:bold">-</span>x <span style="color:#ff7b72">for</span> x, y <span style="color:#ff7b72;font-weight:bold">in</span> zip(times, times[<span style="color:#a5d6ff">1</span>:]]
</span></span></code></pre></div><p>Now use matplotlib to <a href="https://matplotlib.org/2.0.2/users/pyplot_tutorial.html#working-with-text">display a histogram</a> of these. Votes that occur naturally are likely to resemble an <a href="http://en.wikipedia.org/wiki/Exponential_distribution">exponential distribution</a> in their interarrival times. For instance, here are interarrival times for all votes received in a contest:</p>
<p><img alt="Interarrival times for all submissions" loading="lazy" src="/files/2010-11-30-off-the-cuff-voter-fraud-detection/all-votes.png#center"></p>
<p>This subset of votes is clearly fraudulent, due to the near determinism of their interarrival times. This is most likely caused by the voting bot not taking random sleep intervals during voting. It casts a vote, receives a response, clears its cookies, and repeats:</p>
<p><img alt="Interarrival times for clearly fraudulent votes" loading="lazy" src="/files/2010-11-30-off-the-cuff-voter-fraud-detection/fraud-plot.png#center"></p>
<p>These votes, on the other hand, are most likely legitimate. They exhibit a nice Erlang shape and appear to have natural interarrival times that one would expect:</p>
<p><img alt="Proper-looking interarrival times" loading="lazy" src="/files/2010-11-30-off-the-cuff-voter-fraud-detection/not-fraud.png#center"></p>
<p>Of course this method is woefully inadequate for rigorous detection of voting fraud. Ideally one would find a method to compute the probability that a set of votes is generated by a bot. This is enough to inform quick, ad hoc decisions though.</p>
]]></content:encoded>
    </item>
    <item>
      <title>🧐 Data Fitting 1 - Linear Data Fitting</title>
      <link>https://ryanjoneil.dev/posts/2010-11-23-data-fitting-1-linear-data-fitting/</link>
      <pubDate>Tue, 23 Nov 2010 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2010-11-23-data-fitting-1-linear-data-fitting/</guid>
      <description>An introduction to data fitting and classification using linear optimization in Python</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3 and <a href="https://github.com/scipopt/PySCIPOpt">PySCIPOpt</a>. The original version used Python 2 and <a href="https://pythonhosted.org/python-zibopt/">python-zibopt</a>.</em></p>
<p>Data fitting is one of those tasks that everyone should have at least some exposure to. Certainly developers and analysts will benefit from a working knowledge of its fundamentals and their implementations. However, in my own reading I&rsquo;ve found it difficult to locate good examples that are simple enough to pick up quickly and come with accompanying source code.</p>
<p>This article commences an ongoing series introducing basic data fitting techniques. With any luck they won&rsquo;t be overly complex, while still being useful enough to get the point across with a real example and real data. We&rsquo;ll start with a binary classification problem: presented with a series of records, each containing a set number of input values describing it, determine whether or not each record exhibits some property.</p>
<h2 id="model">Model</h2>
<p>We&rsquo;ll use the <code>cancer1.dt</code> data from the <code>proben1</code> set of test cases, which you can download <a href="/files/2010-11-23-data-fitting-1-linear-data-fitting/cancer1.dt">here</a>. Each record starts with 9 data points containing physical characteristics of a tumor. The second to last data point contains 1 if a tumor is benign and 0 if it is malignant. We seek to find a linear function we can run on an arbitrary record that will return a value greater than zero if that record&rsquo;s tumor is predicted to be benign and less than zero if it is predicted to be malignant. We will train our linear model on the first 350 records, and test it for accuracy on the remaining rows.</p>
<p>This is similar to the data fitting problem found in <a href="https://www.thriftbooks.com/w/linear-programming-series-of-books-in-the-mathematical-sciences_vasek-chvatal/249798/#edition=2416723&amp;idiq=15706498">Chvatal</a>. Our inputs consist of a matrix of observed data, $A$, and a vector of classifications, $b$. In order to classify a record, we require another vector $x$ such that the dot product of $x$ and that record will be either greater or less than zero depending on its predicted classification.</p>
<p>A couple points to note before we start:</p>
<ul>
<li>
<p>Most observed data are noisy. This means it may be impossible to locate a hyperplane that cleanly separates given records of one type from another. In this case, we must resort to finding a function that minimizes our predictive error. For the purposes of this example, we&rsquo;ll minimize the sum of the absolute values of the observed and predicted values. That is, we seek $x$ such that we find $min \sum_i{|a_i^T x-b_i|}$.</p>
</li>
<li>
<p>The <a href="https://www.purplemath.com/modules/strtlneq.htm">slope-intercept</a> form of a line, $f(x)=m^T x+b$, contains an offset. It should be obvious that this is necessary in our model so that our function isn&rsquo;t required to pass through the origin. Thus, we&rsquo;ll be adding an extra variable with the coefficient of 1 to represent our offset value.</p>
</li>
<li>
<p>In order to model this, we use two linear constraints for each absolute value. We minimize the sum of these. Our Linear Programming model thus looks like:</p>
</li>
</ul>
<p>$$
\begin{align*}
\min\quad       &amp; z = x_0 + \sum_i{v_i}\\
\text{s.t.}\quad&amp; v_i \geq x_0 + a_i^\intercal x - 1    &amp;\quad\forall&amp;\quad\text{benign tumors}\\
&amp; v_i \geq 1 - x_0 - a_i^\intercal x    &amp;\quad\forall&amp;\quad\text{benign tumors}\\
&amp; v_i \geq x_0 + a_i^\intercal x - (-1) &amp;\quad\forall&amp;\quad\text{malignant tumors}\\
&amp; v_i \geq -1 - x_0 - a_i^\intercal x   &amp;\quad\forall&amp;\quad\text{malignant tumors}
\end{align*}
$$</p>
<h2 id="code">Code</h2>
<p>In order to do this in Python, we use <a href="https://www.scipopt.org/">SCIP</a> and <a href="https://soplex.zib.de/">SoPlex</a>. We start by setting constants for benign and malignant outputs and providing a function to read in the training and testing data sets.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#8b949e;font-style:italic"># Preferred output values for tumor categories</span>
</span></span><span style="display:flex;"><span>BENIGN <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>MALIGNANT <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">read_proben1_cancer_data</span>(filename, train_size):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;Loads a proben1 cancer file into train &amp; test sets&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Number of input data points per record</span>
</span></span><span style="display:flex;"><span>    DATA_POINTS <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">9</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    train_data <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>    test_data <span style="color:#ff7b72;font-weight:bold">=</span> []
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">with</span> open(filename) <span style="color:#ff7b72">as</span> infile:
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># Read in the first train_size lines to a training data list, and the</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># others to testing data. This allows us to test how general our model</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># is on something other than the input data.</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> line <span style="color:#ff7b72;font-weight:bold">in</span> infile<span style="color:#ff7b72;font-weight:bold">.</span>readlines()[<span style="color:#a5d6ff">7</span>:]: <span style="color:#8b949e;font-style:italic"># skip header</span>
</span></span><span style="display:flex;"><span>            line <span style="color:#ff7b72;font-weight:bold">=</span> line<span style="color:#ff7b72;font-weight:bold">.</span>split()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># Records = offset (x0) + remaining data points</span>
</span></span><span style="display:flex;"><span>            input <span style="color:#ff7b72;font-weight:bold">=</span> [float(x) <span style="color:#ff7b72">for</span> x <span style="color:#ff7b72;font-weight:bold">in</span> line[:DATA_POINTS]]
</span></span><span style="display:flex;"><span>            output <span style="color:#ff7b72;font-weight:bold">=</span> BENIGN <span style="color:#ff7b72">if</span> line[<span style="color:#ff7b72;font-weight:bold">-</span><span style="color:#a5d6ff">2</span>] <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;1&#39;</span> <span style="color:#ff7b72">else</span> MALIGNANT
</span></span><span style="display:flex;"><span>            record <span style="color:#ff7b72;font-weight:bold">=</span> {<span style="color:#a5d6ff">&#39;input&#39;</span>: input, <span style="color:#a5d6ff">&#39;output&#39;</span>: output}
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>            <span style="color:#8b949e;font-style:italic"># Determine what data set to put this in</span>
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">if</span> len(train_data) <span style="color:#ff7b72;font-weight:bold">&gt;=</span> train_size:
</span></span><span style="display:flex;"><span>                test_data<span style="color:#ff7b72;font-weight:bold">.</span>append(record)
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">else</span>:
</span></span><span style="display:flex;"><span>                train_data<span style="color:#ff7b72;font-weight:bold">.</span>append(record)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> train_data, test_data
</span></span></code></pre></div><p>The next function implements the LP model described above using SoPlex and SCIP. It minimizes the sum of residuals for each training record. This amounts to summing the absolute value of the difference between predicted and observed output data. The following function takes in input and observed output data and returns a list of coefficients. Our resulting model consists of taking the <a href="https://en.wikipedia.org/wiki/Dot_product">dot product</a> of an input record and these coefficients. If the result is greater than or equal to zero, that record is predicted to be a benign tumor, otherwise it is predicted to be malignant.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">pyscipopt</span> <span style="color:#ff7b72">import</span> Model
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">train_linear_model</span>(train_data):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    Accepts a set of input training data with known output
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    values.  Returns a list of coefficients to apply to
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    arbitrary records for purposes of binary categorization.
</span></span></span><span style="display:flex;"><span><span style="color:#a5d6ff">    &#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Make sure we have at least one training record.</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">assert</span> len(train_data) <span style="color:#ff7b72;font-weight:bold">&gt;</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span>    num_variables <span style="color:#ff7b72;font-weight:bold">=</span> len(train_data[<span style="color:#a5d6ff">0</span>][<span style="color:#a5d6ff">&#39;input&#39;</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Variables are coefficients in front of the data points. It is important</span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># that these be unrestricted in sign so they can take negative values.</span>
</span></span><span style="display:flex;"><span>    m <span style="color:#ff7b72;font-weight:bold">=</span> Model()
</span></span><span style="display:flex;"><span>    x <span style="color:#ff7b72;font-weight:bold">=</span> [m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(<span style="color:#79c0ff">f</span><span style="color:#a5d6ff">&#39;x</span><span style="color:#a5d6ff">{</span>i<span style="color:#a5d6ff">}</span><span style="color:#a5d6ff">&#39;</span>, lb<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>) <span style="color:#ff7b72">for</span> i <span style="color:#ff7b72;font-weight:bold">in</span> range(num_variables)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Residual for each data row</span>
</span></span><span style="display:flex;"><span>    residuals <span style="color:#ff7b72;font-weight:bold">=</span> [m<span style="color:#ff7b72;font-weight:bold">.</span>addVar(lb<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>, ub<span style="color:#ff7b72;font-weight:bold">=</span><span style="color:#79c0ff">None</span>) <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> train_data]
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> r, d <span style="color:#ff7b72;font-weight:bold">in</span> zip(residuals, train_data):
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># r will be the absolute value of the difference between observed and</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># predicted values. We can model absolute values such as r &gt;= |foo| as:</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">#</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">#   r &gt;=  foo</span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic">#   r &gt;= -foo</span>
</span></span><span style="display:flex;"><span>        m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(sum(x <span style="color:#ff7b72;font-weight:bold">*</span> y <span style="color:#ff7b72">for</span> x, y <span style="color:#ff7b72;font-weight:bold">in</span> zip(x, d[<span style="color:#a5d6ff">&#39;input&#39;</span>])) <span style="color:#ff7b72;font-weight:bold">+</span> r <span style="color:#ff7b72;font-weight:bold">&gt;=</span> d[<span style="color:#a5d6ff">&#39;output&#39;</span>])
</span></span><span style="display:flex;"><span>        m<span style="color:#ff7b72;font-weight:bold">.</span>addCons(sum(x <span style="color:#ff7b72;font-weight:bold">*</span> y <span style="color:#ff7b72">for</span> x, y <span style="color:#ff7b72;font-weight:bold">in</span> zip(x, d[<span style="color:#a5d6ff">&#39;input&#39;</span>])) <span style="color:#ff7b72;font-weight:bold">-</span> r <span style="color:#ff7b72;font-weight:bold">&lt;=</span> d[<span style="color:#a5d6ff">&#39;output&#39;</span>])
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Find and return coefficients that min sum of residuals.</span>
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>setObjective(sum(residuals))
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>setMinimize()
</span></span><span style="display:flex;"><span>    m<span style="color:#ff7b72;font-weight:bold">.</span>optimize()
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    solution <span style="color:#ff7b72;font-weight:bold">=</span> m<span style="color:#ff7b72;font-weight:bold">.</span>getBestSol()
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> [solution[xi] <span style="color:#ff7b72">for</span> xi <span style="color:#ff7b72;font-weight:bold">in</span> x]
</span></span></code></pre></div><p>We also provide a convenience function for counting the number of correct predictions by our resulting model against either the test or training data sets.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">count_correct</span>(data_set, coefficients):
</span></span><span style="display:flex;"><span>    <span style="color:#a5d6ff">&#39;&#39;&#39;Returns the number of correct predictions.&#39;&#39;&#39;</span>
</span></span><span style="display:flex;"><span>    correct <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> d <span style="color:#ff7b72;font-weight:bold">in</span> data_set:
</span></span><span style="display:flex;"><span>        result <span style="color:#ff7b72;font-weight:bold">=</span> sum(x<span style="color:#ff7b72;font-weight:bold">*</span>y <span style="color:#ff7b72">for</span> x, y <span style="color:#ff7b72;font-weight:bold">in</span> zip(coefficients, d[<span style="color:#a5d6ff">&#39;input&#39;</span>]))
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>        <span style="color:#8b949e;font-style:italic"># Do we predict the same as the output?</span>
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> (result <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0</span>) <span style="color:#ff7b72;font-weight:bold">==</span> (d[<span style="color:#a5d6ff">&#39;output&#39;</span>] <span style="color:#ff7b72;font-weight:bold">&gt;=</span> <span style="color:#a5d6ff">0</span>):
</span></span><span style="display:flex;"><span>            correct <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">1</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> correct
</span></span></code></pre></div><p>Finally we write a main method to read in the data, build our linear model, and test its efficacy.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">from</span> <span style="color:#ff7b72">pprint</span> <span style="color:#ff7b72">import</span> pprint
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Specs for this input file</span>
</span></span><span style="display:flex;"><span>    INPUT_FILE_NAME <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">&#39;cancer1.dt&#39;</span>
</span></span><span style="display:flex;"><span>    TRAIN_SIZE <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">350</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    train_data, test_data <span style="color:#ff7b72;font-weight:bold">=</span> read_proben1_cancer_data(
</span></span><span style="display:flex;"><span>        INPUT_FILE_NAME,
</span></span><span style="display:flex;"><span>        TRAIN_SIZE
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Add the offset variable to each of our data records</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">for</span> data_set <span style="color:#ff7b72;font-weight:bold">in</span> [train_data, test_data]:
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">for</span> row <span style="color:#ff7b72;font-weight:bold">in</span> data_set:
</span></span><span style="display:flex;"><span>            row[<span style="color:#a5d6ff">&#39;input&#39;</span>] <span style="color:#ff7b72;font-weight:bold">=</span> [<span style="color:#a5d6ff">1</span>] <span style="color:#ff7b72;font-weight:bold">+</span> row[<span style="color:#a5d6ff">&#39;input&#39;</span>]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    coefficients <span style="color:#ff7b72;font-weight:bold">=</span> train_linear_model(train_data)
</span></span><span style="display:flex;"><span>    print(<span style="color:#a5d6ff">&#39;coefficients:&#39;</span>)
</span></span><span style="display:flex;"><span>    pprint(coefficients)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Print % of correct predictions for each data set</span>
</span></span><span style="display:flex;"><span>    correct <span style="color:#ff7b72;font-weight:bold">=</span> count_correct(train_data, coefficients)
</span></span><span style="display:flex;"><span>    print(
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff"> / </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff"> = </span><span style="color:#a5d6ff">%.02f%%</span><span style="color:#a5d6ff"> correct on training set&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (
</span></span><span style="display:flex;"><span>            correct, len(train_data),
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">100</span> <span style="color:#ff7b72;font-weight:bold">*</span> float(correct) <span style="color:#ff7b72;font-weight:bold">/</span> len(train_data)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    )
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    correct <span style="color:#ff7b72;font-weight:bold">=</span> count_correct(test_data, coefficients)
</span></span><span style="display:flex;"><span>    print(
</span></span><span style="display:flex;"><span>        <span style="color:#a5d6ff">&#39;</span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff"> / </span><span style="color:#a5d6ff">%s</span><span style="color:#a5d6ff"> = </span><span style="color:#a5d6ff">%.02f%%</span><span style="color:#a5d6ff"> correct on testing set&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> (
</span></span><span style="display:flex;"><span>            correct, len(test_data),
</span></span><span style="display:flex;"><span>            <span style="color:#a5d6ff">100</span> <span style="color:#ff7b72;font-weight:bold">*</span> float(correct) <span style="color:#ff7b72;font-weight:bold">/</span> len(test_data)
</span></span><span style="display:flex;"><span>        )
</span></span><span style="display:flex;"><span>    )
</span></span></code></pre></div><h2 id="results">Results</h2>
<p>The result of running this model against the <code>cancer1.dt</code> data set is:</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-txt" data-lang="txt"><span style="display:flex;"><span>coefficients:
</span></span><span style="display:flex;"><span>[1.4072882449702786,
</span></span><span style="display:flex;"><span> -0.14014055927954652,
</span></span><span style="display:flex;"><span> -0.6239513714263405,
</span></span><span style="display:flex;"><span> -0.26727681774258882,
</span></span><span style="display:flex;"><span> 0.067107753841131157,
</span></span><span style="display:flex;"><span> -0.28300216102808429,
</span></span><span style="display:flex;"><span> -1.0355594670918404,
</span></span><span style="display:flex;"><span> -0.22774451038152174,
</span></span><span style="display:flex;"><span> -0.69871243677663608,
</span></span><span style="display:flex;"><span> -0.072575089848659444]
</span></span><span style="display:flex;"><span>328 / 350 = 93.71% correct on training set
</span></span><span style="display:flex;"><span>336 / 349 = 96.28% correct on testing set
</span></span></code></pre></div><p>The accuracy is pretty good here against the both the training and testing sets, so this particular model generalizes well.  This is about the simplest model we can implement for data fitting, and we&rsquo;ll get to more complicated ones later, but it&rsquo;s nice to see we can do so well so quickly.  The coefficients correspond to using a function of this form, rounding off to three decimal places:</p>
<p>$$
\begin{align*}
f(x) =\ &amp;1.407 - 0.140 x_1 - 0.624 x_2 - 0.267 x_3 + 0.067 x_4 - \\
&amp;0.283 x_5 - 1.037 x_6 - 0.228 x_7 - 0.699 x_8 - 0.073 x_9
\end{align*}
$$</p>
<h2 id="resources">Resources</h2>
<ul>
<li><a href="/files/2010-11-23-data-fitting-1-linear-data-fitting/cancer1.dt"><code>cancer1.dt</code></a> data file from <code>proben1</code></li>
<li>Full <a href="/files/2010-11-23-data-fitting-1-linear-data-fitting/fit-linear.py">source listing</a></li>
</ul>
]]></content:encoded>
    </item>
    <item>
      <title>🐍 Monte Carlo Simulation in Python</title>
      <link>https://ryanjoneil.dev/posts/2009-10-08-monte-carlo-simulation-in-python/</link>
      <pubDate>Thu, 08 Oct 2009 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2009-10-08-monte-carlo-simulation-in-python/</guid>
      <description>A quick introduction to writing and interpreting Monte Carlo simulations in Python</description>
      <content:encoded><![CDATA[<p><em>Note: This post was updated to work with Python 3.</em></p>
<p>One of the most useful tools one learns in an Operations Research curriculum is <a href="https://en.wikipedia.org/wiki/Monte_Carlo_method">Monte Carlo Simulation</a>. Its utility lies in its simplicity: one can learn vital information about nearly any process, be it deterministic or stochastic, without wading through the grunt work of finding an analytical solution. It can be used for off-the-cuff estimates or as a proper scientific tool. All one needs to know is how to simulate a given process and its appropriate probability distributions and parameters if that process is stochastic.</p>
<p>Here&rsquo;s how it works:</p>
<ul>
<li>Construct a simulation that, given input values, returns a value of interest. This could be a pure quantity, like time spent waiting for a bus, or a boolean indicating whether or not a particular event occurs.</li>
<li>Run the simulation a, usually large, number of times, each time with randomly generated input variables. Record its output values.</li>
<li>Compute sample mean and variance of the output values.</li>
</ul>
<p>In the case of time spent waiting for a bus, the sample mean and variance are estimators of mean and variance for one&rsquo;s wait time. In the boolean case, these represent probability that the given event will occur.</p>
<p>One can think of Monte Carlo Simulation as throwing darts. Say you want to find the area under a curve without integrating. All you must do is draw the curve on a wall and throw darts at it randomly. After you&rsquo;ve thrown enough darts, the area under the curve can be approximated using the percentage of darts that end up under the curve times the total area.</p>
<p>This technique is often performed using a spreadsheet, but that can be a bit clunky and may make more complex simulations difficult. I&rsquo;d like to spend a minute showing how it can be done in Python. Consider the following scenario:</p>
<p>Passengers for a train arrive according to a Poisson process with a mean of 100 per hour. The next train arrives exponentially with a rate of 5 per hour. How many passers will be aboard the train?</p>
<p>We can simulate this using the fact that a Poisson process can be represented as a string of events occurring with exponential inter-arrival times. We use the <code>sim()</code> function below to generate the number of passengers for random instances of the problem. We then compute sample mean and variance for these values.</p>
<div class="highlight"><pre tabindex="0" style="color:#e6edf3;background-color:#0d1117;-moz-tab-size:4;-o-tab-size:4;tab-size:4;"><code class="language-python" data-lang="python"><span style="display:flex;"><span><span style="color:#ff7b72">import</span> <span style="color:#ff7b72">random</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>PASSENGERS <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">100.0</span>
</span></span><span style="display:flex;"><span>TRAINS     <span style="color:#ff7b72;font-weight:bold">=</span>   <span style="color:#a5d6ff">5.0</span>
</span></span><span style="display:flex;"><span>ITERATIONS <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">10000</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">def</span> <span style="color:#d2a8ff;font-weight:bold">sim</span>():
</span></span><span style="display:flex;"><span>    passengers <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0.0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Determine when the train arrives</span>
</span></span><span style="display:flex;"><span>    train <span style="color:#ff7b72;font-weight:bold">=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>expovariate(TRAINS)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#8b949e;font-style:italic"># Count the number of passenger arrivals before the train</span>
</span></span><span style="display:flex;"><span>    now <span style="color:#ff7b72;font-weight:bold">=</span> <span style="color:#a5d6ff">0.0</span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">while</span> <span style="color:#79c0ff">True</span>:
</span></span><span style="display:flex;"><span>        now <span style="color:#ff7b72;font-weight:bold">+=</span> random<span style="color:#ff7b72;font-weight:bold">.</span>expovariate(PASSENGERS)
</span></span><span style="display:flex;"><span>        <span style="color:#ff7b72">if</span> now <span style="color:#ff7b72;font-weight:bold">&gt;=</span> train:
</span></span><span style="display:flex;"><span>            <span style="color:#ff7b72">break</span>
</span></span><span style="display:flex;"><span>        passengers <span style="color:#ff7b72;font-weight:bold">+=</span> <span style="color:#a5d6ff">1.0</span>
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    <span style="color:#ff7b72">return</span> passengers
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span><span style="color:#ff7b72">if</span> <span style="color:#79c0ff">__name__</span> <span style="color:#ff7b72;font-weight:bold">==</span> <span style="color:#a5d6ff">&#39;__main__&#39;</span>:
</span></span><span style="display:flex;"><span>    output <span style="color:#ff7b72;font-weight:bold">=</span> [sim() <span style="color:#ff7b72">for</span> _ <span style="color:#ff7b72;font-weight:bold">in</span> range(ITERATIONS)]
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    total <span style="color:#ff7b72;font-weight:bold">=</span> sum(output)
</span></span><span style="display:flex;"><span>    mean <span style="color:#ff7b72;font-weight:bold">=</span> total <span style="color:#ff7b72;font-weight:bold">/</span> len(output)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    sum_sqrs <span style="color:#ff7b72;font-weight:bold">=</span> sum(x<span style="color:#ff7b72;font-weight:bold">*</span>x <span style="color:#ff7b72">for</span> x <span style="color:#ff7b72;font-weight:bold">in</span> output)
</span></span><span style="display:flex;"><span>    variance <span style="color:#ff7b72;font-weight:bold">=</span> (sum_sqrs <span style="color:#ff7b72;font-weight:bold">-</span> total <span style="color:#ff7b72;font-weight:bold">*</span> mean) <span style="color:#ff7b72;font-weight:bold">/</span> (len(output) <span style="color:#ff7b72;font-weight:bold">-</span> <span style="color:#a5d6ff">1</span>)
</span></span><span style="display:flex;"><span>
</span></span><span style="display:flex;"><span>    print(<span style="color:#a5d6ff">&#39;E[X] = </span><span style="color:#a5d6ff">%.02f</span><span style="color:#a5d6ff">&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> mean)
</span></span><span style="display:flex;"><span>    print(<span style="color:#a5d6ff">&#39;Var(X) = </span><span style="color:#a5d6ff">%.02f</span><span style="color:#a5d6ff">&#39;</span> <span style="color:#ff7b72;font-weight:bold">%</span> variance)
</span></span></code></pre></div>]]></content:encoded>
    </item>
    <item>
      <title>⚡️ On the Beauty of Power Sets</title>
      <link>https://ryanjoneil.dev/posts/2009-02-27-on-the-beauty-of-power-sets/</link>
      <pubDate>Fri, 27 Feb 2009 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2009-02-27-on-the-beauty-of-power-sets/</guid>
      <description>Using power sets in algebraic modeling languages for formulating the Traveling Salesman Problem</description>
      <content:encoded><![CDATA[<p>One of the difficulties we encounter in solving the <a href="https://www.math.uwaterloo.ca/tsp/">Traveling Salesman Problem</a> (TSP) is that, for even a small number of cities, a complete description of the problem requires a factorial number of constraints. This is apparent in the standard formulation used to teach the TSP to OR students. Consider a set of $n$ cities with the distance from city $i$ to city $j$ denoted $d_{ij}$.  We attempt to minimize the total distance of a tour entering and leaving each city exactly once. $x_{ij} = 1$ if the edge from city $i$ to city $j$ is included in the tour, $0$ otherwise:</p>
<p>$$
\small
\begin{align*}
\min\quad       &amp; z = \sum_i \sum_{j\ne i} d_{ij} x_{ij}\\
\text{s.t.}\quad&amp; \sum_{j\ne i} x_{ij} = 1 &amp;\quad\forall&amp;\ i &amp; \text{leave each city once}\\
&amp; \sum_{i\ne j} x_{ij} = 1 &amp;\quad\forall&amp;\ j &amp; \text{enter each city once}\\
&amp; x_{ij} \in \{0,1\}       &amp;\quad\forall&amp;\ i,j
\end{align*}
$$</p>
<p>This appears like a reasonable formulation until we solve it and see that our solution contains disconnected subtours. Suppose we have four cities, labeled $A$ through $D$. Connecting $A$ to $B$, $B$ to $A$, $C$ to $D$ and $D$ to $C$ provides a feasible solution to our formulation, but does not constitute a cycle. Here is a more concrete example of two disconnected subtours $\{(1,5),(5,1)\}$ and $\{(2,3),(3,4),(4,2)\}$ over five cities:</p>
<pre tabindex="0"><code>ampl: display x;
x [*,*]
:   1   2   3   4   5    :=
1   0   0   0   0   1
2   0   0   1   0   0
3   0   0   0   1   0
4   0   1   0   0   0
5   1   0   0   0   0
;
</code></pre><p>Realizing we just solved the <a href="https://en.wikipedia.org/wiki/Assignment_problem">Assignment Problem</a>, we now add subtour elimination constraints. These require that any proper, non-null subset of our $n$ cities is connected by at most $n-1$ active edges:</p>
<p>$$
\sum_{i \in S} \sum_{j \in S} x_{ij} \leq |S|-1 \quad\forall\ S \subset {1, &hellip;, n}, S \ne O
$$</p>
<p>Indexing subtour elimination constraints over a <a href="https://en.wikipedia.org/wiki/Power_set">power set</a> of the cities completes the formulation. However, this requires an additional $\sum_{k=2}^{n-1} \begin{pmatrix} n \\ k \end{pmatrix}$ rows tacked on the end of our matrix and is clearly infeasible for large $n$. The most current computers can handle using this approach <a href="http://zimpl.zib.de/download/zimpl.pdf">is around 19 cities</a>. It remains an instructive tool for understanding the <a href="https://en.wikipedia.org/wiki/Combinatorial_explosion">combinatorial explosion</a> that occurs in problems like TSP and is worth translating into a modeling language. So how does one model it on a computer?</p>
<p>Unfortunately, <a href="https://ampl.com/">AMPL</a>, the gold standard in mathematical modeling languages, is unable to index over sets. Creating a power set in AMPL requires going through a few contortions.  The following code demonstrates power and index sets over four cities:</p>
<pre tabindex="0"><code class="language-ampl" data-lang="ampl">set cities := 1 .. 4 ordered;

param n := card(cities);
set indices := 0 .. (2^n - 1);
set power {i in indices} := {c in cities: (i div 2^(ord(c) - 1)) mod 2 = 1};

display cities;
display n;
display indices;
display power;
</code></pre><p>This yields the following output:</p>
<pre tabindex="0"><code class="language-ampl" data-lang="ampl">set cities := 1 2 3 4;

n = 4

set indices := 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15;

set power[0] := ; # empty
set power[1] := 1;
set power[2] := 2;
set power[3] := 1 2;
set power[4] := 3;
set power[5] := 1 3;
set power[6] := 2 3;
set power[7] := 1 2 3;
set power[8] := 4;
set power[9] := 1 4;
set power[10] := 2 4;
set power[11] := 1 2 4;
set power[12] := 3 4;
set power[13] := 1 3 4;
set power[14] := 2 3 4;
set power[15] := 1 2 3 4;
</code></pre><p>Note how the index set contains an index for each row in our power set. We can now generate the subtour elimination constraints:</p>
<pre tabindex="0"><code class="language-ampl" data-lang="ampl">var x {cities cross cities} binary;
s.t. subtours {i in indices: card(power[i]) &gt; 1 and card(power[i]) &lt; card(cities)}:
sum {(c,k) in power[i] cross power[i]: k != c} x[c,k] &lt;= card(power[i]) - 1;

expand subtours;

subject to subtours[3]:  x[1,2] + x[2,1] &lt;= 1;
subject to subtours[5]:  x[1,3] + x[3,1] &lt;= 1;
subject to subtours[6]:  x[2,3] + x[3,2] &lt;= 1;
subject to subtours[7]:  x[1,2] + x[1,3] + x[2,1] + x[2,3] + x[3,1] + x[3,2] &lt;= 2;
subject to subtours[9]:  x[1,4] + x[4,1] &lt;= 1;
subject to subtours[10]: x[2,4] + x[4,2] &lt;= 1;
subject to subtours[11]: x[1,2] + x[1,4] + x[2,1] + x[2,4] + x[4,1] + x[4,2] &lt;= 2;
subject to subtours[12]: x[3,4] + x[4,3] &lt;= 1;
subject to subtours[13]: x[1,3] + x[1,4] + x[3,1] + x[3,4] + x[4,1] + x[4,3] &lt;= 2;
subject to subtours[14]: x[2,3] + x[2,4] + x[3,2] + x[3,4] + x[4,2] + x[4,3] &lt;= 2;
</code></pre><p>While this does work, the code for generating the power set looks like <a href="https://foldoc.org/voodoo+programming">voodoo</a>. Understanding it required piece-by-piece decomposition, an exercise I suggest you go through yourself if you have a copy of AMPL and 15 minutes to spare:</p>
<pre tabindex="0"><code class="language-ampl" data-lang="ampl">set foo {c in cities} := {ord(c)};
set bar {c in cities} := {2^(ord(c) - 1)};
set baz {i in indices} := {c in cities: i div 2^(ord(c) - 1)};
set qux {i in indices} := {c in cities: (i div 2^(ord(c) - 1)) mod 2 = 1};

display foo;
display bar;
display baz;
display qux;
</code></pre><p>This may be an instance where open source leads commercial software. The good folks who produce the <a href="https://scipopt.org/">SCIP Optimization Suite</a> provide an AMPL-like language called <a href="https://zimpl.zib.de/">ZIMPL</a> with a few additional useful features. One of these is power sets. Compared to the code above, doesn&rsquo;t this look refreshing?</p>
<pre tabindex="0"><code class="language-ampl" data-lang="ampl">set cities := {1 to 4};

set power[] := powerset(cities);
set indices := indexset(power);
</code></pre>]]></content:encoded>
    </item>
    <item>
      <title>📐 Uncapacitated Lot Sizing</title>
      <link>https://ryanjoneil.dev/posts/2009-02-20-uncapacitated-lot-sizing/</link>
      <pubDate>Fri, 20 Feb 2009 00:00:00 +0000</pubDate>
      <guid>https://ryanjoneil.dev/posts/2009-02-20-uncapacitated-lot-sizing/</guid>
      <description>Formulation and aspects of the Uncapacitated Lot Sizing problem in Integer Programming</description>
      <content:encoded><![CDATA[<p>Uncapacitated Lot Sizing (ULS) is a classic <a href="http://en.wikipedia.org/wiki/Operations_research">OR</a> problem that seeks to minimize the cost of satisfying known demand for a product over time.  Demand is subject to varying costs for production, set-up, and storage of the product.  Technically, it is a mixed binary integer linear program &ndash; the key point separating it from the world of <a href="http://en.wikipedia.org/wiki/Linear_programming">linear optimization</a> being that production cannot occur during any period without paying that period&rsquo;s fixed costs for set-up.  Thus it has linear nonnegative variables for production and storage amounts during each period, and a binary variable for each period that determines whether or not production can actually occur.</p>
<p>For $n$ periods with per-period fixed set-up cost $f_t$, unit production cost $p_t$, unit storage cost $h_t$,and demand $d_t$, we define decision variables related to production and storage quantities:</p>
<p>$$
\small
\begin{align*}
x_t &amp;= \text{units produced in period}\ t\\
s_t &amp;= \text{stock at the end of period}\ t\\
y_t &amp;= 1\ \text{if production occurs in period}\ t, 0\ \text{otherwise}
\end{align*}
$$</p>
<p>One can minimize overall cost for satisfying all demand on time using the following model per <a href="https://www.amazon.com/Integer-Programming-Laurence-Wolsey/dp/0471283665/">Wolsey (1998)</a>, defined slightly differently here:</p>
<p>$$
\small
\begin{align*}
\min\quad       &amp; z = \sum_t{p_t x_t} + \sum_t{h_t s_t} + \sum_t{f_t y_t}\\
\text{s.t.}\quad&amp; s_1 = d_1 + s_1\\
&amp; s_{t-1} + x_t = d_t + s_t &amp;\quad\forall&amp;\ t &gt; 1\\
&amp; x_t \leq M y_t            &amp;\quad\forall&amp;\ t\\
&amp; s_t, x_t \geq 0           &amp;\quad\forall&amp;\ t\\
&amp; y_t \in {0,1}           &amp;\quad\forall&amp;\ t
\end{align*}
$$</p>
<p>According to Wolsey, page 11, given that $s_t = \sum_{i=1}^t (x_i - d_i)$ and defining new constants $K = \sum_{t=1}^n h_t(\sum_{i=1}^t d_i)$ and $c_t = p_t + \sum_{i=t}^n h_i$, the objective function can be rewritten as $z = \sum_t c_t x_t + \sum _t f_t y_t - K$.  The book lacks a proof of this and it seems a bit non-obvious, so I attempt an explanation in somewhat painstaking detail here.</p>
<p>$$
\small
\begin{align*}
&amp;\text{Proof}:\\
&amp; &amp; \sum_t p_t x_t + \sum_t h_t s_t + \sum_t f_t y_t &amp;= \sum_t c_t x_t + \sum _t f_t y_t - K\\
&amp;\text{1. Remove} \sum_t f_t y_t:\\
&amp; &amp; \sum_t p_t x_t + \sum_t h_t s_t &amp;= \sum_t c_t x_t - K\\
&amp;\text{2. Expand}\ K\ \text{and}\ c_t:\\
&amp; &amp; \sum_t p_t x_t + \sum_t h_t s_t &amp;= \sum_t (p_t + \sum_{i=t}^n h_i) x_t - \sum_t h_t (\sum_{i=1}^t d_i)\\
&amp;\text{3. Remove}\ \sum_t p_t x_t:\\
&amp; &amp; \sum_t h_t s_t &amp;= \sum_t x_t (\sum_{i=t}^n h_i) - \sum_t h_t (\sum_{i=1}^t d_i)\\
&amp;\text{4. Expand}\ s_t:\\
&amp; &amp; \sum_t h_t (\sum_{i=1}^t x_i) - \sum_t h_t (\sum_{i=1}^t d_i) &amp;= \sum_t x_t (\sum_{i=t}^n h_i) - \sum_t h_t (\sum_{i=1}^t d_i)\\
&amp;\text{5. Remove}\ \sum_t h_t (\sum_{i=1}^t d_i):\\
&amp; &amp; \sum_t h_t (\sum_{i=1}^t x_i) &amp;= \sum_t x_t (\sum_{i=t}^n h_i)
\end{align*}
$$</p>
<p>The result from step 5 becomes obvious upon expanding its left and right-hand terms:</p>
<p>$$
h_1 x_1 + h_2 (x_1 + x_2) + \cdots + h_n (x_1 + \cdots + x_n) =\\
x_1 (h_1 + \cdots + h_n) + x2 (h_2 + \cdots + h_n) + \cdots + x_n h_n
$$</p>
<p>In matrix notation with $h$ and $x$ as column vectors in $\bf R^n$ and $L$ and $U$ being $n \times n$ lower and upper triangular identity matrices, respectively, this can be written as:</p>
<p>$$
\small
\begin{pmatrix}
h_1 \cdots h_n
\end{pmatrix}
\begin{pmatrix}
1 \cdots 0 \\
\vdots \ddots \vdots \\
1 \cdots 1
\end{pmatrix}
\begin{pmatrix}
x_1 \\
\vdots \\
x_n
\end{pmatrix} =
\begin{pmatrix}
x_1 \cdots x_n
\end{pmatrix}
\begin{pmatrix}
1 \cdots 1 \\
\vdots \ddots \vdots \\
0 \cdots 1
\end{pmatrix}
\begin{pmatrix}
h_1 \\
\vdots \\
h_n
\end{pmatrix}
$$</p>
<p>or $h^T L x = x^T U h$.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
