Version 1 of Parrondo's Paradox

Updated 2015-01-05 09:24:14 by arjen

Arjen Markus (4 january 2015) Parrondo's Paradox, described in some detail in this Wikipedia page and clearly analysed in this article illustrates how statistics can be highly unintuitive. The paradox is shown here via two simple games:

  • In game A a single unfair coin is tossed: there is a probability of 49.5% that it is "heads". This increases your capital by 1, whereas "tails" means you lose 1. Clearly, in the long run you will have a negative capital.
  • In game B two coins are tossed. If your current capital is divisible by three, coin 1 is used, otherwise coin 2 is tossed. Again "heads" increases your capital by 1 and "tails" decreases it by the same

amount. Coin 1 gives "heads" with a 9.5% probability and coin 2 gives "heads" in 74.5% of the tosses - but it is used less. Again the expected outcome in the long run is negative.

Now we combine the two games: we throw an unbiased coin to select game A or game B, so 50% percent of the time we play game A and 50% percent of the time we play game B.

The surprising thing is, that this combined game is a winning game:

Result Parrondo

I have implemented this using the vectcl package, as playing a large number of these games in parallel is required to estimate the mean outcome. Here is the code, it is only one possibility of course. Note the way Plotchart is integrated with the calculation.

# parrondo.tcl --
#     Simulation of Parrondo's Paradox: two losing games combined give a winning game.
#
#     The simulation concerns 10000 instances of the separate games and the
#     combined game over 500 steps.
#
#     Note: not really optimised yet
#
package require vectcl
lappend auto_path c:/tcl/lib
package require Plotchart

# fillzero --
#     Return an array of integer zeros (constfill returns doubles)
#
# Arguments:
#     sz                 Size of the array
#
# Returns:
#     Array of "sz" integer zeros
#
proc fillzero {sz} {
    set array {}
    for {set i 0} {$i < $sz} {incr i} {
        lappend array 0
    }
    return $array
}

# randomNumbers --
#     Return a list of random numbers
#
# Arguments:
#     sz                 Size of the list
#
# Returns:
#     Array of "sz" random numbers
#
proc randomNumbers {sz} {
    set r {}
    for {set i 0} {$i < $sz} {incr i} {
        lappend r [expr {rand()}]
    }
    return $r
}

# gameA --
#     Determine the outcome according to game A
#
# Arguments:
#     capitalName        Current capital for all the instances (name!)
#
# Returns:
#     Nothing
#
# Side effects:
#     The new values are stored in the array "capitalName"
#
proc gameA {capitalName} {
    upvar 1 $capitalName capital

    vexpr {
        sz = shape(capital)
        random = randomNumbers(sz)
        capital = capital + (random <= 0.495) - (random > 0.495)
    }
}

# gameB --
#     Determine the outcome according to game B
#
# Arguments:
#     capitalName        Current capital for all the instances (name!)
#
# Returns:
#     Nothing
#
# Side effects:
#     The new values are stored in the array "capitalName"
#
proc gameB {capitalName} {
    upvar 1 $capitalName capital

    vexpr {
        sz = shape(capital)
        random = randomNumbers(sz)
        select = (capital%3 != 0)
        capital = capital + select     .* ((random <= 0.745) - (random > 0.745)) \
                          + (1-select) .* ((random <= 0.095) - (random > 0.095))
    }
}

# gameAB --
#     Determine the outcome according to the combination of games A and B
#
# Arguments:
#     capitalName        Current capital for all the instances (name!)
#
# Returns:
#     Nothing
#
# Side effects:
#     The new values are stored in the array "capitalName"
#
proc gameAB {capitalName} {
    upvar 1 $capitalName capital

    vexpr {
        sz = shape(capital)

        random1 = randomNumbers(sz)
        random2 = randomNumbers(sz)

        selectA = (random1 > 0.5)
        select2 = (capital%3 != 0)
        capital = capital + selectA .* ((random2 <= 0.495) - (random2 > 0.495))            \
                          + (1-selectA)                                                    \
                              .* ( select2     .* ((random2 <= 0.745) - (random2 > 0.745)) \
                                 + (1-select2) .* ((random2 <= 0.095) - (random2 > 0.095)) )
    }
}

# plot --
#     Plot the current values with an appropriate colour
#
# Arguments:
#     series           Series identifier for colours
#     x                X-value
#     y                Y-value
#
proc plot {series x y} {
    $::p plot $series $x $y
}

# main --
#     Actual simulation
#

#
# Set up the plot
#
toplevel .t
pack [canvas .t.c -width 600 -height 400]
set p [::Plotchart::createXYPlot .t.c {0 500 100} {-10 10 5}]

$p dataconfig gameA -colour blue -type both
$p dataconfig gameB -colour lime -type both
$p dataconfig gameAB -colour red -type both

# Just mark the y-axis
$p plot yaxis   0 0
$p plot yaxis 500 0
$p legendconfig -position top-left
$p legend gameA "Game A"
$p legend gameB "Game B"
$p legend gameAB "Combined game"

set capitalName "capital" ;# Workaround - vectcl does not like strings ;)

set zeros [fillzero 10000]

puts "Game A:"
set series "gameA"
vexpr {
    capital = zeros

    for i=1:500 {
        gameA(capitalName)

        if i == 1 || i % 50 == 0 {
            plot(series,i,sum(capital)/(shape(capital)+0.0))
        }
    }
}

puts [vexpr {sum(capital)/(shape(capital)+0.0)}]

puts "Game B:"
set series "gameB"
vexpr {
    capital = zeros

    for i=1:500 {
        gameB(capitalName)

        if i == 1 || i % 50 == 0 {
            plot(series,i,sum(capital)/(shape(capital)+0.0))
        }
    }
}

puts [vexpr {sum(capital)/(shape(capital)+0.0)}]

puts "Game AB:"
set series "gameAB"
vexpr {
    capital = zeros

    for i=1:500 {
        gameAB(capitalName)

        if i == 1 || i % 50 == 0 {
            plot(series,i,sum(capital)/(shape(capital)+0.0))
        }
    }
}

puts [vexpr {sum(capital)/(shape(capital)+0.0)}]