NYCJUG/2022-04-12
Beginner's Regatta
Multi-threading in J
Announcement of Multi-threading Primitives
We have this recent news from Henry Rich about new multi-threading primitives in J: T. and t..
From: Henry Rich <henryhrich@gmail.com> Date: Sat, Apr 9, 2022 at 3:04 PM Subject: [Jprogramming] J904 multithreading To: Beta forum <beta@jsoftware.com>, J-programming forum <programming@jsoftware.com>
The big news in J904 is multithreading. J verbs can now be run in different cores, sharing the J global namespaces.
[A version of J with limited support for multithreading was revealed last year by John Ference of Monument AI. John's work was an important proof of concept, but the J904 version is an independent development using pthreads instead of OpenMP. John has agreed with Jsoftware to give Jsoftware access to the code and testcases used in his work. We have not used the code.]
Two primitives support multithreading:
(0 T. ) creates a thread on a new core. A thread running a verb is called a /task/. Even if you don't plan to create tasks, you should create threads to allow JE to use them for its own purposes.
([x] u t. y) executes ([x] <@:u y) as a task to produce a boxed result. The thread that executed t. continues to run. But the result is a special kind of box called a /pyx/.
['pyx' is Greek for box, and has been brought into English on occasion when a word has been needed for a special box. The most recent such borrowing until now is for the box used by the Royal Mint to hold coins for assay.]
A pyx looks on the outside like any other box. You can pass it as an argument. But as soon as you look inside it, your thread is blocked until the task producing the pyx's value has completed. Then you see the value, and your thread continues.
Tasks share the global namespaces and thus can freely read, write, stomp on, and delete names visible to other tasks.
We encourage you to experiment with threads and tasks. We are sure that a layer of synchronizing primitives - semaphores, locks, and mutexes - will be needed, but we would like to get practical experience with tasks before we implement them. Suggestions welcome.
Henry Rich
Example of Multi-threading
One of the interesting things about this new primitive is that it speeds up J's primitive "each". Let's take a look at how it works.
One of the first things we'll want to know is how many cores we have available. Fortunately, for Windows users, Raul Miller provided this little function to do that:
wincpucores=: {{ assert. 0={.;'kernel32 GetNativeSystemInfo n i' 15!:0 ,<mem=: mema 44 (memf mem)]_2 ic _4{.memr mem,32 4 }}
When I run this on my desktop machine, I get this.
wincpucores '' 20
This agrees with what I know about the machine.
We write a function that is CPU-bound and will take arbitrarily long to complete depending on its argument.
fnc=: 3 : '+/+/%.1e9-~(y,y)?@$2e9 [ randomizeSeed '''''"0
We use the time-honored matrix inversion benchmark that goes back to the early days of APL. The size of the square matrix being inverted is determined by the magnitude of its argument, so we see how increasingly large values take increasingly longer to complete.
6!:2 'fnc 1000' 0.1153 6!:2 'fnc 2000' 0.759236 6!:2 'fnc 4000' 5.45686
We see that each doubling of the argument increases the time by roughly a factor of seven.
2%/\5.45686 0.759236 0.1153 7.1873 6.58487
The function calls something to randomize the seed of the random number generator in an attempt to get different matrixes each time but, because threads run in a global namespace, this does not work as desired as we will see.
NB.* randomizeSeed: randomize the random number seed so multiple NB. simultaneous instances of a function yield different random numbers. randomizeSeed=: 3 : '9!:1](|-/7!:1'''')+<.13#.|.(6!:1''''),(6!:4''''),6!:0'''']y'
We start by creating some threads like this:
{{0 T. ''}}^:12]'' 12 1 T. '' NB. How many threads have we allocated? 12 2 T. '' NB. Thread numbers 12 11 10 9 8 7 6 5 4 3 2 1
So, with 12 threads, we should be able to run 10 versions of our function. Let's do this and get some timings.
6!:2 'pyx0=. fnc t. ''''"0]10$4000' [ smoutput qts'' 2022 4 12 18 8 52.366 8.94e_5 pyx0 qts''
The initial invocation seems nearly instantaneous according to 6!:2 but this is misleading as the operations have not finished when control is returned to us. We can tell the threads have not finished because our session blocks when we try to look at the results in pyx0. One way we can see this is that the "qts''" following this is flush against the left margin.
After a short wait, the results of pyx0 appear.
+-----------+-----------+-----------+-----------+-----------+-----------... |_2.21026e_7|_2.21026e_7|_2.21026e_7|_2.21026e_7|_2.21026e_7|_2.21026e_7... +-----------+-----------+-----------+-----------+-----------+-----------... 2022 4 12 18 9 2.986 load 'dt' 2022 4 12 18 9 2.986 TSDiff 2022 4 12 18 8 52.366 +-----+---------------+ |10.62|0 0 0 0 0 10.62| +-----+---------------+
Once all our threads complete, pyx0 becomes available, so we are able to estimate the total time by taking the difference of the two so the elapsed time for 10 invocations is only twice as long (10.62 seconds) as a single invocation.
We see that all the values are the same because the random seeds were the same for all invocations. We will need to figure out a better way of randomizing simultaneously-generated threads. At the NYCJUG meeting, Lou suggested that if each thread knows its own number, this could help generate distinct random seeds. In fact 3 T. '' returns a thread's own number; doing this in the originating session returns zero as this is the implicit number of the base session.
If we check the available threads again, we see that they are now ordered differently, presumably reflecting their use and return to the pool of threads.
2. T. '' 9 8 7 12 11 5 4 10 3 6 2 1
Let's try this again but use the elapsed session time given by 6!:1 so we don't have to rely on having a timestamp difference routine.
6!:2 'pyx0=. fnc t. ''''"0]10$4000' [ tm=. 6!:1 '' 9.07e_5 pyx0 tm=. tm,~6!:1 '' C-c C-c C-c C-c C-c C-\^CTerminate batch job (Y/N)? tm=. tm,~6!:1 '' Terminate batch job (Y/N)? Terminate batch job (Y/N)? N
Oh no! Maybe this new primitive is not quite ready for prime-time. I had to break out of the session after a couple of minutes had elapsed because my last invocation was hanging. However, this is why these new features are first released in a beta version: to let the foolhardy early adopters shake out some of the bugs.
Show and Tell
We will hear from Danyel about his work on some Project Euler problem solutions in J.
Project Euler
At our next meeting, Danyel will talk about some of his Project Euler solutions - found here - in J. We hope to get some insight in the mindset of someone in the early stages of learning the language.
Factor Analysis of Omaha Hole-cards
We continue the never-ending saga of analysis of the poker game Omaha by trying to build a factor equation which will allow us to rank a set of four hole-cards in terms of how likely it is to be part of a winning hand.
We have these test cases to test the following Boolean functions which allow us to classify hole-cards by relevant factors. We show them arranged four by four to better use the page space.
<"2 showCards &>4 4$TCs +---------------+-------------+-------------+--------------+ |+---+---+--+--+|+--+--+--+--+|+--+--+--+--+|+--+--+---+--+| ||9♦ |9♣ |5♦|4♣|||9♣|8♣|5♠|3♠|||A♣|8♦|5♦|2♦|||A♣|K♦|10♥|7♠|| |+---+---+--+--+|+--+--+--+--+|+--+--+--+--+|+--+--+---+--+| ||9♦ |9♣ |9♥|5♦|||9♣|8♣|4♠|3♠|||K♠|6♠|5♠|4♠|||A♣|K♥|Q♦ |7♥|| |+---+---+--+--+|+--+--+--+--+|+--+--+--+--+|+--+--+---+--+| ||9♦ |9♠ |9♣|9♥|||7♠|6♥|5♥|4♥|||A♣|8♦|3♦|2♦|||A♦|K♠|Q♠ |J♥|| |+---+---+--+--+|+--+--+--+--+|+--+--+--+--+|+--+--+---+--+| ||10♦|10♥|8♦|8♠|||5♦|5♣|7♦|6♣|||A♣|4♠|3♥|2♦|||2♣|2♦|A♦ |3♣|| |+---+---+--+--+|+--+--+--+--+|+--+--+--+--+|+--+--+---+--+| +---------------+-------------+-------------+--------------+
Using these utility functions
hc3a=: 1 = 1 +/ .= 1 1 E."1 [: (2 -/\ \:~) ~. hc4a=: 2=1+/ .=1 1 (E."1) 2-/\ \:~ HCnHC=: 8 +/ . < 1 { ] NB. # values that card "10" is less than
We have these factors:
HCis1Pair=: 3 : '1=2+/ . =#/.~1{y' HCis2Pair=: 3 : '2=2+/ . =#/.~1{y' HCis3ok=: 3 : '1=3+/ . =#/.~1{y' HCis4ok=: 3 : '1=4+/ . =#/.~1{y' HC1SuitPair=: 3 : '1=2+/ . =#/.~0{y' HC2SuitPairs=: 3 : '2=2+/ . =#/.~0{y' HC3Suited=: 3 : '1=3+/ . =#/.~0{y' HC4Suited=: 3 : '1=4+/ . =#/.~0{y' HC1AdjacentPair=: 3 : '((1=1+/ . =2-/\\:~1{y)+.1=1+/ . =2-/\\:~1{lowerAce y)*.(-.HC3Adjacent y)*.-.HC2AdjacentPairs y' HC2AdjacentPairs=: 3 : '(([:*./(1=0 _1{]),1~:1{]) 2-/\\:~1{y)+.([:*./(1=0 _1{]),1~:1{])2-/\\:~1{lowerAce y' HC3Adjacent=: 3 : '((hc3a 1{y)+.hc3a 1{lowerAce y)*.-.HC4Adjacent y' HC4Adjacent=: 3 : '(hc4a 1{y)+.hc4a 1{lowerAce y' HC1HighCard=: 3 : '1=HCnHC y' HC2HighCards=: 3 : '2=HCnHC y' HC3HighCards=: 3 : '3=HCnHC y' HC4HighCards=: 3 : '4=HCnHC y'
Preliminary Testing of Factors
We run our factor tests on the above test cases like this:
TCs=: hc0;hc1;hc2;hc3;hc4;hc5;hc6;hc7;hc8;hc9;hc10;hc11;hc12;hc13;hc14;hc15 showCards&>TCs#~(HCis1Pair@:suitRank)&>TCs[smoutput 'HCis1Pair:' showCards&>TCs#~(HCis2Pair@:suitRank)&>TCs[smoutput 'HCis2Pair:' showCards&>TCs#~(HCis3ok@:suitRank)&>TCs[smoutput 'HCis3ok:' showCards&>TCs#~(HCis4ok@:suitRank)&>TCs[smoutput 'HCis4ok:' showCards&>TCs#~(HC1SuitPair@:suitRank)&>TCs[smoutput 'HC1SuitPair:' showCards&>TCs#~(HC2SuitPairs@:suitRank)&>TCs[smoutput 'HC2SuitPairs:' showCards&>TCs#~(HC3Suited@:suitRank)&>TCs[smoutput 'HC3Suited:' showCards&>TCs#~(HC4Suited@:suitRank)&>TCs[smoutput 'HC4Suited:' showCards&>TCs#~(HC1AdjacentPair@:suitRank)&>TCs[smoutput 'HC1AdjacentPair:' showCards&>TCs#~(HC2AdjacentPairs@:suitRank)&>TCs[smoutput 'HC2AdjacentPairs:' showCards&>TCs#~(HC3Adjacent@:suitRank)&>TCs[smoutput 'HC3Adjacent:' showCards&>TCs#~(HC4Adjacent@:suitRank)&>TCs[smoutput 'HC4Adjacent:' showCards&>TCs#~(HC1HighCard@:suitRank)&>TCs[smoutput 'HC1HighCard:' showCards&>TCs#~(HC2HighCards@:suitRank)&>TCs[smoutput 'HC2HighCards:' showCards&>TCs#~(HC3HighCards@:suitRank)&>TCs[smoutput 'HC3HighCards:' showCards&>TCs#~(HC4HighCards@:suitRank)&>TCs[smoutput 'HC4HighCards:'
Below are the results, commented here.
HCis1Pair: +--+--+--+--+ |9♦|9♣|5♦|4♣| NB. pair of 9s +--+--+--+--+ |5♦|5♣|7♦|6♣| NB. pair of 5s +--+--+--+--+ |2♣|2♦|A♦|3♣| NB. pair of 2s +--+--+--+--+ HCis2Pair: +---+---+--+--+ |10♦|10♥|8♦|8♠| NB. 2-pair +---+---+--+--+ HCis3ok: +--+--+--+--+ |9♦|9♣|9♥|5♦| NB. three 9s +--+--+--+--+ HCis4ok: +--+--+--+--+ |9♦|9♠|9♣|9♥| NB. four 9s +--+--+--+--+ HC1SuitPair: +---+---+--+--+ |9♦ |9♣ |9♥|5♦| NB. pair of diamonds +---+---+--+--+ |10♦|10♥|8♦|8♠| NB. pair of diamonds +---+---+--+--+ |A♣ |K♥ |Q♦|7♥| NB. pair of hearts +---+---+--+--+ |A♦ |K♠ |Q♠|J♥| NB. pair of spades +---+---+--+--+ HC2SuitPairs: +--+--+--+--+ |9♦|9♣|5♦|4♣| NB. two diamonds, two clubs +--+--+--+--+ |9♣|8♣|5♠|3♠| NB. two clubs, two spades +--+--+--+--+ |9♣|8♣|4♠|3♠| NB. two clubs, two spades +--+--+--+--+ |5♦|5♣|7♦|6♣| NB. two diamonds, two clubs +--+--+--+--+ |2♣|2♦|A♦|3♣| NB. two diamonds, two clubs +--+--+--+--+ HC3Suited: +--+--+--+--+ |7♠|6♥|5♥|4♥| NB. three hearts +--+--+--+--+ |A♣|8♦|5♦|2♦| NB. three diamonds +--+--+--+--+ |A♣|8♦|3♦|2♦| NB. three diamonds +--+--+--+--+ HC4Suited: +--+--+--+--+ |K♠|6♠|5♠|4♠| NB. four spades +--+--+--+--+ HC1AdjacentPair: +--+--+---+--+ |9♦|9♣|5♦ |4♣| NB. adjacent 4,5 +--+--+---+--+ |9♣|8♣|5♠ |3♠| NB. adjacent 8,9 +--+--+---+--+ |A♣|8♦|5♦ |2♦| NB. adjacent ace, 2 +--+--+---+--+ |A♣|K♦|10♥|7♠| NB. adjacent king, ace +--+--+---+--+ HC2AdjacentPairs: +--+--+--+--+ |9♣|8♣|4♠|3♠| NB. adjacent 8,9 and 3,4 +--+--+--+--+ HC3Adjacent: +--+--+--+--+ |5♦|5♣|7♦|6♣| NB. 5, 6, 7 +--+--+--+--+ |K♠|6♠|5♠|4♠| NB. 4, 5, 6 +--+--+--+--+ |A♣|8♦|3♦|2♦| NB. ace, 2, 3 +--+--+--+--+ |2♣|2♦|A♦|3♣| NB. ace, 2, 3 +--+--+--+--+ HC4Adjacent: +--+--+--+--+ |7♠|6♥|5♥|4♥| NB. 4, 5, 6, 7 +--+--+--+--+ |A♣|4♠|3♥|2♦| NB. ace, 2, 3, 4 +--+--+--+--+ |A♦|K♠|Q♠|J♥| NB. jack, queen, king, ace +--+--+--+--+ HC1HighCard: +--+--+--+--+ |A♣|8♦|5♦|2♦| NB. one ace +--+--+--+--+ |K♠|6♠|5♠|4♠| NB. one king +--+--+--+--+ |A♣|8♦|3♦|2♦| NB. one ace +--+--+--+--+ |A♣|4♠|3♥|2♦| NB. one ace +--+--+--+--+ |2♣|2♦|A♦|3♣| NB. one ace +--+--+--+--+ HC2HighCards: +--+--+---+--+ |A♣|K♦|10♥|7♠| NB. ace, king +--+--+---+--+ HC3HighCards: +--+--+--+--+ |A♣|K♥|Q♦|7♥| NB. ace, king, queen +--+--+--+--+ HC4HighCards: +--+--+--+--+ |A♦|K♠|Q♠|J♥| NB. ace, king, queen, jack +--+--+--+--+
Applying Factors to the Complete Set
We apply these factors to the list of all the hole cards allHCcombos.
$allHCcombos 270725 4 a. i. 0 135362 270724{allHCcombos NB. allHCcombos is character array 0 1 2 3 8 9 20 44 48 49 50 51 showCards a. i. 0 135362 270724{allHCcombos NB. A few random selections +--+---+--+--+ |5♣|4♣ |3♣|2♣| +--+---+--+--+ |J♣|10♣|9♦|7♠| +--+---+--+--+ |A♠|K♠ |Q♠|J♠| +--+---+--+--+
We set up a train of factors and their corresponding names:
buildBoolFactor=: HCis1Pair,HCis2Pair,HCis3ok,HCis4ok,HC1SuitPair,HC2SuitPairs,HC3Suited,HC4Suited,HC1AdjacentPair,HC2AdjacentPairs,HC3Adjacent,HC4Adjacent,HC1HighCard,HC2HighCards,HC3HighCards,HC4HighCards BFnames=: 'HCis1Pair';'HCis2Pair';'HCis3ok';'HCis4ok';'HC1SuitPair';'HC2SuitPairs';'HC3Suited';'HC4Suited';'HC1AdjacentPair' BFnames=: BFnames,'HC2AdjacentPairs';'HC3Adjacent';'HC4Adjacent';'HC1HighCard';'HC2HighCards';'HC3HighCards';'HC4HighCards'
We apply the train of factors to all the hole-card combinations to get a Boolean for each one.
6!:2 'allHCF=. (buildBoolFactor@:suitRank)"1 a. i. allHCcombos' 9.59889 $allHCF NB. Boolean showing which of the 16 factors apply to each set of hole cards. 270725 16 $~.allHCF NB. Number of unique factor sets 213 16 3{.allHCF 0 0 0 0 0 0 0 1 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 1 0 0 0 0 0 BFNames,:<"0 +/allHCF NB. Number of instances per factor +---------+---------+-------+-------+-----------+------------+---------+---------+---------------+----------------+-----------+-----------+-----------+------------+------------+------------+ |HCis1Pair|HCis2Pair|HCis3ok|HCis4ok|HC1SuitPair|HC2SuitPairs|HC3Suited|HC4Suited|HC1AdjacentPair|HC2AdjacentPairs|HC3Adjacent|HC4Adjacent|HC1HighCard|HC2HighCards|HC3HighCards|HC4HighCards| +---------+---------+-------+-------+-----------+------------+---------+---------+---------------+----------------+-----------+-----------+-----------+------------+------------+------------+ |82368 |2808 |2496 |13 |158184 |36504 |44616 |2860 |130100 |14624 |28544 |2816 |114240 |75600 |20160 |1820 | +---------+---------+-------+-------+-----------+------------+---------+---------+---------------+----------------+-----------+-----------+-----------+------------+------------+------------+
Displaying the Factors
We define a verb to aggregate the mean of the win ratios for each factor set, giving us the weights and a version showing the names in ascending order from least helpful to most helpful factor set.
NB.* bestFactorSets: build average wins ratio per factor set. bestFactorSets=: 3 : 0 if. -. nameExists 'allHCF' do. allHCF=. (buildBoolFactor@:suitRank)"1 a. i. allHCcombos end. uhcf=. <"1 ~.allHCF bfwts=. allHCF (+/%#)/. (y-2){hcWinRatios wts=. (6{.&.>(":&.>0.0001 roundNums bfwts),&.><6$'0'),&.><': ' factorPerWt=. ;((wts,&.>e2l&.>uhcf#&.><BFnames)/:bfwts),&.>CR bfwts;factorPerWt NB. Numeric weights, "print" version listing factor names/avg wt. NB.EG 'wts prtout'=. bestFactorSets 5 NB. Best factors for 5 players )
We can use this, for instance on the mean aggregated scores for the six-player case:
'bfwts6 wtpfs6'=. bestFactorSets 6 $bfwts6 213 wtpfs6 fwrite 'BoolClassScoresNamed6.txt' NB. Mean win ratio per factor set 9658
The resulting file shows the six-player mean win ratio for each unique set of factors. It starts like this:
0.0050: HCis4ok 0.0329: HCis4ok, HC4HighCards 0.0582: HCis3ok, HC1HighCard 0.0632: HCis3ok 0.0861: HCis3ok, HC1AdjacentPair 0.0868: HCis3ok, HC1SuitPair 0.0889: HCis3ok, HC1AdjacentPair, HC1HighCard 0.0969: HCis3ok, HC3HighCards 0.0979: HCis3ok, HC1SuitPair, HC1HighCard 0.1079: HCis3ok, HC1SuitPair, HC1AdjacentPair 0.1184: HCis3ok, HC4HighCards 0.1227: 0.1235: HCis3ok, HC1AdjacentPair, HC3HighCards 0.1266: HCis3ok, HC1AdjacentPair, HC4HighCards 0.1280: HCis3ok, HC1SuitPair, HC1AdjacentPair, HC1HighCard 0.1289: HC1AdjacentPair 0.1304: HC1HighCard ...
Here we see the four-of-a-kind is the worst set we can have for hole-cards, followed by three-of-a-kind by itself and when combined with some of the other factors. This jibes with our previous analysis.
Notice that the 12th entry shows a mean score of 0.1227 for no factor. If we look at a few of the hole-card sets with no factor, we see that they do look weak:
$I. allHCF *./ . = 0 360 360%24 NB. Since these are all rainbows (one of each suit), each basic hand has 24 variants. 15 showCards a. i. allHCcombos{~3{.I. allHCF *./ . = 0 +---+--+--+--+ |8♠ |6♥|4♦|2♣| +---+--+--+--+ |9♠ |6♥|4♦|2♣| +---+--+--+--+ |10♠|6♥|4♦|2♣| +---+--+--+--+
The bottom of the file, where the best scoring sets are, looks like this:
... 0.2737: HCis1Pair, HC2SuitPairs, HC1AdjacentPair, HC3HighCards 0.2770: HCis2Pair, HC2SuitPairs, HC2HighCards 0.2786: HC2SuitPairs, HC4Adjacent, HC2HighCards 0.2788: HCis1Pair, HC2SuitPairs, HC3Adjacent, HC2HighCards 0.2862: HCis1Pair, HC2SuitPairs, HC2AdjacentPairs, HC3Adjacent, HC3HighCards 0.2888: HC2SuitPairs, HC4Adjacent, HC3HighCards 0.2921: HC2SuitPairs, HC4Adjacent, HC4HighCards 0.2929: HCis1Pair, HC2SuitPairs, HC3Adjacent, HC3HighCards 0.2957: HCis2Pair, HC1SuitPair, HC4HighCards 0.2968: HCis2Pair, HC1SuitPair, HC1AdjacentPair, HC4HighCards 0.2994: HCis1Pair, HC2SuitPairs, HC2AdjacentPairs, HC3Adjacent, HC4HighCards 0.3015: HCis1Pair, HC2SuitPairs, HC1AdjacentPair, HC4HighCards 0.3019: HCis1Pair, HC2SuitPairs, HC3Adjacent, HC4HighCards 0.3052: HCis2Pair, HC2SuitPairs, HC1AdjacentPair, HC2HighCards 0.3329: HCis2Pair, HC2SuitPairs, HC1AdjacentPair, HC4HighCards 0.3395: HCis2Pair, HC2SuitPairs, HC4HighCards
So the very best set of factors means our best set of hole-cards is 2-pair, all high cards (>10), and the pairs are both suited. This lines up with our earlier result showing that A-K-A-K suited is the best possible set of hole-cards.
Weighting the Factors
The above file is somewhat useful but is 213 lines long making it hard to see the value of each factor individually.
How can we assess how much each factor adds value to our hole-cards? We would like to do something like a regression but can we do that with a table of Boolean values like allHCF? There are a number of articles about doing "Boolean regression" and "Boolean factor analysis" so it looked like we might have to tackle a new method like one of these. However, since laziness is one of the great virtues of a programmer, we first tried to do the simplest thing we could think of: an ordinary regression.
$coeffs=. allHCF %.~ |:hcWinRatios 16 10
Here are the values of these coefficients organized by factors by number of players.
((<'# players:'),BFNames),.(<"0 ] 2+i.10),<"0 coeffs +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |# players: |2 |3 |4 |5 |6 |7 |8 |9 |10 |11 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HCis1Pair |0.135163 |0.0836213|0.0606298|0.0482101 |0.0402154 |0.0342626 |0.0296112 |0.0257775 |0.0225963 |0.0199165 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HCis2Pair |0.245352 |0.171155 |0.133505 |0.110735 |0.095186 |0.082987 |0.0733681 |0.0655401 |0.0588775 |0.0531432 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HCis3ok |0.120961 |0.0315883|0.0039881|_0.0058998 |_0.010281 |_0.0127698|_0.0144099|_0.0156188|_0.01657 |_0.0172968| +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HCis4ok |0.239186 |0.0664313|0.0144537|_0.00417984|_0.0137557|_0.0168288|_0.0197345|_0.0211681|_0.0224918|_0.0236166| +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC1SuitPair |0.240506 |0.165501 |0.128685 |0.105626 |0.08922 |0.0769291 |0.0672209 |0.0593969 |0.0529667 |0.0475934 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC2SuitPairs |0.275903 |0.204493 |0.166259 |0.140503 |0.121197 |0.105938 |0.0936455 |0.0833523 |0.0747506 |0.0674419 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC3Suited |0.237565 |0.159905 |0.123156 |0.100994 |0.0858351 |0.0746573 |0.065984 |0.0590986 |0.0534486 |0.0486974 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC4Suited |0.237948 |0.155046 |0.117175 |0.0955585 |0.0806437 |0.0702189 |0.0624104 |0.0561227 |0.0511567 |0.0469464 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC1AdjacentPair |0.0920268|0.0608765|0.0463462|0.0375096 |0.031459 |0.0269191 |0.0234734 |0.0208003 |0.0186525 |0.0169343 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC2AdjacentPairs|0.120648 |0.0816641|0.0630898|0.0515054 |0.0434429 |0.0374385 |0.0328712 |0.0292011 |0.0263647 |0.0240861 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC3Adjacent |0.121459 |0.0860625|0.0690697|0.0580471 |0.0501769 |0.0440617 |0.039306 |0.035563 |0.0325202 |0.0300093 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC4Adjacent |0.178151 |0.133119 |0.110532 |0.0955906 |0.0843867 |0.0755782 |0.0682561 |0.0624107 |0.0577128 |0.0539571 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC1HighCard |0.177644 |0.112994 |0.0810836|0.0632435 |0.0524951 |0.0455796 |0.0408661 |0.037459 |0.0348876 |0.032874 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC2HighCards |0.225272 |0.156518 |0.116404 |0.0925246 |0.0779128 |0.068769 |0.0628516 |0.0589217 |0.056251 |0.0544107 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC3HighCards |0.224548 |0.168481 |0.12913 |0.104809 |0.0900819 |0.0812728 |0.0757771 |0.0725376 |0.0706692 |0.0696119 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+ |HC4HighCards |0.178483 |0.148655 |0.118878 |0.0996087 |0.0888061 |0.0825919 |0.0795571 |0.0781552 |0.0778931 |0.0781504 | +----------------+---------+---------+---------+-----------+----------+----------+----------+----------+----------+----------+
These numbers agree with what we've already figured out. Notice how there are negative coefficients associated with many of the four-of-a-kind and three-of-a-kind sets. Interestingly, however, these coefficients are not negative for the smaller groups of players, four or fewer. In fact, as we can see below, this normally bad set of hole-cards is one of the better factors in a two-player game, in a multi-way tie for second place when we round the factors to small integers.
Making Factors More Useful
The table above is too complex to be useful in a real-life situation. How might this information be better organized?
One idea is to turn the coefficients into small integers and to order the factors from lowest to highest for each number of players. Here is one attempt to do this:
ascendingFactors=. BFnames{~"1/:"1|:coeffs NB. # players x factors from worst to best normedCoeffs=. <.0.5+100*(]%+/)"1 (|:coeffs){~"1/:"1|:coeffs NB. # players x Factor names and coefficients mat=. (],.~[:<"0[:]2+[:i.#) (2}.&.>ascendingFactors),:&.>":&.>normedCoeffs
This gives us a table with the information arranged more usefully. The first column gives the number of players and subsequent columns are arranged in ascending order with each factor name above a small integer giving the relative strength of the factor. These strengths are designed to add up to 100.
We see that two sets of suited cards is the best factor for two to nine players, with strengths ranging from nine to twelve, and is the second and third best factor for the 10 and 11 player games.
mat +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |2 |1AdjacentPair|2AdjacentPairs|is3ok |3Adjacent |is1Pair |1HighCard|4Adjacent|4HighCards|3HighCards|2HighCards|3Suited |4Suited |is4ok |1SuitPair |is2Pair |2SuitPairs| | |3 |4 |4 |4 |4 |6 |6 |6 |7 |7 |8 |8 |8 |8 |8 |9 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |3 |is3ok |1AdjacentPair |is4ok |2AdjacentPairs|is1Pair |3Adjacent|1HighCard|4Adjacent |4HighCards|4Suited |2HighCards|3Suited |1SuitPair |3HighCards|is2Pair |2SuitPairs| | |2 |3 |3 |4 |4 |4 |6 |7 |7 |8 |8 |8 |8 |8 |9 |10 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |4 |is3ok |is4ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|4Adjacent |2HighCards|4Suited |4HighCards|3Suited |1SuitPair |3HighCards|is2Pair |2SuitPairs| | |0 |1 |3 |4 |4 |5 |5 |7 |8 |8 |8 |8 |9 |9 |9 |11 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |5 |is3ok |is4ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|2HighCards|4Suited |4Adjacent |4HighCards|3Suited |3HighCards|1SuitPair |is2Pair |2SuitPairs| | |0 |0 |3 |4 |4 |5 |5 |8 |8 |8 |8 |8 |9 |9 |9 |12 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |6 |is4ok |is3ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|2HighCards|4Suited |4Adjacent |3Suited |4HighCards|1SuitPair |3HighCards|is2Pair |2SuitPairs| | |_1 |_1 |3 |4 |4 |5 |5 |8 |8 |8 |9 |9 |9 |9 |9 |12 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |7 |is4ok |is3ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|2HighCards|4Suited |3Suited |4Adjacent |1SuitPair |3HighCards|4HighCards|is2Pair |2SuitPairs| | |_2 |_1 |3 |4 |4 |5 |5 |8 |8 |9 |9 |9 |9 |9 |9 |12 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |8 |is4ok |is3ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|4Suited |2HighCards|3Suited |1SuitPair |4Adjacent |is2Pair |3HighCards|4HighCards|2SuitPairs| | |_3 |_2 |3 |4 |4 |5 |5 |8 |8 |8 |9 |9 |9 |10 |10 |12 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |9 |is4ok |is3ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|4Suited |2HighCards|3Suited |1SuitPair |4Adjacent |is2Pair |3HighCards|4HighCards|2SuitPairs| | |_3 |_2 |3 |4 |4 |5 |5 |8 |8 |8 |8 |9 |9 |10 |11 |12 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |10|is4ok |is3ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|4Suited |1SuitPair |3Suited |2HighCards|4Adjacent |is2Pair |3HighCards|2SuitPairs|4HighCards| | |_3 |_3 |3 |3 |4 |5 |5 |8 |8 |8 |9 |9 |9 |11 |12 |12 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+ |11|is4ok |is3ok |1AdjacentPair|is1Pair |2AdjacentPairs|3Adjacent|1HighCard|4Suited |1SuitPair |3Suited |is2Pair |4Adjacent |2HighCards|2SuitPairs|3HighCards|4HighCards| | |_4 |_3 |3 |3 |4 |5 |5 |8 |8 |8 |9 |9 |9 |11 |12 |13 | +--+-------------+--------------+-------------+--------------+--------------+---------+---------+----------+----------+----------+----------+----------+----------+----------+----------+----------+
This new organization illuminates some other interesting features about these factors. One of the most interesting is that the worst sets of hole cards in general - three of a kind and four of a kind - are not so bad for games with few players. We see that, in a two-player game, four of a kind is fairly highly rated, in a five-way tie for second best - with a score of 8 - in our new table with coefficients rounded to integral values.
Here is a graphical rendition of the above table of numbers:
Dark blue represents the lowest values and magenta represents the highest with the "cool" colors (blue, cyan, green, chartreuse) lower than the "hot" ones (yellow, orange, red, magenta).