<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/styles/rss-style.xsl"?>

<rss version="2.0"
 xmlns:blogChannel="http://backend.userland.com/blogChannelModule"
>

<channel>
<title>troglodyne.net</title>
<link>http://troglodyne.net//posts/6ff13eb0-13de-11ec-84d9-8f4e0c69f986?format=xml</link>
<description>troglodyne.net : /posts/6ff13eb0-13de-11ec-84d9-8f4e0c69f986</description>
<language>en</language>
<pubDate>2026-04-23T19:37:34</pubDate>
<lastBuildDate>2026-04-23T19:37:34</lastBuildDate>

<image>
<title>troglodyne.net</title>
<url>/favicon.ico</url>
<link>http://troglodyne.net</link>
<width>32</width>
<height>32</height>
<description>troglodyne.net favicon</description>
</image>
<item>
<title>Hard Problems</title>
<link>http://troglodyne.net/posts/6ff13eb0-13de-11ec-84d9-8f4e0c69f986</link>
<description><![CDATA[<p> When preparing any tool which you see all the pieces readily
      available, but that nobody has executed upon, you begin to ask
      yourself why that is. This is essentially what I've been going
      through building the <a moz-do-not-send="true"
        href="https://troglodyne.net/video/1616627599">pairwise</a>
      tool. </p>
    <p>Every time&nbsp; I look around and don't see a solution for an
      old problem on CPAN, my spider-senses start to fire.&nbsp; I saw
      no N-dimensional combination methods (only n Choose k) or bin
      covering algorithms, and when you see a lack of N-dimensional
      solutions that usually means there is a lack of closed form
      general solutions to that problem.&nbsp; While this is not true
      for my problem space, it rubs right up against the edge of NP hard
      problems.&nbsp; So it's not exactly shocking I didn't see anything
      fit to purpose.<br>
    </p>
    <p> The idea behind<a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/All-pairs_testing"> pairwise
        test execution</a> is actually quite simple, but the constraints
      of the software systems surrounding it risk making it more complex
      than is manageable. This is because unless we confine ourselves to
      a very specific set of constraints, we run into not one, but two
      NP hard problems. We could then be forced into the unfortunate
      situation where we have to use Polynomial time approximations.<br>
    </p>
    <p>I've run into this a few times in my career. Each time the team
      grows disheartened as what the customer wants seems on the surface
      to be impossible. I always remember that there is always a way to
      win by cheating (more tight constraints). Even the tyranny of the
      rocket equation was overcome through these means (let's put a
      little rocket on a big one!)<br>
    </p>
    <h3>Breaking it down<br>
    </h3>
    <p>The first problem is that N-Wise test choosing is simply a <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Combination">combination</a>.<br>
      This results in far, far more platforms to test than is practical
      once you get beyond 3 independent variables relevant to your
      system under test. For example:<br>
    </p>
    <p>A combination with 3 sets containing 3, 5 and 8 will result in 3
      * 5 * 8 = 120 systems under test! Adding in a fourth or fifth will
      quickly bring you into the territory of <i>thousands of systems </i>to



      test.&nbsp; While this is straightforward to accomplish these
      days, it is quite expensive.<br>
    </p>
    <p>What we actually want is an expression of the <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Pigeonhole_principle">pigeonhole



        principle</a>.&nbsp; We wish to build sets where <em>every
        element of each component set is seen at least once, </em>as
      this will cover everything with the minimum number of needed
      systems under test.&nbsp; This preserves the practical purpose of
      pairwise testing quite nicely.<br>
      <em></em></p>
    <p> In summary, we have a <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Clique_(graph_theory)">clique</a>
      problem and a <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Bin_covering_problem">bin
        covering</a> problem. This means that we have to build a number
      of bins from X number of sets each containing some amount of
      members. We then have to fill said bins with a bunch of tests in a
      way which will result in them being executed as fast as is
      possible. </p>
    <p> Each bin we build will represent some system under test, and
      each set from which we build these bins a particular important
      attribute. For example, consider these sets: </p>
    <ul>
      <li> <b>Operating Systems</b>: Windows, Linux, OSX</li>
      <li><b>Processor Architecture</b>: 32-bit, 64-bit</li>
      <li><b>Browser</b>: Firefox, Chrome, Safari, Brave, Opera,
        SeaMonkey </li>
    </ul>
    <p>A random selection will result in an optimal multi-dimensional
      "pairwise" set of systems under test: </p>
    <ol>
      <li> Firefox - Windows - 64 Bit</li>
      <li>Chrome - Linux - 32 Bit</li>
      <li>Safari - Windows - 32 Bit</li>
      <li>Brave - OSX - 32 Bit</li>
      <li>Opera - OSX - 64 Bit</li>
      <li>SeaMonkey - Linux - 64-Bit<br>
      </li>
    </ol>
    <p> The idea is to pick one of each of the set with the most members
      and then pick from the remaining ones at the index of the current
      pick from the big set modulo the smaller set's size. This is the
      "weak" form of the Pigeonhole Principle in action, which is why it
      is solved easily with the <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Chinese_remainder_theorem">Chinese



        remainder theorem</a>.<br>
    </p>
    <h3>Sometimes you can oversimplify</h3>
    <p> You may have noticed that perhaps we are going too far with our
      constraints here. This brings in danger, as the "strong" general
      form of the pigeonhole principle means we are treading into the
      waters of Ramsey's (clique) problem. For example, if we drop
      either of these two assumptions we can derive from our sets: </p>
    <ol>
      <li> No element of any given set is repeated</li>
      <li>No element of any given set is shared with another </li>
    </ol>
    <p> We immediately descend into the realm of the NP hard problem.
      This is because we are no longer a principal ideal domain and can
      no longer cheat using the Chinese remainder theorem. In this
      reality, we are solving the <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Independent_set_(graph_theory)">Anti-Clique</a>
      problem specifically, which is particularly nasty. Thankfully, we
      can consider those two constraints to be quite realistic.<br>
    </p>
    <p>We will have to account for the fact that the variables are
      actually not independent. You may have noticed that some of these
      "optimal" configurations are not actually realistic. Many
      Operating systems do not support various processor architectures
      and software packages. Three of the configurations above are
      currently invalid for at least one reason.&nbsp; Consider a
      configuration object like so:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;PlatformGroups&nbsp;=&gt;&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Operating&nbsp;Systems'</span><span style="color: #d4d4d4;">&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{CentOS&nbsp;Ubuntu&nbsp;Windows&nbsp;OSX}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'CPU&nbsp;Archetechure'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{32-bit&nbsp;64-bit}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Browser'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{Firefox&nbsp;Opera&nbsp;Safari&nbsp;Chrome&nbsp;Iexplore&nbsp;Brave&nbsp;Dillo&nbsp;lynx}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Mail&nbsp;Server'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{exim&nbsp;courier&nbsp;postfix&nbsp;qmail&nbsp;exchange}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'HTTP&nbsp;Server'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{ngnix&nbsp;apache&nbsp;lighttpd&nbsp;thttpd}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Database&nbsp;Server'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{postgres&nbsp;mysql&nbsp;mariadb&nbsp;mssql&nbsp;oracle}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Message&nbsp;Queue'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{rabbitmq&nbsp;zmq}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Search&nbsp;Engine'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{solr&nbsp;lunr&nbsp;elasticsearch}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;},</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;incompatibilities&nbsp;=&gt;&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Windows'</span><span style="color: #d4d4d4;">&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{32-bit&nbsp;Safari&nbsp;Dillo&nbsp;qmail&nbsp;exim&nbsp;courier&nbsp;postfix&nbsp;thttpd&nbsp;solr}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'OSX'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{32-bit&nbsp;Iexplore}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'CentOS'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{Iexplore}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Ubuntu'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{Iexplore}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;},</span></div><div><span style="color: #d4d4d4;">};</span></div></div>
    <div>Thanks&nbsp;to&nbsp;the&nbsp;requirement&nbsp;that&nbsp;all&nbsp;configurations&nbsp;be&nbsp;unique,&nbsp;we&nbsp;can&nbsp;use&nbsp;a&nbsp;simplified&nbsp;data&nbsp;structure&nbsp;here


      rather than over-complicating the PlatformGroup data structure
      (and our processor code).<br>
    </div>
    <p>Can we throw away these configurations without simply
      "re-rolling" the dice?&nbsp; Unfortunately, no.&nbsp; Not without
      using the <a moz-do-not-send="true"
        href="https://blog.codinghorror.com/the-god-login/">god
        algorithm</a> of computing every possible combination ahead of
      time, and therefore already knowing the answer.&nbsp; As such our
      final implementation looks like so:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">cliques</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$tests</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'HASH'</span><span style="color: #d4d4d4;">&nbsp;?&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}}&nbsp;:&nbsp;();<br></span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Randomize&nbsp;the&nbsp;ordering&nbsp;of&nbsp;the&nbsp;platform&nbsp;groups&nbsp;for&nbsp;eventual&nbsp;consistency.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}}&nbsp;=&nbsp;shuffle(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;The&nbsp;idea&nbsp;here&nbsp;is&nbsp;to&nbsp;have&nbsp;at&nbsp;least&nbsp;one&nbsp;pigeon&nbsp;in&nbsp;each&nbsp;hole.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;This&nbsp;is&nbsp;accomplished&nbsp;by&nbsp;finding&nbsp;the&nbsp;longest&nbsp;list&nbsp;of&nbsp;groups,&nbsp;and&nbsp;then&nbsp;iterating&nbsp;over&nbsp;everything&nbsp;we&nbsp;have&nbsp;modulo&nbsp;their&nbsp;size.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}&nbsp;</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))[0];</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$tests</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Bin&nbsp;covering</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">int</span><span style="color: #d4d4d4;">(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;/&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;0;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">for</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">=0;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;&lt;&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">++)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">&nbsp;( </span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}</span><span style="color: #dcdcaa;"> keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span><span style="color: #d4d4d4;"></span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;If&nbsp;a&nbsp;partial&nbsp;is&nbsp;invalid,&nbsp;we&nbsp;must re-roll the dice.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">while</span><span style="color: #d4d4d4;">&nbsp;(!combination_valid(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,&nbsp;,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;1)&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Allow&nbsp;for&nbsp;'incomplete'&nbsp;sets&nbsp;omitting&nbsp;a&nbsp;configuration&nbsp;group&nbsp;entirely&nbsp;due&nbsp;to&nbsp;total&nbsp;incompatibility</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">last</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;==&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div>	push(@plans, \@newplats);<br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;\</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">}<br><br></span><div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">combination_valid</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{incompatibilities}};</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">next</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">unless</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'ARRAY'</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #dcdcaa;">defined</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;||&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">&nbsp;}&nbsp;@{</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}}&nbsp;)&nbsp;}&nbsp;</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;0&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;&gt;&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;1;</span></div><div><span style="color: #d4d4d4;">}</span></div></div><span style="color: #d4d4d4;"></span></div></div>
    <p>This brings us to another unmentioned constraint: what happens if
      a member of a set is incompatible with all members of another
      set?&nbsp; It turns out accepting this is actually a <i>significant


        optimization</i>, as we will end up <i>never having to re-roll
        an entire sequence</i>.&nbsp; See the while loop above.<br>
    </p>
    <p> Another complication is the fact that we will have to randomize
      the set order to achieve the goal of eventual coverage of every
      possible combination. Given the intention of the tool is to run
      decentralized and <i>without a central oracle</i> other than git,
      we'll have to also have use a seed based upon it's current
      state.&nbsp; The algorithm above does <i>not</i> implement this,
      but it should be straightforward to add.<br>
    </p>
    <h3>Filling the bins<br>
    </h3>
    <p> We at least have a solution to the problem of building the bins.
      So, we can move on to filling them. Here we will encounter
      trade-offs which are quite severe. If we wish to accurately
      reflect reality with our assumptions, we immediately stray into
      "no closed form solution" territory. This is the <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Fair_item_allocation">Fair
        Item Allocation</a> problem, but with a significant twist.&nbsp;
      To take advantage of our available resources better, we should
      always execute<i> at least one test</i>. This will result in fewer
      iterations to run through every possible combination of systems to
      test, but also means we've cheated by adding a "double spend" on
      the low-end.&nbsp; Hooray cheating! </p>
    <p> The fastest approximation is essentially to dole out a number of
      tests equal to the floor of dividing the tests equally among the
      bins <i>plus</i> floor(&nbsp; (tests % bins)&nbsp; / tests ) in
      the case you have less tests than bins. This has an error which is
      not significant until you reach millions of tests. We then get
      eaten alive by rounding error due to flooring.<br>
    </p>
    We could simply add the remainder and give up on fair
    allocation.&nbsp; But given the remainder will always be lower than
    the number of bins, we can just shave one off of it each go-through
    until we run out (while still retaining the minimum bound of
    1).&nbsp; This is is the optimal solution:<br>
    <br>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">my $choose    = int( $total_tests / $bins );<br>my $remainder = $total_tests % bins;<br>...<br># later in our loop<br>my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$take</span><span style="color: #d4d4d4;"> = $choose</span><span style="color: #9cdcfe;"></span><span style="color: #d4d4d4;"> +&nbsp;( </span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;"> &amp;&amp;&nbsp;1&nbsp;)&nbsp;||&nbsp;1;<br>$remainder-- if $remainder;<br></span></div></div>
    <br>
    From there we simply splice out the relevant elements from the array
    of tests.&nbsp; The completed algorithm has some minor differences
    from cliques() above:<br>
    <br>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">cliques</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$tests</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'HASH'</span><span style="color: #d4d4d4;">&nbsp;?&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}}&nbsp;:&nbsp;();</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Randomize&nbsp;the&nbsp;ordering&nbsp;of&nbsp;the&nbsp;platform&nbsp;groups&nbsp;for&nbsp;eventual&nbsp;consistency.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}}&nbsp;=&nbsp;shuffle(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;The&nbsp;idea&nbsp;here&nbsp;is&nbsp;to&nbsp;have&nbsp;at&nbsp;least&nbsp;one&nbsp;pigeon&nbsp;in&nbsp;each&nbsp;hole.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;This&nbsp;is&nbsp;accomplished&nbsp;by&nbsp;finding&nbsp;the&nbsp;longest&nbsp;list&nbsp;of&nbsp;groups,&nbsp;and&nbsp;then&nbsp;iterating&nbsp;over&nbsp;everything&nbsp;we&nbsp;have&nbsp;modulo&nbsp;their&nbsp;size.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}&nbsp;</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))[0];</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$tests</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Bin&nbsp;covering</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">int</span><span style="color: #d4d4d4;">(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;/&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;0;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">for</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">=0;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;&lt;&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">++)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;"> </span><span style="color: #dcdcaa;"><span style="color: #d4d4d4;"></span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}</span><span style="color: #dcdcaa;"> </span>keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;If&nbsp;a&nbsp;partial&nbsp;is&nbsp;invalid,&nbsp;we&nbsp;must re-roll the dice.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">while</span><span style="color: #d4d4d4;">&nbsp;(!combination_valid(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,&nbsp;,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;1)&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Allow&nbsp;for&nbsp;'incomplete'&nbsp;sets&nbsp;omitting&nbsp;a&nbsp;configuration&nbsp;group&nbsp;entirely&nbsp;due&nbsp;to&nbsp;total&nbsp;incompatibility</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">last</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;==&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;1&nbsp;)&nbsp;||&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">,{&nbsp;tests&nbsp;=&gt; [splice(</span><span style="color: #9cdcfe;">@$tests, $offset</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">)],&nbsp;platforms&nbsp;=&gt;&nbsp;\</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">&nbsp;});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">--&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;+=&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">;<br><br></span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Just&nbsp;repeat&nbsp;tests&nbsp;in&nbsp;the&nbsp;event&nbsp;we&nbsp;have&nbsp;more&nbsp;SUTs available than&nbsp;tests</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;\</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">}</span></div></div>
    <p>It is worth noting there is yet another minor optimization in our
      production process here at the end, namely that if we have more
      systems available for tests than tests to execute, we can achieve
      total coverage in less iterations by repeating tests from earlier
      groups.<br>
    </p>
    <h3>Trade-offs in my trade-offs<br>
    </h3>
    Even this makes some significant assumptions:
    <ol>
      <li> Each item we are packing into a bin is of equal size. This
        means every test is assumed to run in the same amount of time <em>on




          the same computer</em>.</li>
      <li>Each item is indivisible</li>
      <li>Each bin values each item equally (in our context this means "<em>every




          computer</em> executes it in the same amount of time")</li>
      <li>Each test will never change in how long it takes to execute
        when it changes, or the system under test does.</li>
      <li>Each <em>bin</em> represents one computer only. </li>
    </ol>
    <p> Obviously the only realistic assumption here is #2. If tests can
      be executed faster by breaking them into smaller tests, the test
      authors should do so, not an argument builder. </p>
    <p> Assumptions #1 and #3, if we take them seriously would not only
      doom us to solving an NP hard problem, but have a host of other
      practical issues. Knowing how long each test takes on each
      computer is quite a large sampling problem, though solvable <em>eventually</em>
      even using only git tags to store this data. Even then, #4 makes
      this an exercise in futility. We really have no choice but to
      accept this source of inefficiency in our production process. </p>
    <p> Invalidating #5 does not bring us too much trouble. Since we
      expect to have a number of test hosts which will satisfy any given
      configuration from the optimal group and will <em>know how many
        there are ahead of time</em>, we can simply split the bin over
      the available hosts and re-run our bin packer over those hosts. </p>
    <p> This will inevitably result in a situation where you have an
      overabundance of available systems under test for some
      configurations and a shortage of others. Given enough tests, this
      can result in workflow disruptions. This is a hard problem to
      solve without "throwing money at the problem", or being more
      judicious with what configurations you support in the first place.
      That is the sort of problem an organization wants to have though.
      It is preferable to the problem of wasting money testing
      everything on every configuration.<br>
    </p>
    <h3>Whither N-wise<br>
    </h3>
    <p>Since the name of the tool is pairwise, I may as well also
      implement and discuss multi-set combinations.&nbsp; Building these
      bins is actually quite straightforward, which is somewhat shocking
      given every algorithm featured for doing pairwise testing at
      pairwise.org was not in fact the optimal one from my 30 year old
      combinatorics textbook.&nbsp; Pretty much all of them used
      tail-call recursion in languages which do not optimize this, or
      they took (good) shortcuts which prevented them from functioning
      in N dimensions.<br>
    </p>
    <p>Essentially you build an iterator which, starting with the first
      set, pushes a partial combination with every element of its set
      matched with one of the second onto your stack.<br>
      You then repeat the process, considering the first set to be the
      partial, and crank right through all the remaining sets.<br>
    </p>
    <p>Dealing with incompatibilities is essentially the same procedure
      as above.&nbsp; The completed algorithm looks like so:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">combine</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$tests</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'HASH'</span><span style="color: #d4d4d4;">&nbsp;?&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}}&nbsp;:&nbsp;();</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#construct&nbsp;iterator</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@pigeonholes</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">values</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$bins</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;product&nbsp;</span><span style="color: #dcdcaa;">map</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$_</span><span style="color: #d4d4d4;">)&nbsp;}&nbsp;</span><span style="color: #9cdcfe;">@pigeonholes</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$tests</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Bin&nbsp;covering</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$bins</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">int</span><span style="color: #d4d4d4;">(&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">&nbsp;/&nbsp;</span><span style="color: #9cdcfe;">$bins</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;0;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;@{</span><span style="color: #9cdcfe;">$pigeonholes</span><span style="color: #d4d4d4;">[0]};</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">while</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">)&nbsp;)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">shift</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#Handle&nbsp;initial&nbsp;elements</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;[</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">]&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ne</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'ARRAY'</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#Break&nbsp;out&nbsp;of&nbsp;the&nbsp;loop&nbsp;if&nbsp;we&nbsp;have&nbsp;no&nbsp;more&nbsp;possibilities&nbsp;to&nbsp;exploit</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$subj</span><span style="color: #d4d4d4;">)&nbsp;==&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@pigeonholes</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;1&nbsp;)&nbsp;||&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">,&nbsp;{&nbsp;tests&nbsp;=&gt;&nbsp;[&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;],&nbsp;platforms&nbsp;=&gt;&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;}&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">--&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;+=&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Just&nbsp;repeat&nbsp;tests&nbsp;in&nbsp;the&nbsp;event&nbsp;we&nbsp;have&nbsp;more&nbsp;SUTs&nbsp;than&nbsp;tests</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">next</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#Keep&nbsp;pushing&nbsp;partials&nbsp;on&nbsp;to&nbsp;the&nbsp;end&nbsp;of&nbsp;the&nbsp;iterator,&nbsp;until&nbsp;we&nbsp;run&nbsp;out&nbsp;of&nbsp;categories&nbsp;to&nbsp;add</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;(@{</span><span style="color: #9cdcfe;">$pigeonholes</span><span style="color: #d4d4d4;">[</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$subj</span><span style="color: #d4d4d4;">)]})&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">@$subj</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;If&nbsp;the&nbsp;combination&nbsp;isn't&nbsp;valid,&nbsp;return&nbsp;an&nbsp;undef&nbsp;member&nbsp;to&nbsp;simplify&nbsp;loop&nbsp;breakout</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;This&nbsp;results&nbsp;in&nbsp;some&nbsp;configurations&nbsp;which&nbsp;are&nbsp;essentially&nbsp;the&nbsp;same.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;That&nbsp;said,&nbsp;we&nbsp;cannot&nbsp;simply&nbsp;discard&nbsp;them&nbsp;if&nbsp;we&nbsp;wish&nbsp;to&nbsp;cover&nbsp;the&nbsp;case&nbsp;a&nbsp;configuration&nbsp;having&nbsp;incompatibilities&nbsp;with&nbsp;entire&nbsp;configuration&nbsp;groups.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;We&nbsp;could&nbsp;compress&nbsp;them&nbsp;later&nbsp;to&nbsp;avoid&nbsp;some&nbsp;slop,&nbsp;but&nbsp;it's&nbsp;probably&nbsp;not&nbsp;worth&nbsp;the&nbsp;effort.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">,&nbsp;combination_valid(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">)&nbsp;?&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;:&nbsp;</span><span style="color: #dcdcaa;">undef</span><span style="color: #d4d4d4;">&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">,\</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;\</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">}</span></div></div>
    <h3>Uniting all under Heaven<br>
    </h3>
    <p>You may have noticed this is a greedy algorithm.&nbsp; If we
      decided to use this as a way to generate a cache for a "god
      algorithm" version of the anti-clique generator above, we could
      very easily run into memory exhaustion with large enough
      configuration sets, defeating the purpose. You could flush the
      partials that are actually complete, but even then you'd only be
      down to 1/n theoretical memory usage where n is the size of your
      2nd largest configuration set (supposing you sort such that it's
      encountered last).&nbsp; This may prove "good enough" in practice,
      especially since users tend to tolerate delays in the "node added
      to network" phase better than the "trying to run tests"
      phase.&nbsp; It would also speed up the matching of available
      systems under test to the desired configuration supersets, as we
      could also "already know the answer".<br>
    </p>
    <p>Profiling this showed that I either had to fix my algorithm or
      resort to this.&nbsp; My "worst case" example of 100 million tests
      using the cliques() method took 3s, while generating everything
      took 4.&nbsp; Profiling shows the inefficient parts are almost
      100% my bin-covering.<br>
    </p>
    <p>Almost all of this time is spent splice()ing huge arrays of
      tests.&nbsp; In fact, the vast majority of the time in my test
      (20s total!) is simply building the sequence (1..100_000_000),
      which we are using as a substitute for a similar length argument
      array of tests.<br>
    </p>
    <p>We are in luck, as once again we have an optimization suggested
      by the constraints of our execution environment.&nbsp; Given any
      host only needs to know <i>what it needs to execute</i> we can
      save only the relevant indices, and do <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy
        evaluation</a>.&nbsp; This means our sequence expansion (which
      takes the most time) has an upper bound of how long it takes<i> to
        generate up to our offset</i>.&nbsp; The change is
      straightforward:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">,{&nbsp;tests&nbsp;=&gt;&nbsp;[</span><span style="color: #9cdcfe;"> $offset</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;],&nbsp;platforms&nbsp;=&gt;&nbsp;\</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">&nbsp;});</span></div></div>
    <p>The question is, can we cheat even more by starting at our offset
      too?&nbsp; Given we are expecting a glob or regex describing a
      number of files which we don't know ahead of time what will be
      produced, this seems unlikely.&nbsp; We could probably speed it up
      globbing with <code>GLOB_NOSORT</code><code>.</code> Practically
      every other sieve trick we can try (see <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/De_Morgan%27s_laws">DeMorgan's


        Laws</a>) is already part of the C library implementing glob
      itself.&nbsp; I suspect that we will have to understand the <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Parity_problem_(sieve_theory)">parity


        problem</a> a great deal better for <i>optimal</i><i></i>
      seeking via search criteria.<br>
    </p>
    <p>Nevertheless, this gets our execution time for the cliques()
      algorithm down to 10ms, and 3s as the upper bound to generate our
      sequence isn't bad compared to how long it will take to execute
      our subset of 100 million tests.&nbsp; We'd probably slow the
      program down using a cached solution at this point, not to mention
      having to deal with the problems inherent with such.&nbsp;
      Generating all combinations as we'd have to do to build the cache
      itself takes another 3s, and there's no reason to punish most
      users just to handle truly extreme data sets.<br>
    </p>
    <p>It is possible we could optimize our check that a combination is
      valid, and get a more reasonable execution time for combine() as
      well.&nbsp; Here's our routine as a refresher:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">combination_valid</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{incompatibilities}};</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">next</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">unless</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'ARRAY'</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #dcdcaa;">defined</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;||&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">&nbsp;}&nbsp;@{</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}}&nbsp;)&nbsp;}&nbsp;</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;0&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;&gt;&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;1;</span></div><div><span style="color: #d4d4d4;">}</span></div></div>
    <p>Making the inner grep a List::Util::first instead seems obvious,
      but the added overhead made it not worth it for the small data
      set. Removing our guard on the other hand halved execution time,
      so I have removed it in production.&nbsp; Who knew ref( ) was so
      slow?&nbsp; Next, I "disengaged safety protocols" by turning off
      warnings and killing the defined check.&nbsp; This made no
      appreciable difference, so I<i> still</i> haven't yet run into a
      situation where I've needed to turn off warnings in a tight
      loop.&nbsp; Removing the unnecessary allocation of @compat and
      returning directly shaved another 200ms.&nbsp; All told, I got
      down to 800ms, which is in "detectable but barely" delay
      territory, which is good enough in my book.<br>
    </p>
    <h3>Conclusion<br>
    </h3>
    <p>The thing I take away from all this is that the most useful thing
      a mathematics education teaches is the ability to identify
      specific problems as instances of generalized problems (to which a
      great deal of thinking has already been devoted).&nbsp; While this
      is not a new lesson, I continuously astonish myself how
      unreasonably effective it is.&nbsp; That, and exposure to the wide
      variety of pursuits in mathematics gives a leg up as to where to
      start looking.<br>
    </p>
    <p>I also think the model I took developing this has real
      strength.&nbsp; Developing a program while simultaneously doing
      what amounts to a term paper on how it's to operate very clearly
      draws out the constraints and acceptance criteria from a program
      in an apriori way.&nbsp; It also makes documentation a fait
      accompli.&nbsp; Making sure to test and profile while doing this
      as well completed the (as best as is possible without users) <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Methodological_dualism">methodologically
        dual</a> design, giving me the utmost confidence that this
      program will be fit for purpose.&nbsp; Given most "technical debt"
      is caused by not fully understanding the problem when going into
      writing your program (which is so common it might shock the
      uninitiated) and making sub-optimal trade-offs when designing it,
      I think this approach mitigates most risks in that regard.<br>
    </p>
    <p>That said, it's a lot harder to think things through and then
      test your hypotheses than just charging in like a bull in a china
      shop or groping in the dark.&nbsp; This is the most common pattern
      I see in practice doing software development professionally.&nbsp;
      To be fair, it's not like people are actually willing to <i>pay</i>
      for what it takes to achieve real quality, and "good enough" often
      is.&nbsp; <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Bounded_rationality">Bounded
        rationality</a> is the rule of the day, and our lot in life is
      mostly that of a <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Satisficing">satisficer</a>.&nbsp;
      Optimal can be the enemy of good, and the tradeoffs we've made
      here certainly prove this out.<br>
    </p>
    <p>When I was doing QA for a living people are surprised when I tell
      them the most important book for testers to read is <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Administrative_Behavior">Administrative
        Behavior.</a> This is because you have to understand the
      constraints of your environment do do your job well, which is to
      provide actionable information to decision-makers.&nbsp; I'm
      beginning to realize this actually suffuses the entire development
      process from top to bottom.<br>
    </p>]]></description>
<author>george</author>
<guid isPermaLink="true">http://troglodyne.net/posts/6ff13eb0-13de-11ec-84d9-8f4e0c69f986</guid>
<pubDate>2021-04-13T18:02:22</pubDate>
<enclosure type="text/html" url="http://troglodyne.net/posts/6ff13eb0-13de-11ec-84d9-8f4e0c69f986" />
</item>
<item>
<title>Hard Problems</title>
<link>http://troglodyne.net/posts/1618336942</link>
<description><![CDATA[<p> When preparing any tool which you see all the pieces readily
      available, but that nobody has executed upon, you begin to ask
      yourself why that is. This is essentially what I've been going
      through building the <a moz-do-not-send="true"
        href="https://troglodyne.net/video/1616627599">pairwise</a>
      tool. </p>
    <p>Every time&nbsp; I look around and don't see a solution for an
      old problem on CPAN, my spider-senses start to fire.&nbsp; I saw
      no N-dimensional combination methods (only n Choose k) or bin
      covering algorithms, and when you see a lack of N-dimensional
      solutions that usually means there is a lack of closed form
      general solutions to that problem.&nbsp; While this is not true
      for my problem space, it rubs right up against the edge of NP hard
      problems.&nbsp; So it's not exactly shocking I didn't see anything
      fit to purpose.<br>
    </p>
    <p> The idea behind<a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/All-pairs_testing"> pairwise
        test execution</a> is actually quite simple, but the constraints
      of the software systems surrounding it risk making it more complex
      than is manageable. This is because unless we confine ourselves to
      a very specific set of constraints, we run into not one, but two
      NP hard problems. We could then be forced into the unfortunate
      situation where we have to use Polynomial time approximations.<br>
    </p>
    <p>I've run into this a few times in my career. Each time the team
      grows disheartened as what the customer wants seems on the surface
      to be impossible. I always remember that there is always a way to
      win by cheating (more tight constraints). Even the tyranny of the
      rocket equation was overcome through these means (let's put a
      little rocket on a big one!)<br>
    </p>
    <h3>Breaking it down<br>
    </h3>
    <p>The first problem is that N-Wise test choosing is simply a <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Combination">combination</a>.<br>
      This results in far, far more platforms to test than is practical
      once you get beyond 3 independent variables relevant to your
      system under test. For example:<br>
    </p>
    <p>A combination with 3 sets containing 3, 5 and 8 will result in 3
      * 5 * 8 = 120 systems under test! Adding in a fourth or fifth will
      quickly bring you into the territory of <i>thousands of systems </i>to



      test.&nbsp; While this is straightforward to accomplish these
      days, it is quite expensive.<br>
    </p>
    <p>What we actually want is an expression of the <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Pigeonhole_principle">pigeonhole



        principle</a>.&nbsp; We wish to build sets where <em>every
        element of each component set is seen at least once, </em>as
      this will cover everything with the minimum number of needed
      systems under test.&nbsp; This preserves the practical purpose of
      pairwise testing quite nicely.<br>
      <em></em></p>
    <p> In summary, we have a <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Clique_(graph_theory)">clique</a>
      problem and a <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Bin_covering_problem">bin
        covering</a> problem. This means that we have to build a number
      of bins from X number of sets each containing some amount of
      members. We then have to fill said bins with a bunch of tests in a
      way which will result in them being executed as fast as is
      possible. </p>
    <p> Each bin we build will represent some system under test, and
      each set from which we build these bins a particular important
      attribute. For example, consider these sets: </p>
    <ul>
      <li> <b>Operating Systems</b>: Windows, Linux, OSX</li>
      <li><b>Processor Architecture</b>: 32-bit, 64-bit</li>
      <li><b>Browser</b>: Firefox, Chrome, Safari, Brave, Opera,
        SeaMonkey </li>
    </ul>
    <p>A random selection will result in an optimal multi-dimensional
      "pairwise" set of systems under test: </p>
    <ol>
      <li> Firefox - Windows - 64 Bit</li>
      <li>Chrome - Linux - 32 Bit</li>
      <li>Safari - Windows - 32 Bit</li>
      <li>Brave - OSX - 32 Bit</li>
      <li>Opera - OSX - 64 Bit</li>
      <li>SeaMonkey - Linux - 64-Bit<br>
      </li>
    </ol>
    <p> The idea is to pick one of each of the set with the most members
      and then pick from the remaining ones at the index of the current
      pick from the big set modulo the smaller set's size. This is the
      "weak" form of the Pigeonhole Principle in action, which is why it
      is solved easily with the <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Chinese_remainder_theorem">Chinese



        remainder theorem</a>.<br>
    </p>
    <h3>Sometimes you can oversimplify</h3>
    <p> You may have noticed that perhaps we are going too far with our
      constraints here. This brings in danger, as the "strong" general
      form of the pigeonhole principle means we are treading into the
      waters of Ramsey's (clique) problem. For example, if we drop
      either of these two assumptions we can derive from our sets: </p>
    <ol>
      <li> No element of any given set is repeated</li>
      <li>No element of any given set is shared with another </li>
    </ol>
    <p> We immediately descend into the realm of the NP hard problem.
      This is because we are no longer a principal ideal domain and can
      no longer cheat using the Chinese remainder theorem. In this
      reality, we are solving the <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Independent_set_(graph_theory)">Anti-Clique</a>
      problem specifically, which is particularly nasty. Thankfully, we
      can consider those two constraints to be quite realistic.<br>
    </p>
    <p>We will have to account for the fact that the variables are
      actually not independent. You may have noticed that some of these
      "optimal" configurations are not actually realistic. Many
      Operating systems do not support various processor architectures
      and software packages. Three of the configurations above are
      currently invalid for at least one reason.&nbsp; Consider a
      configuration object like so:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;PlatformGroups&nbsp;=&gt;&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Operating&nbsp;Systems'</span><span style="color: #d4d4d4;">&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{CentOS&nbsp;Ubuntu&nbsp;Windows&nbsp;OSX}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'CPU&nbsp;Archetechure'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{32-bit&nbsp;64-bit}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Browser'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{Firefox&nbsp;Opera&nbsp;Safari&nbsp;Chrome&nbsp;Iexplore&nbsp;Brave&nbsp;Dillo&nbsp;lynx}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Mail&nbsp;Server'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{exim&nbsp;courier&nbsp;postfix&nbsp;qmail&nbsp;exchange}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'HTTP&nbsp;Server'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{ngnix&nbsp;apache&nbsp;lighttpd&nbsp;thttpd}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Database&nbsp;Server'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{postgres&nbsp;mysql&nbsp;mariadb&nbsp;mssql&nbsp;oracle}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Message&nbsp;Queue'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{rabbitmq&nbsp;zmq}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Search&nbsp;Engine'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{solr&nbsp;lunr&nbsp;elasticsearch}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;},</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;incompatibilities&nbsp;=&gt;&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Windows'</span><span style="color: #d4d4d4;">&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{32-bit&nbsp;Safari&nbsp;Dillo&nbsp;qmail&nbsp;exim&nbsp;courier&nbsp;postfix&nbsp;thttpd&nbsp;solr}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'OSX'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{32-bit&nbsp;Iexplore}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'CentOS'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{Iexplore}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #ce9178;">'Ubuntu'</span><span style="color: #d4d4d4;">&nbsp;&nbsp;=&gt;&nbsp;[</span><span style="color: #ce9178;">qw{Iexplore}</span><span style="color: #d4d4d4;">],</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;},</span></div><div><span style="color: #d4d4d4;">};</span></div></div>
    <div>Thanks&nbsp;to&nbsp;the&nbsp;requirement&nbsp;that&nbsp;all&nbsp;configurations&nbsp;be&nbsp;unique,&nbsp;we&nbsp;can&nbsp;use&nbsp;a&nbsp;simplified&nbsp;data&nbsp;structure&nbsp;here


      rather than over-complicating the PlatformGroup data structure
      (and our processor code).<br>
    </div>
    <p>Can we throw away these configurations without simply
      "re-rolling" the dice?&nbsp; Unfortunately, no.&nbsp; Not without
      using the <a moz-do-not-send="true"
        href="https://blog.codinghorror.com/the-god-login/">god
        algorithm</a> of computing every possible combination ahead of
      time, and therefore already knowing the answer.&nbsp; As such our
      final implementation looks like so:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">cliques</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$tests</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'HASH'</span><span style="color: #d4d4d4;">&nbsp;?&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}}&nbsp;:&nbsp;();<br></span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Randomize&nbsp;the&nbsp;ordering&nbsp;of&nbsp;the&nbsp;platform&nbsp;groups&nbsp;for&nbsp;eventual&nbsp;consistency.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}}&nbsp;=&nbsp;shuffle(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;The&nbsp;idea&nbsp;here&nbsp;is&nbsp;to&nbsp;have&nbsp;at&nbsp;least&nbsp;one&nbsp;pigeon&nbsp;in&nbsp;each&nbsp;hole.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;This&nbsp;is&nbsp;accomplished&nbsp;by&nbsp;finding&nbsp;the&nbsp;longest&nbsp;list&nbsp;of&nbsp;groups,&nbsp;and&nbsp;then&nbsp;iterating&nbsp;over&nbsp;everything&nbsp;we&nbsp;have&nbsp;modulo&nbsp;their&nbsp;size.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}&nbsp;</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))[0];</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$tests</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Bin&nbsp;covering</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">int</span><span style="color: #d4d4d4;">(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;/&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;0;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">for</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">=0;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;&lt;&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">++)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">&nbsp;( </span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}</span><span style="color: #dcdcaa;"> keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span><span style="color: #d4d4d4;"></span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;If&nbsp;a&nbsp;partial&nbsp;is&nbsp;invalid,&nbsp;we&nbsp;must re-roll the dice.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">while</span><span style="color: #d4d4d4;">&nbsp;(!combination_valid(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,&nbsp;,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;1)&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Allow&nbsp;for&nbsp;'incomplete'&nbsp;sets&nbsp;omitting&nbsp;a&nbsp;configuration&nbsp;group&nbsp;entirely&nbsp;due&nbsp;to&nbsp;total&nbsp;incompatibility</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">last</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;==&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div>	push(@plans, \@newplats);<br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;\</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">}<br><br></span><div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">combination_valid</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{incompatibilities}};</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">next</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">unless</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'ARRAY'</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #dcdcaa;">defined</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;||&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">&nbsp;}&nbsp;@{</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}}&nbsp;)&nbsp;}&nbsp;</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;0&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;&gt;&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;1;</span></div><div><span style="color: #d4d4d4;">}</span></div></div><span style="color: #d4d4d4;"></span></div></div>
    <p>This brings us to another unmentioned constraint: what happens if
      a member of a set is incompatible with all members of another
      set?&nbsp; It turns out accepting this is actually a <i>significant


        optimization</i>, as we will end up <i>never having to re-roll
        an entire sequence</i>.&nbsp; See the while loop above.<br>
    </p>
    <p> Another complication is the fact that we will have to randomize
      the set order to achieve the goal of eventual coverage of every
      possible combination. Given the intention of the tool is to run
      decentralized and <i>without a central oracle</i> other than git,
      we'll have to also have use a seed based upon it's current
      state.&nbsp; The algorithm above does <i>not</i> implement this,
      but it should be straightforward to add.<br>
    </p>
    <h3>Filling the bins<br>
    </h3>
    <p> We at least have a solution to the problem of building the bins.
      So, we can move on to filling them. Here we will encounter
      trade-offs which are quite severe. If we wish to accurately
      reflect reality with our assumptions, we immediately stray into
      "no closed form solution" territory. This is the <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Fair_item_allocation">Fair
        Item Allocation</a> problem, but with a significant twist.&nbsp;
      To take advantage of our available resources better, we should
      always execute<i> at least one test</i>. This will result in fewer
      iterations to run through every possible combination of systems to
      test, but also means we've cheated by adding a "double spend" on
      the low-end.&nbsp; Hooray cheating! </p>
    <p> The fastest approximation is essentially to dole out a number of
      tests equal to the floor of dividing the tests equally among the
      bins <i>plus</i> floor(&nbsp; (tests % bins)&nbsp; / tests ) in
      the case you have less tests than bins. This has an error which is
      not significant until you reach millions of tests. We then get
      eaten alive by rounding error due to flooring.<br>
    </p>
    We could simply add the remainder and give up on fair
    allocation.&nbsp; But given the remainder will always be lower than
    the number of bins, we can just shave one off of it each go-through
    until we run out (while still retaining the minimum bound of
    1).&nbsp; This is is the optimal solution:<br>
    <br>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">my $choose    = int( $total_tests / $bins );<br>my $remainder = $total_tests % bins;<br>...<br># later in our loop<br>my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$take</span><span style="color: #d4d4d4;"> = $choose</span><span style="color: #9cdcfe;"></span><span style="color: #d4d4d4;"> +&nbsp;( </span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;"> &amp;&amp;&nbsp;1&nbsp;)&nbsp;||&nbsp;1;<br>$remainder-- if $remainder;<br></span></div></div>
    <br>
    From there we simply splice out the relevant elements from the array
    of tests.&nbsp; The completed algorithm has some minor differences
    from cliques() above:<br>
    <br>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">cliques</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$tests</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'HASH'</span><span style="color: #d4d4d4;">&nbsp;?&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}}&nbsp;:&nbsp;();</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Randomize&nbsp;the&nbsp;ordering&nbsp;of&nbsp;the&nbsp;platform&nbsp;groups&nbsp;for&nbsp;eventual&nbsp;consistency.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}}&nbsp;=&nbsp;shuffle(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pg</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;The&nbsp;idea&nbsp;here&nbsp;is&nbsp;to&nbsp;have&nbsp;at&nbsp;least&nbsp;one&nbsp;pigeon&nbsp;in&nbsp;each&nbsp;hole.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;This&nbsp;is&nbsp;accomplished&nbsp;by&nbsp;finding&nbsp;the&nbsp;longest&nbsp;list&nbsp;of&nbsp;groups,&nbsp;and&nbsp;then&nbsp;iterating&nbsp;over&nbsp;everything&nbsp;we&nbsp;have&nbsp;modulo&nbsp;their&nbsp;size.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}&nbsp;</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))[0];</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$longest</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$tests</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Bin&nbsp;covering</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">int</span><span style="color: #d4d4d4;">(&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">&nbsp;/&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;0;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">for</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">=0;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;&lt;&nbsp;</span><span style="color: #9cdcfe;">$llen</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">++)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;"> </span><span style="color: #dcdcaa;"><span style="color: #d4d4d4;"></span><span style="color: #dcdcaa;">sort</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$b</span><span style="color: #d4d4d4;">}})&nbsp;&lt;=&gt;&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$a</span><span style="color: #d4d4d4;">}})&nbsp;}</span><span style="color: #dcdcaa;"> </span>keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$i</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;If&nbsp;a&nbsp;partial&nbsp;is&nbsp;invalid,&nbsp;we&nbsp;must re-roll the dice.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">while</span><span style="color: #d4d4d4;">&nbsp;(!combination_valid(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,&nbsp;,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;(</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;1)&nbsp;%&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(@{</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Allow&nbsp;for&nbsp;'incomplete'&nbsp;sets&nbsp;omitting&nbsp;a&nbsp;configuration&nbsp;group&nbsp;entirely&nbsp;due&nbsp;to&nbsp;total&nbsp;incompatibility</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">last</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">&nbsp;==&nbsp;</span><span style="color: #9cdcfe;">$orig_idx</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$pgroups</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$pgroup</span><span style="color: #d4d4d4;">}[</span><span style="color: #9cdcfe;">$idx</span><span style="color: #d4d4d4;">]);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;1&nbsp;)&nbsp;||&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">,{&nbsp;tests&nbsp;=&gt; [splice(</span><span style="color: #9cdcfe;">@$tests, $offset</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">)],&nbsp;platforms&nbsp;=&gt;&nbsp;\</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">&nbsp;});</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">--&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;+=&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">;<br><br></span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Just&nbsp;repeat&nbsp;tests&nbsp;in&nbsp;the&nbsp;event&nbsp;we&nbsp;have&nbsp;more&nbsp;SUTs available than&nbsp;tests</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$tot</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;\</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">}</span></div></div>
    <p>It is worth noting there is yet another minor optimization in our
      production process here at the end, namely that if we have more
      systems available for tests than tests to execute, we can achieve
      total coverage in less iterations by repeating tests from earlier
      groups.<br>
    </p>
    <h3>Trade-offs in my trade-offs<br>
    </h3>
    Even this makes some significant assumptions:
    <ol>
      <li> Each item we are packing into a bin is of equal size. This
        means every test is assumed to run in the same amount of time <em>on




          the same computer</em>.</li>
      <li>Each item is indivisible</li>
      <li>Each bin values each item equally (in our context this means "<em>every




          computer</em> executes it in the same amount of time")</li>
      <li>Each test will never change in how long it takes to execute
        when it changes, or the system under test does.</li>
      <li>Each <em>bin</em> represents one computer only. </li>
    </ol>
    <p> Obviously the only realistic assumption here is #2. If tests can
      be executed faster by breaking them into smaller tests, the test
      authors should do so, not an argument builder. </p>
    <p> Assumptions #1 and #3, if we take them seriously would not only
      doom us to solving an NP hard problem, but have a host of other
      practical issues. Knowing how long each test takes on each
      computer is quite a large sampling problem, though solvable <em>eventually</em>
      even using only git tags to store this data. Even then, #4 makes
      this an exercise in futility. We really have no choice but to
      accept this source of inefficiency in our production process. </p>
    <p> Invalidating #5 does not bring us too much trouble. Since we
      expect to have a number of test hosts which will satisfy any given
      configuration from the optimal group and will <em>know how many
        there are ahead of time</em>, we can simply split the bin over
      the available hosts and re-run our bin packer over those hosts. </p>
    <p> This will inevitably result in a situation where you have an
      overabundance of available systems under test for some
      configurations and a shortage of others. Given enough tests, this
      can result in workflow disruptions. This is a hard problem to
      solve without "throwing money at the problem", or being more
      judicious with what configurations you support in the first place.
      That is the sort of problem an organization wants to have though.
      It is preferable to the problem of wasting money testing
      everything on every configuration.<br>
    </p>
    <h3>Whither N-wise<br>
    </h3>
    <p>Since the name of the tool is pairwise, I may as well also
      implement and discuss multi-set combinations.&nbsp; Building these
      bins is actually quite straightforward, which is somewhat shocking
      given every algorithm featured for doing pairwise testing at
      pairwise.org was not in fact the optimal one from my 30 year old
      combinatorics textbook.&nbsp; Pretty much all of them used
      tail-call recursion in languages which do not optimize this, or
      they took (good) shortcuts which prevented them from functioning
      in N dimensions.<br>
    </p>
    <p>Essentially you build an iterator which, starting with the first
      set, pushes a partial combination with every element of its set
      matched with one of the second onto your stack.<br>
      You then repeat the process, considering the first set to be the
      partial, and crank right through all the remaining sets.<br>
    </p>
    <p>Dealing with incompatibilities is essentially the same procedure
      as above.&nbsp; The completed algorithm looks like so:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">combine</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">$tests</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'HASH'</span><span style="color: #d4d4d4;">&nbsp;?&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{PlatformGroups}}&nbsp;:&nbsp;();</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#construct&nbsp;iterator</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@pigeonholes</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">values</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%pgroups</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$bins</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;product&nbsp;</span><span style="color: #dcdcaa;">map</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$_</span><span style="color: #d4d4d4;">)&nbsp;}&nbsp;</span><span style="color: #9cdcfe;">@pigeonholes</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$tests</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Bin&nbsp;covering</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$bins</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">int</span><span style="color: #d4d4d4;">(&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">&nbsp;/&nbsp;</span><span style="color: #9cdcfe;">$bins</span><span style="color: #d4d4d4;">);</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;0;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;@{</span><span style="color: #9cdcfe;">$pigeonholes</span><span style="color: #d4d4d4;">[0]};</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">while</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">)&nbsp;)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">shift</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#Handle&nbsp;initial&nbsp;elements</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;[</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">]&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ne</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'ARRAY'</span><span style="color: #d4d4d4;">;</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#Break&nbsp;out&nbsp;of&nbsp;the&nbsp;loop&nbsp;if&nbsp;we&nbsp;have&nbsp;no&nbsp;more&nbsp;possibilities&nbsp;to&nbsp;exploit</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$subj</span><span style="color: #d4d4d4;">)&nbsp;==&nbsp;</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@pigeonholes</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$to_take</span><span style="color: #d4d4d4;">&nbsp;+&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;1&nbsp;)&nbsp;||&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">,&nbsp;{&nbsp;tests&nbsp;=&gt;&nbsp;[&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;],&nbsp;platforms&nbsp;=&gt;&nbsp;</span><span style="color: #9cdcfe;">$subj</span><span style="color: #d4d4d4;">&nbsp;}&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">--&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$remainder</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;+=&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;Just&nbsp;repeat&nbsp;tests&nbsp;in&nbsp;the&nbsp;event&nbsp;we&nbsp;have&nbsp;more&nbsp;SUTs&nbsp;than&nbsp;tests</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$offset</span><span style="color: #d4d4d4;">&nbsp;%&nbsp;</span><span style="color: #9cdcfe;">$tot_tests</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">next</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><br><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#Keep&nbsp;pushing&nbsp;partials&nbsp;on&nbsp;to&nbsp;the&nbsp;end&nbsp;of&nbsp;the&nbsp;iterator,&nbsp;until&nbsp;we&nbsp;run&nbsp;out&nbsp;of&nbsp;categories&nbsp;to&nbsp;add</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;(@{</span><span style="color: #9cdcfe;">$pigeonholes</span><span style="color: #d4d4d4;">[</span><span style="color: #dcdcaa;">scalar</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@$subj</span><span style="color: #d4d4d4;">)]})&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">@$subj</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;If&nbsp;the&nbsp;combination&nbsp;isn't&nbsp;valid,&nbsp;return&nbsp;an&nbsp;undef&nbsp;member&nbsp;to&nbsp;simplify&nbsp;loop&nbsp;breakout</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;This&nbsp;results&nbsp;in&nbsp;some&nbsp;configurations&nbsp;which&nbsp;are&nbsp;essentially&nbsp;the&nbsp;same.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;That&nbsp;said,&nbsp;we&nbsp;cannot&nbsp;simply&nbsp;discard&nbsp;them&nbsp;if&nbsp;we&nbsp;wish&nbsp;to&nbsp;cover&nbsp;the&nbsp;case&nbsp;a&nbsp;configuration&nbsp;having&nbsp;incompatibilities&nbsp;with&nbsp;entire&nbsp;configuration&nbsp;groups.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #6a9955;">#&nbsp;We&nbsp;could&nbsp;compress&nbsp;them&nbsp;later&nbsp;to&nbsp;avoid&nbsp;some&nbsp;slop,&nbsp;but&nbsp;it's&nbsp;probably&nbsp;not&nbsp;worth&nbsp;the&nbsp;effort.</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">,&nbsp;combination_valid(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">)&nbsp;?&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;:&nbsp;</span><span style="color: #dcdcaa;">undef</span><span style="color: #d4d4d4;">&nbsp;);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@iterator</span><span style="color: #d4d4d4;">,\</span><span style="color: #9cdcfe;">@partial</span><span style="color: #d4d4d4;">);</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;\</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">}</span></div></div>
    <h3>Uniting all under Heaven<br>
    </h3>
    <p>You may have noticed this is a greedy algorithm.&nbsp; If we
      decided to use this as a way to generate a cache for a "god
      algorithm" version of the anti-clique generator above, we could
      very easily run into memory exhaustion with large enough
      configuration sets, defeating the purpose. You could flush the
      partials that are actually complete, but even then you'd only be
      down to 1/n theoretical memory usage where n is the size of your
      2nd largest configuration set (supposing you sort such that it's
      encountered last).&nbsp; This may prove "good enough" in practice,
      especially since users tend to tolerate delays in the "node added
      to network" phase better than the "trying to run tests"
      phase.&nbsp; It would also speed up the matching of available
      systems under test to the desired configuration supersets, as we
      could also "already know the answer".<br>
    </p>
    <p>Profiling this showed that I either had to fix my algorithm or
      resort to this.&nbsp; My "worst case" example of 100 million tests
      using the cliques() method took 3s, while generating everything
      took 4.&nbsp; Profiling shows the inefficient parts are almost
      100% my bin-covering.<br>
    </p>
    <p>Almost all of this time is spent splice()ing huge arrays of
      tests.&nbsp; In fact, the vast majority of the time in my test
      (20s total!) is simply building the sequence (1..100_000_000),
      which we are using as a substitute for a similar length argument
      array of tests.<br>
    </p>
    <p>We are in luck, as once again we have an optimization suggested
      by the constraints of our execution environment.&nbsp; Given any
      host only needs to know <i>what it needs to execute</i> we can
      save only the relevant indices, and do <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Lazy_evaluation">lazy
        evaluation</a>.&nbsp; This means our sequence expansion (which
      takes the most time) has an upper bound of how long it takes<i> to
        generate up to our offset</i>.&nbsp; The change is
      straightforward:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #dcdcaa;">push</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">@plans</span><span style="color: #d4d4d4;">,{&nbsp;tests&nbsp;=&gt;&nbsp;[</span><span style="color: #9cdcfe;"> $offset</span><span style="color: #d4d4d4;">,&nbsp;</span><span style="color: #9cdcfe;">$tt</span><span style="color: #d4d4d4;">&nbsp;],&nbsp;platforms&nbsp;=&gt;&nbsp;\</span><span style="color: #9cdcfe;">@newplats</span><span style="color: #d4d4d4;">&nbsp;});</span></div></div>
    <p>The question is, can we cheat even more by starting at our offset
      too?&nbsp; Given we are expecting a glob or regex describing a
      number of files which we don't know ahead of time what will be
      produced, this seems unlikely.&nbsp; We could probably speed it up
      globbing with <code>GLOB_NOSORT</code><code>.</code> Practically
      every other sieve trick we can try (see <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/De_Morgan%27s_laws">DeMorgan's


        Laws</a>) is already part of the C library implementing glob
      itself.&nbsp; I suspect that we will have to understand the <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Parity_problem_(sieve_theory)">parity


        problem</a> a great deal better for <i>optimal</i><i></i>
      seeking via search criteria.<br>
    </p>
    <p>Nevertheless, this gets our execution time for the cliques()
      algorithm down to 10ms, and 3s as the upper bound to generate our
      sequence isn't bad compared to how long it will take to execute
      our subset of 100 million tests.&nbsp; We'd probably slow the
      program down using a cached solution at this point, not to mention
      having to deal with the problems inherent with such.&nbsp;
      Generating all combinations as we'd have to do to build the cache
      itself takes another 3s, and there's no reason to punish most
      users just to handle truly extreme data sets.<br>
    </p>
    <p>It is possible we could optimize our check that a combination is
      valid, and get a more reasonable execution time for combine() as
      well.&nbsp; Here's our routine as a refresher:<br>
    </p>
    <div style="color: #d4d4d4;background-color: #1e1e1e;font-family: Consolas, 'Courier New', monospace;font-weight: normal;font-size: 14px;line-height: 19px;white-space: pre;"><div><span style="color: #569cd6;">sub</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">combination_valid</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">,</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">)&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;%{</span><span style="color: #9cdcfe;">$conf</span><span style="color: #d4d4d4;">-&gt;{incompatibilities}};</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">foreach</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;(</span><span style="color: #dcdcaa;">keys</span><span style="color: #d4d4d4;">(</span><span style="color: #9cdcfe;">%compat</span><span style="color: #d4d4d4;">))&nbsp;{</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">next</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #c586c0;">unless</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">ref</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #ce9178;">'ARRAY'</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #569cd6;">my</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;=&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">;&nbsp;</span><span style="color: #dcdcaa;">defined</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;&amp;&amp;&nbsp;(&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">&nbsp;||&nbsp;</span><span style="color: #dcdcaa;">grep</span><span style="color: #d4d4d4;">&nbsp;{&nbsp;</span><span style="color: #9cdcfe;">$element</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #dcdcaa;">eq</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">$_</span><span style="color: #d4d4d4;">&nbsp;}&nbsp;@{</span><span style="color: #9cdcfe;">$compat</span><span style="color: #d4d4d4;">{</span><span style="color: #9cdcfe;">$key</span><span style="color: #d4d4d4;">}}&nbsp;)&nbsp;}&nbsp;</span><span style="color: #9cdcfe;">@combo</span><span style="color: #d4d4d4;">;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;0&nbsp;</span><span style="color: #c586c0;">if</span><span style="color: #d4d4d4;">&nbsp;</span><span style="color: #9cdcfe;">@compat</span><span style="color: #d4d4d4;">&nbsp;&gt;&nbsp;1;</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;}</span></div><div><span style="color: #d4d4d4;">&nbsp;&nbsp;&nbsp;&nbsp;</span><span style="color: #c586c0;">return</span><span style="color: #d4d4d4;">&nbsp;1;</span></div><div><span style="color: #d4d4d4;">}</span></div></div>
    <p>Making the inner grep a List::Util::first instead seems obvious,
      but the added overhead made it not worth it for the small data
      set. Removing our guard on the other hand halved execution time,
      so I have removed it in production.&nbsp; Who knew ref( ) was so
      slow?&nbsp; Next, I "disengaged safety protocols" by turning off
      warnings and killing the defined check.&nbsp; This made no
      appreciable difference, so I<i> still</i> haven't yet run into a
      situation where I've needed to turn off warnings in a tight
      loop.&nbsp; Removing the unnecessary allocation of @compat and
      returning directly shaved another 200ms.&nbsp; All told, I got
      down to 800ms, which is in "detectable but barely" delay
      territory, which is good enough in my book.<br>
    </p>
    <h3>Conclusion<br>
    </h3>
    <p>The thing I take away from all this is that the most useful thing
      a mathematics education teaches is the ability to identify
      specific problems as instances of generalized problems (to which a
      great deal of thinking has already been devoted).&nbsp; While this
      is not a new lesson, I continuously astonish myself how
      unreasonably effective it is.&nbsp; That, and exposure to the wide
      variety of pursuits in mathematics gives a leg up as to where to
      start looking.<br>
    </p>
    <p>I also think the model I took developing this has real
      strength.&nbsp; Developing a program while simultaneously doing
      what amounts to a term paper on how it's to operate very clearly
      draws out the constraints and acceptance criteria from a program
      in an apriori way.&nbsp; It also makes documentation a fait
      accompli.&nbsp; Making sure to test and profile while doing this
      as well completed the (as best as is possible without users) <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Methodological_dualism">methodologically
        dual</a> design, giving me the utmost confidence that this
      program will be fit for purpose.&nbsp; Given most "technical debt"
      is caused by not fully understanding the problem when going into
      writing your program (which is so common it might shock the
      uninitiated) and making sub-optimal trade-offs when designing it,
      I think this approach mitigates most risks in that regard.<br>
    </p>
    <p>That said, it's a lot harder to think things through and then
      test your hypotheses than just charging in like a bull in a china
      shop or groping in the dark.&nbsp; This is the most common pattern
      I see in practice doing software development professionally.&nbsp;
      To be fair, it's not like people are actually willing to <i>pay</i>
      for what it takes to achieve real quality, and "good enough" often
      is.&nbsp; <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Bounded_rationality">Bounded
        rationality</a> is the rule of the day, and our lot in life is
      mostly that of a <a moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Satisficing">satisficer</a>.&nbsp;
      Optimal can be the enemy of good, and the tradeoffs we've made
      here certainly prove this out.<br>
    </p>
    <p>When I was doing QA for a living people are surprised when I tell
      them the most important book for testers to read is <a
        moz-do-not-send="true"
        href="https://en.wikipedia.org/wiki/Administrative_Behavior">Administrative
        Behavior.</a> This is because you have to understand the
      constraints of your environment do do your job well, which is to
      provide actionable information to decision-makers.&nbsp; I'm
      beginning to realize this actually suffuses the entire development
      process from top to bottom.<br>
    </p>]]></description>
<author>george</author>
<guid isPermaLink="true">http://troglodyne.net/posts/1618336942</guid>
<pubDate>2021-04-13T18:02:22</pubDate>
<enclosure url="http://troglodyne.net/posts/1618336942" type="text/html" />
</item>
</channel>
</rss>
