NEWS

Palamedes 1.0-a63 (2016-09-23)

Added functions to do numeric base conversions:

Examples:

    input <- "01001001 00101100 00100000 01100110 01101111 01110010 00100000 01101111 01101110 01100101 00101100 00100000 01110111 01100101 01101100 01100011 01101111 01101101 01100101 00100000 01101111 01110101 01110010 00100000 01101110 01100101 01110111 00100000 01110010 01101111 01100010 01101111 01110100 00100000 01101111 01110110 01100101 01110010 01101100 01101111 01110010 01100100 01110011 00100001 00001010"

    robot2english <- glue(dec2asc(bin2dec(split)))

    robot2english(input)

Fixed bug in ApplyAST/apply when argument is an object but not an array. Allows the following definition of robot2english work:

    robot2english <- _ |> split |> bin2dec |> dec2asc |> glue
    robot2english(input)

Fixed apply function to work more consistently with the apply infix operator @@.

Changed the default separators in

Added more HTML functions:

Examples:

    grid(caption("Superheroes"), heading["Name","City","Alter-Ego"], row ["Clark Kent","Metropolis","Superman"], row ["Bruce Wayne","Gotham","Batman"])
    span ["H", sub "2", "0"]
    paragraph ["E = m c", sup "2"]
    color("red", "DOES NOT COMPUTE!")
    color("green", "Money!")
    color("blue", "Sky")
    span [strike("Delete Me"), " ", u("and add me instead!")]

Added translate function for character-by-character translation.

    translate("abcdefg","abc","xyz")
    translate("abcdefg",{"a": "x", "b": "y", "c":"z"})
    translate("abcdefg",["a", "b", "c"], ["x", "y", "z"])

    /* Replace a set of chars with 1 char */
    translate("abcdefg","abc","z")
    translate("abcdefg",["a", "b", "c"], ["z"])

    /* Using translate to delete a character */
    translate("abcdefg","abc","")
    translate("abcdefg",["a", "b", "c"], [])

Added padleft/padright(string, width, padding_character) for formatting strings.

Added array functions. Note: these are non-destructive:

Examples:

    shift [2,3,5]
    unshift([2,3,5], 11)
    unshift([2,3,5],[11,13])

Added back print function

Added debug function to show info about internal representation of objects.

Palamedes 1.0-a62 (2016-09-21)

Only changed the editor to process query string arguments:

Examples:

This URL will load and run The Most Powerful Dice script file with the code window hidden:

editor.html?script=MostPowerfulDice.pal&hide=1

This URL simply rolls a d20. It turns on echoing so that you can see the output without using any HTML functions:

editor.html?cmd=d20&echo=1

This URL also rolls a d20, leaves echoing off, but wraps the output in HTML with the text function:

editor.html?cmd=text(d20)

Palamedes 1.0-a61 (2016-09-17)

Changed the Unicode for set to the wave arrow and the ascii to <~. Mnemonic: squiggle means set. Removed all the other Unicode and ascii set symbols, because they were eyesores. Sorry for changing this so much. This is the very last time. Promise.

Added ascii symbol ++ for concat.

Added new interface to edit and run lengthy scripts.

Added ability to output html:

LU_Surprise <- lookup [d6, 1 => "Surprised 1 round", 2 => "Surprised 2 rounds", 3--6 => "No surprise"]

chargen <- ["str", "int", "wis", "con", "dex", "cha", "gold"] -> (6#3d6)++[10*3d6]

html [ ...
    h1("Big Heading"), ...
    text("Here is an ordered list: "), ...
    ol("item "+[1..6]), ...
    hr(), ...
    text("The sum of squares 1 to 10 is "), ...
    em(Σ([1..10]^2)), ...
    br(), ...
    text("The code to do this is "), ...
    code("Σ([1..10]^2)"), ...
    blockquote("The sum of the square roots of any two sides of an isosceles triangle is equal to the square root of the remaining side!\n-- Homer J. Simpson"), ...
    table(pc(p(3d6))), ...
    barchart(p(2d6)), ...
    table(LU_Surprise), ...
    comment("This is an HTML comment!"), ...
    choice("class", [0 => "Choose a class...", 1 => "Fighter", 2 => "Magic User", 3 => "Cleric"]), ...
    button("Roll up a character...", "table(chargen)"), ...
    input("text", "name", "Frobozz", "Character's name? "), br(), ...
    button("Roll for surprise", "text(LU_Surprise)"), br(), ...
    text("Find out more at "), ...
    link("http://palamedes.altervista.org","Palamedes") ]

Changed map to allow you to iterate over a JSON object with a function that takes a key/value pair. Variable _1 is the key, _2 the value. For example,

map(_1 in ["b", "c"] ? _2^2 : _2, {"a": 3, "b": 2})

(Removed merge and aggregate functions introduced in a60. These were not documented.)

Added merge function that combines two json objects using a function:

merge(_1 in ["b", "c"] ? _2 + _3, {"a": 3, "b": 2}, {"b": 7, "c": 11})
merge(_1 in ["b", "c"] ? [_2, _3], {"a": 3, "b": 2}, {"b": 7, "c": 11})

Palamedes 1.0-a60 (2016-09-10)

Syntax changes

Parentheses around the arguments of functions that take multiple parameters are now mandatory!

    orderstat d20, 3, 1  /* Do NOT do this anymore */
    orderstat(d20, 3, 1) /* Do this instead */

    binom 6, 1/6         /* Do NOT do this anymore */
    binom(6, 1/6)        /* Do this instead */

map and filter

map is now implemented as a higher-order function that takes a function and an array as arguments:

    map 2 * n^2 + 1, n, [1..10]   /* Do NOT do this anymore */
    map(2 * _^2 + 1, [1..10])     /* Do this instead */

Ditto for filter.

There is also a structure-preserving version of map called fmap (for flat map) that operates elementwise on nested arrays like matrices, tensors, trees and graphs.

Two dashes -- (range constructor)

You may create the array [1..10] more simply as 1--10.

lookup

lookup now works differently than if. It still takes an array argument. The first element of the array is a distribution. The rest are pairs of arrays of ranges and lookup values. If a random variate from the distribution falls with a range, the corresponding lookup value is returned. You may pass a lookup to the p function in order to find its probability distribution.

Examples:

    MISCELLANEOUS_WEAPONS <- lookup [z100, ...
      01--25 => "10 Magic Arrows", ...
      26--40 =>  3d10 + " Magic Arrows", ...
      41--55 => "Dagger +1 vs Man-Sized Opponents, +2 vs Goblins and Kobolds", ...
      56--60 => "Dagger +2 vs Man-Sized Opponents, " + ...
                  "+3 vs Orcs, Goblins and Kobolds", ...
      61--65 => "Magic Bow", ...
      66--70 => "Axe +1", ...
      71--80 => "Mace +2", ...
      81--85 => "War Hammer +1", ...
      86--89 => "War Hammer +2", ...
        90   => "War Hammer +3, 6 in. Throwing Range with Return", ...
      91--96 => "Spear +1", ...
      97--99 => "Spear +2", ...
        00   => "Spear +3"]

    'MISCELLANEOUS_WEAPONS              NB. review its definition

    p MISCELLANEOUS_WEAPONS             NB. find its prob. distribution

    MISCELLANEOUS_WEAPONS               NB. generate one randomly

    10 # MISCELLANEOUS_WEAPONS//listify NB. generate ten randomly

OD&D Treasure Generator

The tests subdirectory in the source code ZIP archive contains a fairly complete and working treasure generator. Magic swords and jewels aren't implemented yet. Hope to get everything complete and debugged in version a61.

There is a new operator introduced to simplify this application. Expressions like this:

    10--60: 35%

work as follows:

    If `flip(35%)` is true, then
        If 60/10 is an integer, then
            return {10}d{60/10}
        Else
            return choose 10--60
    Else
        return 0

Another helpful feature added is that if the variable json_formatter is defined, then all JSON objects will pass through this function before being output. In this app, we use it to format the keys cp, sp and gp with the function commify(1000*_). See the code for the exact implementation. To get rid of this formatting, execute void json_formatter.

tables and matrices

Tables and matrices are now output in HTML format unless you set the plaintext variable to true, which may be done with plaintext on or set plaintext = true.

There is a new autosize variable, true by default, which will resize the first column of a table to the width of its longest value.

Plaintext tables still use the colwidth and colgap variables if autosize is off.

barcharts

You can now produce a barchart of a distrtibution by passing it to the barchart function. Barcharts are output in HTML format unless you set the plaintext variable to true.

    In[16]  := 2d6//p//barchart
    Out[16] :=
                                key     value
                                  2     █████
                                  3     ███████████
                                  4     █████████████████
                                  5     ███████████████████████
                                  6     █████████████████████████████
                                  7     ███████████████████████████████████
                                  8     █████████████████████████████
                                  9     ███████████████████████
                                 10     █████████████████
                                 11     ███████████
                                 12     █████

bin

You can lump a distribution into bins with the bin function.

Examples:

    In[1]  := modifiers <- {"-3": [3], "-2": 4--5, "-1": 6--8, "0": 9--12, "1": 13--15, "2": 16--17, "3": [18]}
    Out[1] := modifiers
    In[2]  := table modifiers
    Out[2] :=
           key     value
            -3     [3]
            -2     [4,5]
            -1     [6,7,8]
             0     [9,10,11,12]
             1     [13,14,15]
             2     [16,17]
             3     [18]

    In[3]  := ranges <- bin(3d6, [3,4--5,6--8,9--12,13--15,16--17,18])
    Out[3] := ranges
    In[4]  := tab ranges
    Out[4] :=
             key     value
      [13,14,15]     21.30%
         [16,17]      4.17%
            [18]      0.46%
             [3]      0.46%
           [4,5]      4.17%
         [6,7,8]     21.30%
    [9,10,11,12]     48.15%

    In[5]  := prob_of_modifier <- ranges after modifiers
    Out[5] := prob_of_modifier
    In[6]  := tab prob_of_modifier
    Out[6] :=
       key     value
        -3      0.46%
        -2      4.17%
        -1     21.30%
         0     48.15%
         1     21.30%
         2      4.17%
         3      0.46%

JSON

You may now use arbitrary expressions as values in JSON expressions.

Examples:

Generate D&D characters:

    chargen <- {"str": 3d6, "int": 3d6, "wis": 3d6, "con": 3d6, "dex": 3d6, "cha": 3d6, "gold": 10*3d6}
    'chargen             NB. review definition
    chargen              NB. randomly generate 1 character
    10#chargen//listify  NB. randomly generate 10 characters and format as list

Nest two JSON objects and do arithmetic on their values via fmap:

    mu <- {"alpha": 1, "beta": 2, "gamma": 3}
    nu <- {"delta": 10, "epsilon": [1..4], "zeta": mu}
    fmap(_^2, mu)
    fmap(2*_+1, nu)

Tables of complex objects should print corectly

    In[3]  := mytable <- {"alpha": {"beta": 2, "gamma": 3}, "delta": 4, "epsilon": {"zeta": 5, "eta": 6}, "omega": 00--09}
    Out[3] := mytable
    In[4]  := table mytable
    Out[4] :=
          key     value
        alpha     {"beta":2,"gamma":3}
        delta     4
      epsilon     {"zeta":5,"eta":6}
        omega     [0,1,2,3,4,5,6,7,8,9]

REPL commands: new, run, list, cls, etc.

There are also new commands to selectively delete history, functions, variables, SQL tables, and conditional probabilities (see below).

The following REPL cmds only work in the nodejs cli:

Convenience macros

These variables are pre-defined:

    mob ← 1 - cdf + p           /* meets-or-beats function */
    tab ← table(percent)        /* format as % and display in table */
    tbl ← table(percent(p))     /* calc prob, format %, display in table */
    pct ← percent(_)            /* format as % w/ 2 decimals */
    pc  ← percent(_,0)          /* format as % w/o decimals */

Example:

    In[6]  := tbl(2d6)
    Out[6] :=
    key     value
      2      2.78%
      3      5.56%
      4      8.33%
      5     11.11%
      6     13.89%
      7     16.67%
      8     13.89%
      9     11.11%
     10      8.33%
     11      5.56%
     12      2.78%

help

There is an experimental framework for a help system.

forget, void, efface, trash and cut

Use these functions to delete portions of the program.

Pipe into 2nd argument of binary function: ||>

Examples:

    In[17]  := [1..10] ||> take 3
    Out[17] := [1, 2, 3]
    In[18]  := foo <- _1 + _2
    Out[18] := foo
    In[19]  := [1..3] ||> foo [3,5,7]
    Out[19] := [4, 7, 10]
    In[20]  := foo <- _1^_2
    Out[20] := foo
    In[21]  := [1..3] ||> foo [3,5,7]
    Out[21] := [3, 25, 343]

Properly mapping 2 arguments: map versus mapcar

Examples:

    /* Multiply 3 times 5 to get 15 */
    apply(_1 * _2, 3, 5)
    [3,5] |> _1 * _2
    _1 * _2 @@ [3,5]

    /* map takes 1 list and applies function to each element */
    /* first element is the array [3,5] */
    /* here map returns [15, 14], i.e., [3*5, 7*2] */
    map(_1 * _2, [[3,5], [7,2]])

    /* mapcar takes 2 lists, while map takes only 1 list */
    /* here mapcar returns [21,10], i.e., [3*7, 5*2] */
    /* NOTE: mapcar is not the same as map!!! */
    apply(_1 * _2, [3,5], [7,2])
    [[3,5], [7,2]] |> _1 * _2
    _1 * _2 @@ [[3,5], [7,2]]

Improperly mapping 2 arguments: apply and fmap pitfalls

    /* Not meaningful... throws exception */
    [[3,5]] |> _1 * _2
    apply(_1 * _2, [3,5])
    _1 * _2 @@ [[3,5]]

    /* Also not meaningful, b/c fmap works elementwise */
    /* it never has two operands to work with... */
    fmap(_1 * _2, [[3,5], [7,2]])  NB. ERROR!

Evaluation control with higher-order functions

Normally we supply map with a function and an array as arguments like so:

    In[1]  := map(sin, [0, pi/6, pi/4, pi/2]) |> exact
    Out[1] := [0, 1/2, √2/2, 1]

But we might want to create a function out of map in which the array part is variable. In this case, the value of the function must be held like so:

    In[2]  := func <- map(hold(sin), _)
    Out[2] := func
    In[3]  := func [0, pi/6, pi/4, pi/2] |> exact
    Out[3] := [0, 1/2, √2/2, 1]

And in other cases, we might want to create a function out of map in which the function part is variable. In that case, the value of the function must be evaluated immediately, like so:

    In[4]  := func <- map(eval, [0, pi/6, pi/4, pi/2])
    Out[4] := func
    In[5]  := func sin |> exact
    Out[5] := [0, 1/2, √2/2, 1]

Note: Evaluation control is purely exprimental and may change in behavior soon.

Style-insensitivity

Use whatever style you want by defining macros. Exception: dash-style is not supported b/c it looks like subtraction. Examples using lc, the function to convert a string to lower case

    lower_case ← lc  /* snake_style */
    lowerCase ← lc   /* humpStyle */
    LowerCase ← lc   /* MixedCaseStyle */
    LOWERCASE ← lc   /* UPPERCASESTYLE */
    lowercase ← lc   /* lowercasestyle */
    LOWER ← lc
    LCASE ← lc
    toLowerCase ← lc
    lowcase ← lc
    lower ← lc

Renamed, removed, and re-purposed functions

Palamedes 1.0-a59 (2016-08-17)

Defining unary functions

It's now possible to combine builtin unary functions with basic arithmetic operations (+, -, *, /, mod, ^, √) and constants (numeric, string, boolean), in order to define new functions, and apply them in a multitude of ways.

EXAMPLE: To find the probability of meeting or beating a target number, we proceed as follows. The cumulative distribution function (cdf) is the probability of being less than or equal to a target number. So it's complement, 1 - cdf, is the probability of being greater than a target number. Finally, 1 - cdf + p is the probability of meeting or beating a target number. We can define this in Palamedes as follows:

    mob ← 1 - cdf + p

Now we can apply it just like any other unary function, using either prefix or postfix notation. For example, to show a table of probabilities of 3d6 meeting or beating a target number, we can do any of the following:

    table(percent(mob(3d6)))        NB. prefix notation
    mob 3d6//QQ//table              NB. prefix with postfix formatting
    3d6 |> mob |> percent |> table  NB. postfix notation

Point-free style

Note that when we define functions like this, we use "pointless" or point-free style, which is to say: we do not identify the arguments (or "points") on which these functions operate.

For example, we don't do this...

    mob(a) ← 1 - cdf(a) + p(a)  NB. DON'T DO THIS!!!

We do this instead...

    mob ← 1 - cdf + p

When defining functions this way, don't overlook the value of the id function, which you can substitute where ever you would have used the "point". For example, to define g(a) = 2*(1+a), we can do this:

    g ← 2*(1+id)

Then, to apply g to 20, we can do any of the following:

    g(20)
    g<|20
    20|>g
    g 20

Lambda unary functions

It's possible to use simple functions like 1 - cdf + p without assigning them to an identifier like mob. For example, all of the following produce equivalent results:

    apply (1 - cdf + p) (x20 + x10)  NB. `apply` operator
    1 - cdf + p <| x10 + x20         NB. backward pipe notation
    x10 + x20 |> 1 - cdf + p         NB. forward pipe notation, F\#-style
    x10 + x20//1 - cdf + p           NB. forward pipe notation, Mathematica-style

We can apply the g function discussed above to 20 anonymously using use apply, pipe forward or backward in several ways:

    In[1]  := apply (2*(1+id)) 20
    Out[1] := 42
    In[2]  := 20 |> 1 + id |> 2 * id
    Out[2] := 42
    In[3]  := 2*(1+id) <| 20
    Out[3] := 42

Postfix notation with nullary functions

Postfix notation with nullary functions is now supported:

    table(History())        NB. Prefix notation with nullary function
    () |> History |> table  NB. Postfix notation with nullary function
    ()//History//table      NB. Ditto

Meet-or-beat function

For your convenience, the meet-or-beat function mob has been pre-defined as

    mob ← 1 - cdf + p

but you are free to redefine it.

More changes

Palamedes 1.0-a58 (2016-08-12)

Input/output History

In the new cli interface, the input prompt looks like this:

    In[42] :=

and in both the new web and cli interfaces, the output is formatted like this:

    In[42]  := 4d6k3
    Out[42] := 16

Out[number or array] is a new function that takes a number (or array of numbers) as input and returns the output corresponding to the given number(s). For example,

    In[43]  := 10*Out[42]
    Out[43] := 160

Since Out takes an array of numbers, you can do something more complicated, like the following. We roll 3d6 six separate times and then aggregate the outputs together in a new D&D character named merlin:

    In[0]  := 3d6
    Out[0] := 6
    In[1]  := 3d6
    Out[1] := 12
    In[2]  := 3d6
    Out[2] := 7
    In[3]  := 3d6
    Out[3] := 5
    In[4]  := 3d6
    Out[4] := 10
    In[5]  := 3d6
    Out[5] := 8
    In[6]  := set merlin = ["str","int","wis","con","dex","cha"] → Out[0..5]
    Out[6] := {"str":6,"int":12,"wis":7,"con":5,"dex":10,"cha":8}
    In[7]  := merlin |> table
    Out[7] :=
                                    key     value
                                    cha     8
                                    con     5
                                    dex     10
                                    int     12
                                    str     6
                                    wis     7

There's also a new History() function that returns a JSON object with your command history. You probably want to pipe it through table to format it:

    In[8]  := History() |> table
    Out[8] :=
                                    key     value
                                      0     3d6
                                      1     3d6
                                      2     3d6
                                      3     3d6
                                      4     3d6
                                      5     3d6
                                      6     merlin ↜ ["str", "int", "wis", "con", "dex", "cha"] → Out([0 .. 5])
                                      7     table(merlin)

Optionally, History() takes a start number and an end number. It will display the history starting with first number, up to but not including the second number. These numbers default to 0 and the number of the pending input, respectively.

The expression history has been pre-defined as table(History()), but you can re-define it.

Caveat: History() shows expressions after they were parsed, not how they were input. This is useful for debugging expressions that don't do what was expected.

There is also a Counter() function that has the number of the pending input.

Similarly to Out, there is a new In[number or array] function that can be used to redo the given line or lines of input:

    In[0]  := 6 # 4d6k3
    Out[0] := [12, 7, 5, 15, 12, 15]
    In[1]  := In[0]
    Out[1] := [10, 12, 11, 7, 13, 12]
    In[2]  := In[0]
    Out[2] := [13, 9, 16, 14, 10, 14]

To test set membership, use either in in all lowercase or the Unicode symbol ∈.

Arrow

As shown earlier, the new arrow operator (→) constructs mappings (JSON objects) from two input arrays:

    Dice> c gets ["a","b","c"] arrow [1,2,3]
    c
    Dice> c
    {"a":1,"b":2,"c":3}
    Dice> c after "b"
    2

    Dice> chargen ← ["str","int","wis", "con", "dex", "cha", "gold"] →  (6 # 3d6) ⊎ [10 * 3d6]
    chargen
    Dice> chargen
    {"str":12,"int":14,"wis":9,"con":5,"dex":9,"cha":11,"gold":140}
    Dice> chargen
    {"str":10,"int":11,"wis":11,"con":13,"dex":10,"cha":10,"gold":120}
    Dice> set frobozz = chargen
    {"str":8,"int":14,"wis":11,"con":12,"dex":12,"cha":7,"gold":130}
    Dice> frobozz ∘ "str"
    8

Logical implication has been renamed from "→" to "⊃"|"IMPLIES"|"Implies"|"implies".

S(h)ort(hand)

There's new Unicode shorthand for sort (⍋) and reverse sort (⍒) that work on arrays and strings:

    Dice> ⍋[6, 4, 3, 2, 8, 7, 9, 10, 5, 1]
    [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
    Dice> ⍒[6, 4, 3, 2, 8, 7, 9, 10, 5, 1]
    [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
    Dice> ⍋"Jesse Custer"
    [" ", "C", "J", "e", "e", "e", "r", "s", "s", "s", "t", "u"]

Anagrams

The new anagram function works on arrays and strings:

    Dice> anagram("","")
    true
    Dice> anagram("dog","cat")
    false
    Dice> anagram("Jesse Custer","Secret Jesus")
    true
    Dice> anagram([],[])
    true
    Dice> anagram([1,2,3],[3,2,1])
    true
    Dice> anagram([1,2,3],[3,5,1])
    false

Roman numerals

Convert numbers between Roman and Arabic numerals:

    Dice> roman [1974, 2016]
    [MCMLXXIV, MMXVI]
    Dice> arabic ["MCMLXXIV", "MMXVI"]
    [1974, 2016]

These functions are inverses of each other:

    Dice> arabic(roman [1974, 2016])
    [1974, 2016]
    Dice> roman(arabic ["MCMLXXIV", "MMXVI"])
    [MCMLXXIV, MMXVI]

Ciphers

Implemented affine ciphers. affine(plaintext, multiplier, shift, direction) produces the corresponding ciphertext. By default, direction=1 means to encrypt and -1 means decrypt. For example, multiplier=25 and shift=25 results in the Atbash cipher, and multiplier=1, shift=13 results in ROT13. There are convenience functions for atbash and rot13, which are their own inverse. Examples:

    Dice> atbash("Affine Cipher")
    "Zuurmv Xrksvi"
    Dice> atbash(atbash("Affine Cipher"))
    "Affine Cipher"
    Dice> rot13("Affine Cipher")
    "Nssvar Pvcure"
    Dice> rot13(rot13("Affine Cipher"))
    "Affine Cipher"
    Dice> affine("Affine Cipher", 5, 8)
    "Ihhwvc Swfrcp"
    Dice> affine("Ihhwvc Swfrcp", 5, 8, -1)
    "Affine Cipher"

Range constructors now work with letters too. Here's a round about way to make a lowercase routine:

    Dice> toLower gets ["A".."Z"] concat ["a".."z"] arrow ["a".."z"] concat ["a".."z"]
    toLower
    Dice> glue(toLower after split("WTF",""),"")
    wtf

Better to use the builtin lc function instead:

    Dice> lc("WTF")
    "wtf"

Here's the ATBASH cipher the hard way:

    Dice> ATBASH gets ["A".."Z"] concat [unquote " "] arrow ["Z".."A"] concat [unquote " "]
    ATBASH
    Dice> ATBASH
    {"A":"Z","B":"Y","C":"X","D":"W","E":"V","F":"U","G":"T","H":"S","I":"R","J":"Q","K":"P","L":"O","M":"N","N":"M","O":"L","P":"K","Q":"J","R":"I","S":"H","T":"G","U":"F","V":"E","W":"D","X":"C","Y":"B","Z":"A"," ":" "}
    Dice> secret gets "the quick brown fox jumps over the lazy dog"
    secret
    Dice> glue(ATBASH after split(uc secret, ""), "")
    GSV JFRXP YILDM ULC QFNKH LEVI GSV OZAB WLT
    Dice> glue(ATBASH after ATBASH after split(uc secret, ""), "")
    THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG

Better to use the atbash builtin function instead.

Primes

Replaced the primes(n) function that returns the primes less than n with the Prime[number or array of 1-based indices] function that returns the array of primes with the given indices. Examples:

    In[0]  := Prime(1) NB. return the first prime
    Out[0] := 2
    In[1]  := Prime[1..10] NB. return the first ten primes
    Out[1] := [2, 3, 5, 7, 11, 13, 17, 19, 23, 29]
    In[2]  := Prime(Prime[1..10]) NB. return the first ten Super-primes
    Out[2] := [3, 5, 11, 17, 31, 41, 59, 67, 83, 109]

Super-primes are sometimes called "higher order primes" or "Prime-Index-Primes" or "PIPs." They are sequence A006450 in the OEIS.

Caveat: The Prime function only knows about the first 1000 primes (and subsequently the first 168 Super-primes).

TODO: Create a Sieve(n) function that will find all the primes up to the limit n. See the Sieve of Eratosthenes.

TODO: Create an IsPrime[number or array of numbers] function that will test the primality of the input number (or numbers).

Version

Version() is now a function that returns a JSON object:

    In[0]  := version()
    Out[0] := {"Name":"Palamedes 1.0-a58 (2016-08-08)","Project":"Palamedes","Version":"1.0-a58","Major Version":1,"Minor Version":0,"Stage":"Alpha","Build":58,"Date":"2016-08-08"}
    In[1]  := version() |> table
    Out[1] :=
                                    key     value
                                  Build     58
                                   Date     2016-08-08
                          Major Version     1
                          Minor Version     0
                                   Name     Palamedes 1.0-a58 (2016-08-08)
                                Project     Palamedes
                                  Stage     Alpha
                                Version     1.0-a58
    In[2]  := version() ∘ "Build" NB. Just get the build component of the version
    Out[2] := 58

Internally, the version is now kept as a JSON file, rather than an HTML file.

The expression version has been pre-defined as table(Version()), but you may redefine it as desired.

save/load/transcript/show

The save command has been updated to save your entire command history. It takes an optional string with a file name. By default, the filename is "dice.sav".

The load command has been updated to load a file of Palamedes commands line-by-line and execute them. It takes an optional string with a file name. By default, the filename is "dice.sav".

The transcript command has been updated to append the output of each subsequent command to a file. It takes an optional string with a file name. By default, the filename is "dice.out".

The show command has been updated to display the contents of a file to the screen. It takes a string with a file name. This is useful in conjunction with the previous commands, e.g., show "dice.sav" or show "dice.out".

Command-line interface

The cli now takes the following options

Short option Long Option Use
-e --exec execute the following command and exit
-h --help print CLI usage and exit
-v --version print Palamedes version info and exit

Examples:

    node cli.js -e "6#3d6"

will roll 3d6 6 times, print the results as an array, then exit.

    node cli.js < tests/dreidel.pal

will execute the given test script, then exit.

    node cli.js

will enter interactive mode. You can exit with any of the commands bye, close, end, quit, stop or Ctrl-D.

Web interface

The web interface only accepts 1 line at a time and does not accept partial statements any more. However, there is a link to open a form with a textarea and a submit button that accepts longer scripts, and these scripts may contain lines with partial statements followed by an ellipsis, just like the old (version a57 and prior) interface.

Palamedes 1.0-a57 (20151026)

ISO Base Name
lb 2 binary logarithm
ln e natural logarithm
lg 10 common logarithm

Juxtaposition

Removed the juxtaposition operator from the AST and coded the following cases directly into the parser:

    dice_expression negative_number --> subtraction

E.g., 2d6-1 (juxtaposition) same as 2d6 -1 (juxtaposition) same as 2d6 - 1 (subtraction)

    dice_expression positve_number --> sampling operator

E.g., 6#3d6 same as 3d6 6 same as 3d6 6 times (if times = 0 and base = 1)?

    array negative_number --> subtraction
    array positive_integer --> subscripting index operator

    number IDENTIFIER --> multiplication
    IDENTIFIER negative_number --> subtraction
    IDENTIFIER positive_number --> function call if identifier is a function, error otherwise

E.g., 2 π same as 2*pi

    E negative_number --> subtraction
    E positive_number --> exponentiation
    number E --> multiplication

E.g., E 2 same as e^2 or exp(2)

    number number --> base n1 + n2
    number number number --> base^2 n1 + base n2 + n3
    etc.

By default, base = 1, so 1 2 3 4 same as 1+2+3+4 (--> 10). But set base=2 and you can do binary: 1 0 1 --> 5 and 1 1 0 --> 6 and 1 1 1 --> 7 etc.

Examples of symmetric/palindrome

    symmetric(3d6) → true
    symmetric(4d6k3) → false
    palindrome "Never odd or even" → true
    symmetric "palindrome" → false
    symmetric 1001 → true
    symmetric 1901 → false
    symmetric [1, 0, 0, 1] → true
    symmetric [1, 9, 0, 1] → false
    symmetric [[1, 0], [0, 1]] → true
    symmetric [[1, 9], [0, 1]] → false

New Aliases

Proposed naming convention: When it makes sense, operators/functions/commands ought to have up to 4 names:

Aliases for existing functions

Aliases for new functions

Palamedes 1.0-a56 (20150814)

Palamedes 1.0-a55 (20150320)

Added string functions:

  1. lc(string) - convert input string to lower case; one argument; threadable/listable
  2. glue(string, separator) - glues the array of input strings together after removing quotes; 2 arguments; not threadable/listable
  3. split(string, separator, limit) - split input string into array using separator up to limit times; 3 arguments; not threadable/listable
  4. split2(string, separator) - split input string into array using separator; only 2 arguments; threadable/listable
  5. tc(string) - convert input string to title case; one argument; threadable/listable
  6. toNumber(string) - convert input string to a number; one argument; threadable/listable
  7. trim(string) - trim whitespace from both ends of input string after removing quotation marks; one argument; threadable/listable
  8. uc(string) - convert input string to upper case; one argument; threadable/listable

Changed the sum function.

  1. Applied to a string, sum returns it.
  2. Applied to an array of strings, sum concatenates them, after removing quotation marks.

Examples

Change the case of a string:

Dice> lc "STOP SHOUTING"
lc("STOP SHOUTING") → "stop shouting"

Dice> uc "talk louder!"
uc("talk louder!") → "TALK LOUDER!"

Dice> tc "a day no pigs would die"
tc("a day no pigs would die") → "A Day No Pigs Would Die"

Split an array of strings on whitespace using split2 and regexes (in single quotes). This produces a matrix of strings. Note that quoes are removed from the inputs:

Dice> split2( ["A Day No Pigs Would Die","The Catcher in the Rye"], '\s+')
split2(["A Day No Pigs Would Die", "The Catcher in the Rye"],'\s+') →
[[   A,     Day, No, Pigs, Would, Die ], ...
 [ The, Catcher, in,  the,    Rye   ]]

Split a string into an array of characters using split:

Dice> split("a day no pigs would die", "")
split("a day no pigs would die","") → [a,  , d, a, y,  , n, o,  , p, i, g, s,  , w, o, u, l, d,  , d, i, e]

Use the 3rd/limit parameter to split:

Dice> split("a day no pigs would die", "")
split("a day no pigs would die","") → [a,  , d, a, y,  , n, o,  , p, i, g, s,  , w, o, u, l, d,  , d, i, e]

Dice> split("a day no pigs would die", "", 5) drop 2
split("a day no pigs would die","",5) drop 2 → [d, a, y]

As a reminder, you can repeat a string using multiplication * of a string by a number (in any order), e.g.,

Dice> 3 * " die" + "!"
3 * " die" + "!" → " die die die!"

Dice> " die" * 3 + "!"
" die" * 3 + "!" → " die die die!"

Note that trim automatically chops off quotation marks:

Dice> 3 * " die" + "!" |> trim
trim(3 * " die" + "!") → die die die!

Dice> 3 * " die" + "!" |> trim |> quote
quote(trim(3 * " die" + "!")) → "die die die!"

glue works like JavaScript's join. It takes an array of strings and a separator and glues them together:

Dice> glue(["hello", "world"], ", ")
glue(["hello", "world"],", ") → hello, world

Note that the join infix operator does a natural join between tables.

The function sum has been altered, so that if you apply it to an array of strings, it joins them using the empty string as the separator:

Dice> sum( tc["hello", "world"])
sum(tc(["hello", "world"])) → HelloWorld

Dice> sum ["123", "543"]
sum(["123", "543"]) → 123543

And sum applied to a single string returns the string itself:

Dice> sum( "hello" )
sum("hello") → "hello"

To coerce strings to numbers, use the new toNumber function:

Dice> toNumber ["123", "543"]
toNumber(["123", "543"]) → [123, 543]

Dice> ["123", "543"] |> toNumber |> sum
sum(toNumber(["123", "543"])) → 666

Palamedes 1.0-a54 (20150313)

  1. Change: The select operator has been renamed pick. This was done because, Palamedes now supports SQL expressions that operate on matrices. See below for more info.
  2. Added: SQL statements like select Name, Age from Person where Age > 39 order by Age desc. There is support for natural joins and group-by clauses. See SQL for complete details.
  3. Change: The syntax for high/low and take/drop has changed (again), this time to make them work like other pipe expressions. The syntax is expression TAKE number
  4. Added: take and drop have new short forms and respectively.
  5. Added: high and low have been expanded to work on JSON objects in addition to arrays. Given a JSON object, they return a new object with the highest/lowest number keys. Similarly, take/drop have been expanded to work on JSON objects---they will take/drop the given number of keys at random.
  6. Added: There is new/additional syntax for gets/set.
  7. Added: Standard math notation for ceil and floor functions.
  8. Added Unicode Miscellaneous Symbols (U+2600 - U+26FF) incl. Playing cards & dice to tokens allowed in identifiers.
  9. Change: Made after operator a pipe so you can write Person group by Age after 34 after 1 after 0 without parens.
  10. Change: Switched from John Gruber's original Markdown to Python-Markdown, and the extra and toc extensions. In the future, may add an extension for writing math formulae in LaTeX like Markdown-LaTeX which converts LaTeX to images; other math extensions use MathJax or MathML; difficult to pick one over the other, since support varies by browser.

Assignment operators

The syntax for the set statement

logger ↜ true
logger <: true
logger set true

has been supplemented with some new forms which may read better:

set logger true
set logger=true

The set statement has two new forms: += for adding to a variable and *= for multiplying a variable. Example:

set myvar = 5
set myvar += 5  NB. myvar is now 10
set myvar *= 5  NB. myvar is now 50

We may add more assignment operators such as -=, /=, %=, &=, |=, ^=, ↑=.

The gets statement

yourcar ← myvar + 1
yourcar <- myvar * 2
yourvar gets myvar ^ 3

has now been supplemented with one additional form which may read better:

let yourvar = myvar^3

Remember: set evaluates its RHS and stores the value, whereas gets does no evaluation and stores the expression. Call the variable itself to evaluate its stored value:

yourvar

or use the ' or lazy operator to see the stored expression:

'yourvar
lazy yourvar

More aliases for booleans

The boolean value true now has aliases yes and on. The following 5 expressions all do the same thing:

set logger = true  NB. TRUE and True also work
set logger = yes
set logger = on
set logger = ⊤  NB. Most concise version
set logger = otherwise  NB. Verbose but useful in an if/lookup statement, e.g.,

let anniversary = if [year=1 ⇒ "Cotton", year=2 ⇒ "Paper", …
    year=3 ⇒ "Leather", year=4 ⇒ "Fruit and flowers", …
    otherwise ⇒ error "divorce"]

False may be written , no or off.

Take, drop, high, low

These statements have changed once again. BEWARE: The forms TAKE number, array and TAKE number from array are no longer supported! The only supported form is expression TAKE number. There is an short form: expression ↑ number. Examples:

/* Pick a number 1 to 10 */
shuffle [1..10] take 1

/* Shuffle and deal 5 cards */
([2..10] ∪ ["J", "Q", "K", "A"]) × ["♠", "♡", "♢", "♣"] |> shuffle ↑ 5

Drop works in a similar fashion. It has the synonym .

High and Low now also work with JSON objects:

{"1000": "Biggest", "2": "Small", "0": "Smallest", "1": "Smaller", "100": "Bigger", "10": "Big"} high 3

{"1000": "Biggest", "2": "Small", "0": "Smallest", "1": "Smaller", "100": "Bigger", "10": "Big"} low 3

Take and drop also work with JSON. If you wanted to take random sub-objects of size 3, you could do this:

{"1000": "Biggest", "2": "Small", "0": "Smallest", "1": "Smaller", "100": "Bigger", "10": "Big"} take 3

Ceil, floor

Palamedes now supports the standard math notation ⌈1.5⌉ and ⌊3/2⌋ for the ceiling and floor functions. These

⌈ 1.5 ⌉
⌊ 1/2 + 1 ⌋

are equivalent to

ceil(1.5)
floor(0.5 + 1)

Palamedes 1.0-a53 (20150304)

Example: Weighted sums of probability distributions

CommandResults
/* Base value of jewelry in D&D Vol 2 Monsters and Treasure, p. 40 */
/* Define it as a weighted sum */
JewelryProb ← 20%*prob(100*3d6) + 60%*prob(1000*d6) + 20%*prob(1000*d10)

/* Apply `select` to the weighted sum to make a random jewelry generator */
Jewelry ← select JewelryProb

/* Generate one piece of jewelry */
Jewelry

/* Generate array of ten pieces of jewelry */
10#Jewelry

/* Generate ten pieces of jewelry displayed as a list with commas and "gp" */
10#Jewelry + " gp"//unquote//commify//listify

/* Applying `prob` to Jewelry same as JewelryProb */
all(JewelryProb = prob(Jewelry))

/* Show the probability table as rationals */
Jewelry//prob//rat//table

/* Verify the probabilities sum to unity */
Jewelry//prob//sum

/* Show a table of descriptive statistics */
Jewelry//stats//round//commify//table
JewelryProb ← 0.2 * prob(100 * 3d6) + 0.6 * prob(1000 * d6) + 0.2 * prob(1000 * d10)

Jewelry ← select JewelryProb

Jewelry → 1000

10 # Jewelry → [3000, 2000, 1000, 9000, 2000, 400, 3000, 2000, 800, 3000]

listify(commify(unquote(10 # Jewelry + " gp"))) → 6,000 gp
6,000 gp
3,000 gp
1,000 gp
6,000 gp
2,000 gp
2,000 gp
9,000 gp
5,000 gp
1,000 gp

∀ (JewelryProb = prob(Jewelry)) → true

table(rat(prob(Jewelry))) →
                                key     value
                                300     1/1080
                                400     1/360
                                500     1/180
                                600     1/108
                                700     1/72
                                800     7/360
                                900     5/216
                               1000     29/200
                               1100     1/40
                               1200     5/216
                               1300     7/360
                               1400     1/72
                               1500     1/108
                               1600     1/180
                               1700     1/360
                               1800     1/1080
                               2000     3/25
                               3000     3/25
                               4000     3/25
                               5000     3/25
                               6000     3/25
                               7000     1/50
                               8000     1/50
                               9000     1/50
                              10000     1/50

sum(prob(Jewelry)) → 1

table(commify(round(stats(Jewelry)))) →
                                key     value
                              count     25
                               kurt     -0.03
                                max     10,000
                               mean     3,410
                             median     3,000
                                min     300
                               mode     1,000
                                 sd     2,325.92
                               skew     0.79
                                var     5,409,900

Palamedes 1.0-a52 (20150304)

Fixed bugs in the internal functions r_op1 and r_op2 used to thread operators over structured arguments. In cases where one argument was the empty array [], an exception might have been thrown or a wrong result returned. Here are examples of behavior before and after this change:

BeforeAfter
[] + 1 → 1
fix([]) → exception
{"key": []}//round → exception
[] + 1 → []
fix([]) → []
{"key": []}//round → {"key": []}

Palamedes 1.0-a51 (20150303)

Palamedes 1.0-a50 (20150302)

Palamedes 1.0-a49 (20150201)

echo off/on and quiet on/off

InputOutput
1+2+3+4
1+2+3+4+5
echo off
1+2+3+4
1+2+3+4+5
quiet on
1+2+3+4
1+2+3+4+5
quiet off
last
echo on
last
1 + 2 + 3 + 4 → 10

1 + 2 + 3 + 4 + 5 → 15

10

15

15

last → 15

Take

The syntax for take and drop have changed. They now have 3 alternate forms. Here is an example that uses take in conjunction with shuffle to sample from a distribution with equal probabilities without replacement. All three forms of take are shown:

InputOutput
echo off
ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
suits ← ["♠", "♡", "♢", "♣"]
cards ←  ranks × suits

shuffle cards take 5
take 5 from shuffle cards
take 5, shuffle cards
ranks ← [2 .. 10] ∪ ["J", "Q", "K", "A"]

suits ← ["♠", "♡", "♢", "♣"]

cards ← ranks × suits


[[ "J", "♢" ], ...
 [   2, "♢" ], ...
 [ "Q", "♠" ], ...
 [ "Q", "♢" ], ...
 [ "A", "♠" ]]


[[ "J", "♢" ], ...
 [   8, "♡" ], ...
 [   8, "♣" ], ...
 [   4, "♣" ], ...
 [   9, "♢" ]]


[[  5, "♢" ], ...
 [  8, "♠" ], ...
 [ 10, "♡" ], ...
 [  9, "♠" ], ...
 [  5, "♣" ]]

Without

without is used to sample from a distribution with unequal probabilities without replacement. Here are some examples:

InputOutput
echo off
0 without [1, 25%, 2, 50%, 3, 25%]
1 without [1, 25%, 2, 50%, 3, 25%]
2 without [1, 25%, 2, 50%, 3, 25%]
3 without [1, 25%, 2, 50%, 3, 25%]
4 without [1, 25%, 2, 50%, 3, 25%]
[]

[2]

[1, 3]

[3, 1, 2]

[2, 1, 3]

listify and stringify

InputOutput
echo off
"The list of " + ["A","B"]
"The list of " + stringify(unquote(["A","B"]))
listify(["A","B"])
listify(unquote(["A","B"]))
["The list of A", "The list of B"]

"The list of A, B"

"A"
"B"

A
B

LIKE infix operator examples

InputOutput
echo off
"Hello" like 'hel'i
"Hello" like 'heck'i
"Hello" like 'hel'
"Hello" =~ 'Hel'
"Hello" !~ '^Hel'
"Hello" like ['^Hel', 'lo', '^lo']
["hello","help", "hollow"] LIKE '^Hel'
["hello","help", "hollow"] !~ '^Hel'
["hello","help", "hollow"] =~ '^Hel'i
["hello","help", "hollow"] !~ '^Hel'i
outer(like2,["Hello","Help", "hollow"],['^Hel', 'lo', '^lo'])
true

false

false

true

false

[true, true, false]

[false, false, false]

[true, true, true]

[true, true, false]

[false, false, true]


[[  true,  true, false ], ...
 [  true, false, false ], ...
 [ false,  true, false ]]

Compression operator

InputOutput
echo off
domain(ls()) like 'tril'i | domain(ls())
domain(ls()) like 'tril'i | domain(ls()) | ls()
domain(ls()) like 'tril'i | domain(ls()) | ls() |> table
[trillion, trillionth]

{"trillion":"1000000000000","trillionth":"1e-12"}


                                key     value
                           trillion     1000000000000
                         trillionth     1e-12

Covariance, correlation, slope, intercept and linreg

InputOutput
echo on
$x gets [1,2,3,4,5]
$y gets [1,2,1.3,3.75,2.25]
var($x)
var($y)
cov2($x,$y)
cov($x,$y)
corr2($x,$y)
corr($x,$y)
$x ← [1, 2, 3, 4, 5]

$y ← [1, 2, 1.3, 3.75, 2.25]

var($x) → 2.5

var($y) → 1.14925

cov2($x,$y) → 1.0625

cov($x,$y) →
[[    2.5,  1.0625 ], ...
 [ 1.0625, 1.14925 ]]

corr2($x,$y) → 0.6268327489789575

corr($x,$y) →
[[                  1, 0.6268327489789575 ], ...
 [ 0.6268327489789575,                  1 ]]

The Linear regression example may be redone more simply as:

$x gets [1,2,3,4,5]                  /* independent data: array */
$y gets [1,2,1.3,3.75,2.25]          /* dependent data: array */
linreg($x,$y)

This will output an array whose first element is the intercept and whose second is the slope.

Chisqtest

Refer to the example at Chi-Square Test for Independence. This can be done quite simply by passing the contingency table to the Chisqtest function like so:

InputOutput
Chisqtest [[200, 150, 50],[250, 300, 50]]//table
                                key     value
                              chisq     16.203703703703702
                                 df     2
                            p-value     0.0003029775487248809

Palamedes 1.0-a48 (20141219)

Palamedes 1.0-a47 (20141211)

Palamedes 1.0-a46 (20141210)

Palamedes 1.0-a45 (20141202)

Expanded support for Bayesian Networks

Definition. A Bayesian network is a probabilistic model that represents a set of random variables and their conditional dependencies. They have extensive applications in areas such as

History. Harvard Professor Joel Blitzstein says, "The soul of statistics is conditioning." The method for "inverting" a conditional probability to determine the probability of an unobserved variable is Bayes Law, developed by the Reverend Thomas Bayes in his Essay towards solving a Problem in the Doctrine of Chances in 1763.

More than a millenium prior, the rhethoritician Gorgias (483—375 B.C.E.) presented the archetype of a legal argument based on the conditional probabilities connecting evidence to guilt. This speech is entitled The Defense of Palamedes. I have an essay on it: On Gorgias' Defense of Palamedes. The centrality of Palamedes in this seminal work is yet another important reason this software bears his name!

Fallacies. One motivation for automating Bayesian inference is that many people find it difficult to reason about conditional probabilities, and there are many common fallacies. Two prominent fallacies are

In the paper Probabilistic reasoning in clinical medicine: Problems and opportunities by David M. Eddy (1982), a hundred physicians were asked about the probability of a having a disease based on a positive test result; 95% of these doctors committed the inverse fallacy in their answer. The Sally Clark case is a recent example of an expert witness committing the prosecutor's fallacy.

Random variables. Palamedes allows a user to input relationships among a set of random variables. It then can answer probabilistic queries about them.

Right now, Palamedes' Bayes net model only supports binary random variables, e.g., guilty and ~guilty (meaning "not guilty" or "innocent"). In a future version, random variables will be able to take arbitrary values, e.g., Weather=snow, Weather=rain, or Weather=sunny.

Probability statements. Probabilities are input or queried into the system via statements of the form Pr(Conjunction1 of random variables | Conjunction2 of random variables). The bar | (meaning "given") and the second conjunction are optional. Here are a few examples:

CommandNotes
Pr(guilty) gets value0
Example of inputting an unconditional probability
Pr(evidence1|~guilty) gets value1
Example of inputting a conditional probability
Pr(guilty|evidence1,evidence2,evidence3)
Example of querying a conditional probability involving a conjunction of random variables

Conjunctions between random variables may be input using commas; the word "and" or the symbol &&; or the word "intersect" or the symbol .

Implementation. Exact and approximate methods exist for inferring unobserved variables in Bayesian networks. Common exact methods include exponential enumeration and variable elimination. Approximate methods include importance sampling, belief propagation, and stochastic MCMC simulation.

Palamedes uses an exact method that takes an AI/logic programming approach. It uses pattern matching to match user inputs against mathematical rules for making deductions until the current goal is achieved. The rules are simply what you would find in any probability and statistics textbook about conditional probabilities:

To see a table of all of the deductions made by the system, use the command bayes(). Note: These deductions are memoized. If you need to clear them because you've added new info or changed values, simply pass an argument to the bayes function, e.g., bayes(0).

This implementation is inspired by Ivan Bratko's interpreter for belief networks, figure 15.11, in the book Prolog Programming for Artificial Intelligence. It has comparable performance to variable elimination.

Limitations. The main limitation of any exact method is that it has computational complexity O(k*2^k). So approximate methods are needed to deal with large belief networks. In a future version, we will implement an MCMC method for dealing with large nets.

Example 1.

The following example of legal reasoning in a court case example comes from the paper Teaching an Application of Bayes' Rule for Legal Decision-Making: Measuring the Strength of Evidence, pp. 10-12.

CommandResults
/* 3.1.1 Calculation of Posterior Probability after Evidence 1 (E₁), p. 10 */
Pr( G)                  ← 0.5
Pr(~G)                  NB. Should be 0.5
Pr(E₁|G)                ← 1
Pr(E₁|~G)               ← 0.45
Pr(G|E₁)                NB. Should be 0.69
/* 3.2.1 Calculation of Posterior Probability after the Second Piece of Evidence (E₂), p. 11 */
Pr(~G|E₁)               NB. Should be 0.31
Pr(E₂|G and E₁)         ← 1
Pr(E₂|~G and E₁)        ← 0.21
Pr(G|E₁ and E₂)         NB. Should be 0.914
/* 3.3.1 Calculation of Posterior Probability after Evidence 3 (E₃), p. 12 */
Pr(~G|E₁ and E₂)        NB. Should be 0.086
Pr(E₃|G and E₁ and E₂)  ← 1
Pr(E₃|~G and E₁ and E₂) ← 0.017
Pr(G|E₁ and E₂ and E₃)  NB. Should be 0.998
Pr(G) ← 0.5
Pr(~G) → 0.5
Pr(E₁ | G) ← 1
Pr(E₁ | ~G) ← 0.45
Pr(G | E₁) → 0.6896551724137931

Pr(~G | E₁) → 0.31034482758620685
Pr(E₂ | E₁ ∩ G) ← 1
Pr(E₂ | E₁ ∩ ~G) ← 0.21
Pr(G | E₁ ∩ E₂) → 0.9136592051164916

Pr(~G | E₁ ∩ E₂) → 0.08634079488350843
Pr(E₃ | E₁ ∩ E₂ ∩ G) ← 1
Pr(E₃ | E₁ ∩ E₂ ∩ ~G) ← 0.017
Pr(G | E₁ ∩ E₂ ∩ E₃) → 0.998396076702777

Example 2.

The following example comes from Figure 14.2 on page 512 in the book Artificial Intelligence: A Modern Approach by Stuart Russell and Peter Norvig.

CommandResults
/* Input probability tables */
Pr(JohnCalls| Alarm) gets 0.90
Pr(JohnCalls|~Alarm) gets 0.05
Pr(MaryCalls| Alarm) gets 0.70
Pr(MaryCalls|~Alarm) gets 0.01
Pr(Alarm| Burglary and  Earthquake) gets 0.95
Pr(Alarm| Burglary and ~Earthquake) gets 0.94
Pr(Alarm|~Burglary and  Earthquake) gets 0.29
Pr(Alarm|~Burglary and ~Earthquake) gets 0.001
Pr(Burglary) gets 0.001
Pr(Earthquake) gets 0.002

/* Query the system */
Pr(JohnCalls, MaryCalls, Alarm, ~Burglary, ~Earthquake)  NB. Should be 0.000628
Pr(Burglary | JohnCalls, MaryCalls)  NB. Should be 0.28

Pr(JohnCalls | Alarm) ← 0.90
Pr(JohnCalls | ~Alarm) ← 0.05
Pr(MaryCalls | Alarm) ← 0.70
Pr(MaryCalls | ~Alarm) ← 0.01
Pr(Alarm | Burglary ∩ Earthquake) ← 0.95
Pr(Alarm | Burglary ∩ ~Earthquake) ← 0.94
Pr(Alarm | Earthquake ∩ ~Burglary) ← 0.29
Pr(Alarm | ~Burglary ∩ ~Earthquake) ← 0.001
Pr(Burglary) ← 0.001
Pr(Earthquake) ← 0.002

Pr(Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake) → 0.0006281112599999999
Pr(Burglary | JohnCalls ∩ MaryCalls) → 0.28417183536439294

In order to see the deductions made by the system in Example 2, use the bayes() command. You'll see the following table:

bayes() →
                                                         key     value
    Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake|     Pr(JohnCalls) * Pr(Alarm ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake | JohnCalls)
       Alarm ∩ MaryCalls ∩ ~Burglary ∩ ~Earthquake|JohnCalls     Pr(MaryCalls | JohnCalls) * Pr(Alarm ∩ ~Burglary ∩ ~Earthquake | JohnCalls ∩ MaryCalls)
       Alarm ∩ ~Burglary ∩ ~Earthquake|JohnCalls ∩ MaryCalls     Pr(Alarm | JohnCalls ∩ MaryCalls) * Pr(~Burglary ∩ ~Earthquake | Alarm ∩ JohnCalls ∩ MaryCalls)
                                                      Alarm|     Pr(Alarm | Burglary ∩ Earthquake) * Pr(Burglary ∩ Earthquake) + Pr(Alarm | Burglary ∩ ~Earthquake) * Pr(Burglary ∩ ~Earthquake) + Pr(Alarm | Earthquake ∩ ~Burglary) * Pr(Earthquake ∩ ~Burglary) + Pr(Alarm | ~Burglary ∩ ~Earthquake) * Pr(~Burglary ∩ ~Earthquake)
                                      Alarm|Alarm ∩ Burglary     1
                                     Alarm|Alarm ∩ JohnCalls     1
                         Alarm|Alarm ∩ JohnCalls ∩ ~Burglary     1
           Alarm|Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake     1
                                     Alarm|Alarm ∩ ~Burglary     1
                       Alarm|Alarm ∩ ~Burglary ∩ ~Earthquake     1
                                              Alarm|Burglary     Pr(Alarm | Burglary ∩ Earthquake) * Pr(Burglary ∩ Earthquake | Burglary) + Pr(Alarm | Burglary ∩ ~Earthquake) * Pr(Burglary ∩ ~Earthquake | Burglary) + Pr(Alarm | Earthquake ∩ ~Burglary) * Pr(Earthquake ∩ ~Burglary | Burglary) + Pr(Alarm | ~Burglary ∩ ~Earthquake) * Pr(~Burglary ∩ ~Earthquake | Burglary)
                                 Alarm|Burglary ∩ Earthquake     0.95
                                  Alarm|Burglary ∩ JohnCalls     Pr(Alarm | Burglary) * Pr(JohnCalls | Alarm ∩ Burglary) / Pr(JohnCalls | Burglary)
                                Alarm|Burglary ∩ ~Earthquake     0.94
                                Alarm|Earthquake ∩ ~Burglary     0.29
                                             Alarm|JohnCalls     Pr(Alarm) * Pr(JohnCalls | Alarm) / Pr(JohnCalls)
                                 Alarm|JohnCalls ∩ MaryCalls     Pr(Alarm | JohnCalls) * Pr(MaryCalls | Alarm ∩ JohnCalls) / Pr(MaryCalls | JohnCalls)
                                             Alarm|~Burglary     Pr(Alarm | Burglary ∩ Earthquake) * Pr(Burglary ∩ Earthquake | ~Burglary) + Pr(Alarm | Burglary ∩ ~Earthquake) * Pr(Burglary ∩ ~Earthquake | ~Burglary) + Pr(Alarm | Earthquake ∩ ~Burglary) * Pr(Earthquake ∩ ~Burglary | ~Burglary) + Pr(Alarm | ~Burglary ∩ ~Earthquake) * Pr(~Burglary ∩ ~Earthquake | ~Burglary)
                               Alarm|~Burglary ∩ ~Earthquake     0.001
                                      Burglary ∩ Earthquake|     Pr(Burglary) * Pr(Earthquake | Burglary)
                              Burglary ∩ Earthquake|Burglary     Pr(Burglary | Burglary) * Pr(Earthquake | Burglary)
                             Burglary ∩ Earthquake|~Burglary     Pr(Burglary | ~Burglary) * Pr(Earthquake | Burglary ∩ ~Burglary)
                                     Burglary ∩ ~Earthquake|     Pr(Burglary) * Pr(~Earthquake | Burglary)
                             Burglary ∩ ~Earthquake|Burglary     Pr(Burglary | Burglary) * Pr(~Earthquake | Burglary)
                            Burglary ∩ ~Earthquake|~Burglary     Pr(Burglary | ~Burglary) * Pr(~Earthquake | Burglary ∩ ~Burglary)
                                                   Burglary|     0.001
                                           Burglary|Burglary     1
                                          Burglary|JohnCalls     Pr(Burglary) * Pr(JohnCalls | Burglary) / Pr(JohnCalls)
                              Burglary|JohnCalls ∩ MaryCalls     Pr(Burglary | JohnCalls) * Pr(MaryCalls | Burglary ∩ JohnCalls) / Pr(MaryCalls | JohnCalls)
                                          Burglary|~Burglary     0
                                     Earthquake ∩ ~Burglary|     Pr(~Burglary) * Pr(Earthquake | ~Burglary)
                             Earthquake ∩ ~Burglary|Burglary     Pr(~Burglary | Burglary) * Pr(Earthquake | Burglary ∩ ~Burglary)
                            Earthquake ∩ ~Burglary|~Burglary     Pr(~Burglary | ~Burglary) * Pr(Earthquake | ~Burglary)
                                                 Earthquake|     0.002
                                         Earthquake|Burglary     Pr(Earthquake)
                             Earthquake|Burglary ∩ ~Burglary     Pr(Earthquake)
                                        Earthquake|~Burglary     1 - Pr(~Earthquake | ~Burglary)
                                                  JohnCalls|     Pr(JohnCalls | Alarm) * Pr(Alarm) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm)
                                             JohnCalls|Alarm     0.90
                                  JohnCalls|Alarm ∩ Burglary     Pr(JohnCalls | Alarm) * Pr(Alarm | Alarm ∩ Burglary) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ Burglary)
                                 JohnCalls|Alarm ∩ ~Burglary     Pr(JohnCalls | Alarm) * Pr(Alarm | Alarm ∩ ~Burglary) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ ~Burglary)
                   JohnCalls|Alarm ∩ ~Burglary ∩ ~Earthquake     Pr(JohnCalls | Alarm) * Pr(Alarm | Alarm ∩ ~Burglary ∩ ~Earthquake) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ ~Burglary ∩ ~Earthquake)
                                          JohnCalls|Burglary     Pr(JohnCalls | Alarm) * Pr(Alarm | Burglary) + Pr(JohnCalls | ~Alarm) * Pr(~Alarm | Burglary)
                                            JohnCalls|~Alarm     0.05
                                             MaryCalls|Alarm     0.70
                                 MaryCalls|Alarm ∩ JohnCalls     Pr(MaryCalls | Alarm) * Pr(Alarm | Alarm ∩ JohnCalls) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ JohnCalls)
                     MaryCalls|Alarm ∩ JohnCalls ∩ ~Burglary     Pr(MaryCalls | Alarm) * Pr(Alarm | Alarm ∩ JohnCalls ∩ ~Burglary) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ JohnCalls ∩ ~Burglary)
       MaryCalls|Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake     Pr(MaryCalls | Alarm) * Pr(Alarm | Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake)
                              MaryCalls|Burglary ∩ JohnCalls     Pr(MaryCalls | Alarm) * Pr(Alarm | Burglary ∩ JohnCalls) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | Burglary ∩ JohnCalls)
                                         MaryCalls|JohnCalls     Pr(MaryCalls | Alarm) * Pr(Alarm | JohnCalls) + Pr(MaryCalls | ~Alarm) * Pr(~Alarm | JohnCalls)
                                            MaryCalls|~Alarm     0.01
                                                     ~Alarm|     1 - Pr(Alarm)
                                     ~Alarm|Alarm ∩ Burglary     0
                                    ~Alarm|Alarm ∩ JohnCalls     0
                        ~Alarm|Alarm ∩ JohnCalls ∩ ~Burglary     0
          ~Alarm|Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake     0
                                    ~Alarm|Alarm ∩ ~Burglary     0
                      ~Alarm|Alarm ∩ ~Burglary ∩ ~Earthquake     0
                                             ~Alarm|Burglary     1 - Pr(Alarm | Burglary)
                                 ~Alarm|Burglary ∩ JohnCalls     1 - Pr(Alarm | Burglary ∩ JohnCalls)
                                            ~Alarm|JohnCalls     1 - Pr(Alarm | JohnCalls)
                                    ~Burglary ∩ ~Earthquake|     Pr(~Burglary) * Pr(~Earthquake | ~Burglary)
       ~Burglary ∩ ~Earthquake|Alarm ∩ JohnCalls ∩ MaryCalls     Pr(~Burglary | Alarm ∩ JohnCalls ∩ MaryCalls) * Pr(~Earthquake | Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary)
                            ~Burglary ∩ ~Earthquake|Burglary     Pr(~Burglary | Burglary) * Pr(~Earthquake | Burglary ∩ ~Burglary)
                           ~Burglary ∩ ~Earthquake|~Burglary     Pr(~Burglary | ~Burglary) * Pr(~Earthquake | ~Burglary)
                                                  ~Burglary|     1 - Pr(Burglary)
                                             ~Burglary|Alarm     Pr(~Burglary) * Pr(Alarm | ~Burglary) / Pr(Alarm)
                                 ~Burglary|Alarm ∩ JohnCalls     Pr(~Burglary | Alarm) * Pr(JohnCalls | Alarm ∩ ~Burglary) / Pr(JohnCalls | Alarm)
                     ~Burglary|Alarm ∩ JohnCalls ∩ MaryCalls     Pr(~Burglary | Alarm ∩ JohnCalls) * Pr(MaryCalls | Alarm ∩ JohnCalls ∩ ~Burglary) / Pr(MaryCalls | Alarm ∩ JohnCalls)
                                          ~Burglary|Burglary     0
                                         ~Burglary|~Burglary     1
       ~Earthquake|Alarm ∩ JohnCalls ∩ MaryCalls ∩ ~Burglary     Pr(~Earthquake | Alarm ∩ JohnCalls ∩ ~Burglary) * Pr(MaryCalls | Alarm ∩ JohnCalls ∩ ~Burglary ∩ ~Earthquake) / Pr(MaryCalls | Alarm ∩ JohnCalls ∩ ~Burglary)
                   ~Earthquake|Alarm ∩ JohnCalls ∩ ~Burglary     Pr(~Earthquake | Alarm ∩ ~Burglary) * Pr(JohnCalls | Alarm ∩ ~Burglary ∩ ~Earthquake) / Pr(JohnCalls | Alarm ∩ ~Burglary)
                               ~Earthquake|Alarm ∩ ~Burglary     Pr(~Earthquake | ~Burglary) * Pr(Alarm | ~Burglary ∩ ~Earthquake) / Pr(Alarm | ~Burglary)
                                        ~Earthquake|Burglary     1 - Pr(Earthquake | Burglary)
                            ~Earthquake|Burglary ∩ ~Burglary     1 - Pr(Earthquake | Burglary ∩ ~Burglary)
                                       ~Earthquake|~Burglary     1 - Pr(Earthquake)

To clear these deductions, use the command bayes(0).

Other stuff

Added the variables colwidth (default 35) and colgap (default 5) which govern the output of the first column in a table. Example: To see the first column of the table displayed by the bayes() command above, I first had to do colwidth gets 60.

There is a function called js that allow you to execute arbitrary JavaScript. It is currently commented-out on line 62 of AST.js because "eval can be harmful." I find it useful for debugging and developing, and you may find it useful too, so grab the source code, uncomment and rebuild using the notes in the README.

The logger variable introduced in the last release now defaults to false. The logger is a way to display info and warnings to the user. It's useful for development and debugging but may not make it to a beta release.

Palamedes 1.0-a44 (20141111)

Conditional probabilities

Started coding support for conditional probabilities and Bayesian reasoning. Still at a very early stage. Right now, the grammar accepts statements like Pr(A|B) where both A and B are conjunctions/interesections of events or their complements. Given some input, the system will attempt to infer output based on Bayes' Rule, the Complement Rule for conditional probabilities, and the Law of total probability.

BEWARE: The system doesn't yet know the Chain Rule for conditional probabilities or rules such as Fienberg and Schervish (1986), or Lindley (1977). In other words: Only the simplest cases work! Here are a few examples:

CommandResults
/* Simple example: infer complement */
Pr(A) ← 0.45
Pr(~A)

/* Bayes' Theorem and Cancer Screening */
/* Source: https://www.youtube.com/watch?v=j2tNxIaGpR4 */
Pr(cancer) gets 1%
Pr(tests_positive|cancer) gets 90%
Pr(tests_positive|~cancer) gets 10% NB. False positive
Pr(cancer|tests_positive)           NB. Should be 1/12 or 0.08333333333333333

/* Turn off logger */
logger gets false

/* Source: http://stattrek.com/probability/bayes-theorem.aspx */
Pr( A ) gets 5/365    NB. It rains 5 days out of the year
Pr( B | A ) gets 90%  NB. When it rains, the weatherman predicts rain 90% of the time
Pr( B | ~A ) gets 10% NB. When it does not rain, the weatherman predicts rain 10% of the time
Pr( A | B )           NB. Probability it rains given forecast for rain. Should be 1/9 or 0.1111111111111111

/* Teaching an Application of Bayes’ Rule for Legal Decision-Making: Measuring the Strength of Evidence */
/* Source: http://www.amstat.org/publications/jse/v22n1/satake.pdf */
Pr(guilt) ← 50%              NB. Prior probability of guilt
Pr(~guilt)                   NB. Prior probability of innocence. Should be 0.5
Pr(evidence | guilt) ← 100%  NB. Probability of evidence based on presumption of guilt
Pr(evidence | ~guilt) ← 45%  NB. Probability of evidence based on presumption of innocence
Pr(guilt | evidence)         NB. Posterior probability of guilt after seeing the evidence. Should be  0.69
Pr(~guilt | evidence)        NB. Posterior probability of innocence after seeing the evidence. Should be 0.31
Pr(A) ← 0.45
NB. Applied complement rule: Pr(~A) = 1 - Pr(A)
Pr(~A) → 0.55

Pr(cancer) ← 0.01
Pr(tests_positive | cancer) ← 0.9
Pr(tests_positive | ~cancer) ← 0.1
NB. Trying to apply Bayes' rule to: Pr(cancer | tests_positive)
NB. Checking inverse: Pr(tests_positive | cancer)
NB. Checking inverse prior: Pr(cancer)
NB. Checking prior: Pr(tests_positive)
NB. Trying to apply LOTP to: Pr(tests_positive)
NB. Found LOTP candidate Pab: Pr(tests_positive | cancer)
NB. Checking Pb: Pr(cancer)
NB. Checking PNb: Pr(~cancer)
NB. Checking prior: Pr(tests_positive | ~cancer)
NB. Applied complement rule: Pr(~cancer) = 1 - Pr(cancer)
NB. Applied LOTP to candidate: Pr(tests_positive) = Pr(tests_positive | cancer) * Pr(cancer) + Pr(tests_positive | ~cancer) * Pr(~cancer)
NB. Applied Bayes' rule: Pr(cancer | tests_positive) = Pr(tests_positive | cancer) * Pr(cancer) / Pr(tests_positive)
NB. Trying to apply LOTP to: Pr(tests_positive)
NB. Found LOTP candidate Pab: Pr(tests_positive | cancer)
NB. Checking Pb: Pr(cancer)
NB. Checking PNb: Pr(~cancer)
NB. Checking prior: Pr(tests_positive | ~cancer)
NB. Applied complement rule: Pr(~cancer) = 1 - Pr(cancer)
NB. Applied LOTP to candidate: Pr(tests_positive) = Pr(tests_positive | cancer) * Pr(cancer) + Pr(tests_positive | ~cancer) * Pr(~cancer)
NB. Applied complement rule: Pr(~cancer) = 1 - Pr(cancer)
Pr(cancer | tests_positive) → 0.08333333333333333

logger ← false

Pr(A) ← 0.0136986301369863
Pr(B | A) ← 0.9
Pr(B | ~A) ← 0.1
Pr(A | B) → 0.1111111111111111

Pr(guilt) ← 0.5
Pr(~guilt) → 0.5
Pr(evidence | guilt) ← 1
Pr(evidence | ~guilt) ← 0.45
Pr(guilt | evidence) → 0.6896551724137931
Pr(~guilt | evidence) → 0.3103448275862069

Note that the system tells you how it's making its inferences. You can turn this behavior off by setting the variable logger to false as shown above.

Other stuff

Palamedes 1.0-a43 (20141110)

l@ubuntu:~/work/Palamedes$ node cli.js
Dice>  binom 3, 1/6 |> prob |> table
table(prob(binom 3, 0.16666666666666666)) →
                 key     value
                   0     0.5787037037037038
                   1     0.34722222222222227
                   2     0.06944444444444445
                   3     0.004629629629629629

Dice>  binom 3, 1/6 |> prob |> sum
sum(prob(binom 3, 0.16666666666666666)) → 1
GoodBad
Dice> 1+2+3+4  NB. This is 10          
1 + 2 + 3 + 4 → 10
Dice> 1*2*3*4  NB. This is 24
1 * 2 * 3 * 4 → 24
Dice> 1+2+3+4  nb. This is 10
1 + 2 + 3 + plus(plus(plus(plus(4,nb.),This),is),10) → 20
Dice> 1*2*3*4  nb. This is 24
1 * 2 * 3 * plus(plus(plus(plus(4,nb.),This),is),24) → 168
Dice> matrixform()
matrixform() → OK
Dice> I(3)
I(3) →
[[ 1, 0, 0 ], ...          
 [ 0, 1, 0 ], ...
 [ 0, 0, 1 ]]
Dice> row_sep set unquote(","+nl+" ") /* Get rid of othe dots after each row */
row_sep ↜ unquote("," + nl + " ") → ,

Dice> I(3)
I(3) →
[[ 1, 0, 0 ],
 [ 0, 1, 0 ],
 [ 0, 0, 1 ]]
Dice> tableform()
tableform() → OK
Dice> I(3)
I(3) →
| 1 | 0 | 0 |          
| 0 | 1 | 0 |
| 0 | 0 | 1 |
Dice> col_sep set unquote("") /* Get rid of the pipes between columns */
col_sep ↜ unquote("") →
Dice> I(3)
I(3) →
| 1 0 0 |
| 0 1 0 |
| 0 0 1 |

Palamedes 1.0-a42 (20141107)

Transpose operator

Raising a matrix to the power of T now performs matrix transpose. This is more cosmetically appealing alternative to the trans function:

Dice> M gets [[1,5],[-2,3]]
M ← [[1, 5], [-2, 3]]
Dice> M
M →
[[  1, 5 ], ...
 [ -2, 3 ]]
Dice> M^T
M ^ T →
[[ 1, -2 ], ...
 [ 5,  3 ]]

Diag

The diag function now takes an array and constructs a matrix with it on the diagonal, everything else zeroed out:

Dice> diag [5,7,2]
diag([5, 7, 2]) →
[[ 5, 0, 0 ], ...
 [ 0, 7, 0 ], ...
 [ 0, 0, 2 ]]

diag still takes a matrix and returns a matrix with the same diagonal and everything else zeroed out.

TMTOWTDI: Another way to create a 3x3 identity matrix:

Dice> diag(3#1)
diag(3 # 1) →
[[ 1, 0, 0 ], ...
 [ 0, 1, 0 ], ...
 [ 0, 0, 1 ]]

This would be a good place to compile all the ways we have so far of creating a 3x3 identy matrix:

/* 1 */ I(3) /* Best way to do it!!! */
/* 2 */ diag(3#1)
/* 3 */ matrix(lookup [r=c, 1, ⊤, 0],3,3)
/* 4 */ resize([3, 3],[1] ⊎ (3 # 0))
/* 5 */ M^0 /* M any 3x3 matrix e.g., */ @ M ← [[1,2,3],[0,4,5],[1,0,6]]
/* 6 */ M × M^-1 //rat /* M some invertible 3x3 matrix. Worst way to do it! */

Lisp list functions

Added car, cdr and cons functions, a la Lisp.

Dice> cons(1,2)
cons(1,2) → [1, 2]
Dice> cons(1,[2,3])
cons(1,[2, 3]) → [1, 2, 3]
Dice> car([1,2,3])
car([1, 2, 3]) → 1
Dice> cdr([1,2,3])
cdr([1, 2, 3]) → [2, 3]

Constants

Predefined the Dirac constant as if you had typed ℏ ← 6.62606957e-34 / (2 * π)

Juxtaposition operator

Added a juxtaposition operator, which operates on space-separated numerals, variables or arrays when no operator is given. It works on 2 inputs at a time, but automatically folds left over more than two inputs. The juxtaposition operator is useful when you just want to add a bunch of numbers together and don't want to type in a bunch of plus signs. The output is ugly, since Palamedes explains how its interpreting your input. But the input requires fewer keystrokes, and it is easy to ignore the output up to the final and just read the final result:

Dice> 9 32 73 53 17 26 99 71 1 3 22 58 59 5 85 11 99 37 24 2
plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(plus(9,32),73),53),17),26),99),71),1),3),22),58),59),5),85),11),99),37),24),2) → 786

That's the same as either of these, which require more keystrokes:

Dice> 9 + 32 + 73 + 53 + 17 + 26 + 99 + 71 + 1 + 3 + 22 + 58 + 59 + 5 + 85 + 11 + 99 + 37 + 24 + 2
9 + 32 + 73 + 53 + 17 + 26 + 99 + 71 + 1 + 3 + 22 + 58 + 59 + 5 + 85 + 11 + 99 + 37 + 24 + 2 → 786
Dice> sum [9, 32, 73, 53, 17, 26, 99, 71, 1, 3, 22, 58, 59, 5, 85, 11, 99, 37, 24, 2]
sum([9, 32, 73, 53, 17, 26, 99, 71, 1, 3, 22, 58, 59, 5, 85, 11, 99, 37, 24, 2]) → 786

By default the juxtaposition operator is set to plus. Change it to any 2-argument-or-greater function name (e.g., plus, times, min, min2, max, max2, minus, div, mod, lcm2, pow, list, cons, and2, or2) sans quotes as shown in the following example:

Dice> 2 3 4 5
plus(plus(plus(2,3),4),5) → 14
Dice> 'juxtaposition
juxtaposition → plus
Dice> juxtaposition set unquote "pow"
juxtaposition ↜ unquote("pow") → pow
Dice> 2 3 4 5
pow(pow(pow(2,3),4),5) → 1152921504606847000
Dice> 2^3^4^5 /* same as this */
2 ^ 3 ^ 4 ^ 5 → 1152921504606847000
Dice> juxtaposition set unquote "times"
juxtaposition ↜ unquote("times") → times
Dice> 2 3 4 5
times(times(times(2,3),4),5) → 120
Dice> 2 * 3 * 4 * 5 /* same as this or prod[2..5] or 5! */
2 * 3 * 4 * 5 → 120
Dice> juxtaposition set unquote "max"
juxtaposition ↜ unquote("max") → max
Dice> 2 3 4 5
max(max(max(2,3),4),5) → 5
Dice> juxtaposition set unquote "min"
juxtaposition ↜ unquote("min") → min
Dice> 2 3 4 5
min(min(min(2,3),4),5) → 2
Dice> juxtaposition set unquote "list"
juxtaposition ↜ unquote("list") → list
Dice> 2 3 4 5
list(list(list(2,3),4),5) → [2, 3, 4, 5]

You can use variables or arrays with the juxtaposition operator too. Here we use the last variable to continue a sum:

Dice> 1 2 3 4
plus(plus(plus(1,2),3),4) → 10
Dice> last 3 1
plus(plus(last,3),1) → 14

Beware: But you can't use juxtaposition with dice throws! At least not yet.

Dice> d4 d6 d8 d12 d20
/* ERROR! */

Beware: If the first variable is the same as a function name it applies the function instead! First we add 1 to N. The variable N is initially 0. But switching the order of N and 1 applies the normal random function to 1, returning a variate with mean 1 and variance 1:

Dice> 1 N
plus(1,N) → 1
Dice> N
N → 0
Dice> N 1
N(1) → 1.1506646051606864

Beware of precedence rules---The juxtaposition operator is assumed where no operator is specified, and it takes precedence over other operators, but not other functions. Juxtaposition folds to the left. Using a function to the left is OK, but using a function on the right produces an error.

Dice> 1 2^3
plus(1,2) ^ 3 → 27
Dice> 1 2^3 1+3
plus(1,2) ^ plus(3,1) + 3 → 84
Dice> min(2,3) 4
plus(min(2,3),4) → 6
Dice> 2 min(3,4)
/* ERROR! */

Using plus as the default juxtaposition operator behaves "correctly" with negative values:

Dice> 1-3
plus(1,-3) → -2
Dice> 1 -3
plus(1,-3) → -2
Dice> 1+-3 /* not juxtaposition! ordinary addition */
1 + -3 → -2
Dice> 1 - 3 /* not juxtaposition! ordinary subtraction */
1 - 3 → -2
Dice> 1--3 /* not juxtaposition! subtracting a negative */
1 - -3 → 4

Same examples with a variable:

Dice> b set 7
b ↜ 7 → 7
Dice> b-3
plus(b,-3) → 4
Dice> b -3
plus(b,-3) → 4
Dice> b - 3 /* not juxtaposition! ordinary subtraction */
b - 3 → 4
Dice> b+-3 /* not juxtaposition! ordinary addition */
b + -3 → 4

Lexer changes

Aliased ¼ for 0.25, ½ for 0.5 and ¾ for 0.75

Identifiers can now use either:

Extended Example: Variance-Covariance Matrix and Correlation Matrix

Palamedes doesn't have any builtin functions for computing covariances or correlations. That's because its matrix capabilities are so good, it's rather trivial just to apply the definitions. See the following links containing background info on these calculations:

So here is the same example redone in Palamedes including the conversion from a covariance matrix to correlation matrix:

/* Input */
N ← 5
Math ← [90, 90, 60, 60, 30]
English ← [60, 90, 60, 60, 30]
Art ← [90, 30, 60, 90, 30]
Scores ← [Math, English, Art]
xbar ← [mean Scores]^T
ones ← [N#1]
Covariance ← N^-1 * (Scores - xbar × ones) × (Scores - xbar × ones)^T; Covariance
Correlation ← sqrt(diag(Covariance))^-1 × Covariance × sqrt(diag(Covariance))^-1; Correlation//round
/* Output */
N ← 5
Math ← [90, 90, 60, 60, 30]
English ← [60, 90, 60, 60, 30]
Art ← [90, 30, 60, 90, 30]
Scores ← [Math, English, Art]
xbar ← [mean(Scores)]^T
ones ← [N # 1]
Covariance ← N ^ -1 * (Scores - xbar × ones) × (Scores - xbar × ones)^T
Covariance →
[[ 504, 360, 180 ], ...
 [ 360, 360,   0 ], ...
 [ 180,   0, 720 ]]
Correlation ← sqrt(diag(Covariance)) ^ -1 × Covariance × sqrt(diag(Covariance)) ^ -1
round(Correlation) →
[[    1, 0.85, 0.3 ], ...
 [ 0.85,    1,   0 ], ...
 [  0.3,    0,   1 ]]

Note: If you apply var or sd to an array or matrix, it computes the sample standard deviation. So if you need the population statistics, you must use a correction factor. For example, if you wanted to convert the Correlation matrix back to the population Variance-Covariance matrix, do this:

factor ← sqrt((N - 1)/N)
sd ← factor * sd Scores
diag(sd) × Correlation × diag(sd)//round

Note: The whitespace space around the minus sign in N - 1 is needed. It's needed here because N does double duty as a random variate function, so N-1 would actually compute N(-1,1). If we had used lowercase n, which isn't a function name, then we could write n-1 but this would then implicitly call the juxtaposition operator. If it were the default value of plus that wouldn't be a problem in this case. The reason for all this complexity is that without preceding space, the minus sign is part of the number: -1 is a negative number, not a subtraction.

But if you really wanted the sample Covariance matrix, you could simply write:

sd ← sd Scores
diag(sd) × Correlation × diag(sd)

Example: Palamedes plays dreidel

/* Happy Hanukkah! (Sunset Tuesday, December 16) */
sides gets {"yiddish": ["nisht", "halb", "gants", "shtel ayn"], "english": ["nothing", "half", "all", "put in"], "hebrew": ["נ","ה","ג","ש"]}
dreidel gets quote(choose(sides after "yiddish"))
dreidel; dreidel; dreidel
3 # dreidel
100000 # dreidel |> freq |> round

Palamedes 1.0-a41 (20141030)

Grammar changes

Lexer changes

AST changes

Extended Example

There is an example of computing stats for a simplified version of Chainmail combat using two completely different methods:

The following simplifications to the rules were made:

Both sides simply face each other and slug it out until one or both sides has no troops left (an absorption state).

Both methods compute the expected number of turns to absorption, variance on the number of turns to absorption, and other information. But the Absorbing Markov chain model computes exact results. The Monte Carlo approximation agrees with these exact results:

For complete details, including the Palamedes program listings, follow these links:

Palamedes 1.0-a40 (20141029)

Added tensor(expr,q,m,n) tensor constructor to create m-by-n-by-p tensor. It varies p (flor "planes") over 0..q-1, r (for "rows") over 0..m-1 and c (for "columns") over 0..n-1and evaluates expr at each point to get the value of the new tensor T[p,r,c]. The expression expr should be written in terms of the variables p, r and c. The values in these variables will not be permanently effected---they are saved and later restored.

Added matrix(expr,m,n) matrix constructor to create an m-by-n matrix. It varies r (for "rows") over 0..m-1 and c (for "columns") over 0..n-1and evaluates expr at each point to get the value of the new matrix M[r,c]. The expression expr should be written in terms of the variables r and c. The values in these variables will not be permanently effected---they are saved and later restored. Example: Another way to create a 3-by-3 identity matrix...

Dice> matrix(lookup [r=c, 1, true, 0],3,3)
matrix(lookup [r = c, 1, true, 0],3,3) →
[[ 1, 0, 0 ], ...
 [ 0, 1, 0 ], ...
 [ 0, 0, 1 ]]

Added array(expr,m) matrix constructor to create an array of length m. It varies i (for "index") over 0..m-1 and evaluates expr at each point to get the value of the new array A[i]. The expression expr should be written in terms of the variables i. The values in this variable will not be permanently effected---it is saved and later restored. Example:

Dice> array(i^2+4,5)
array(i ^ 2 + 4,5) → [4, 5, 8, 13, 20]

Added update(T,value,p,r,c), update(M,value,r,c) and update(A,value,i) functions. Updates the given object (tensor, matrix or array) with the given value at the given point, e.g., update(M,3,r,c) sets M[r,c] = value. But update returns a new object---the old value of the object (T, M or A) is not effected. Example:

Dice> update(I,9,0,1)
update(I,9,0,1) →
[[ 1, 9 ], ...
 [ 0, 1 ]]

Note that this doesn't really change the value of I unless you use an assignment, e.g., I set update(I,9,0,1).

Added I(n) function to create an n-by-n identity matrix. Another way to create a 3-by-3 identity matrix:

Dice> I(3)
I(3) →
[[ 1, 0, 0 ], ...
 [ 0, 1, 0 ], ...
 [ 0, 0, 1 ]]

Added diag(M) function to create an diagonal matrix from M. Off-diagonal elements are zeroed out. The Original matrix M is uneffected.

Dice> diag(matrix(2^(r^2+2*c+1),3,3))
diag(matrix(2 ^ (r ^ 2 + 2 * c + 1),3,3)) →
[[ 2,  0,   0 ], ...
 [ 0, 16,   0 ], ...
 [ 0,  0, 512 ]]

Modified Tokenizer so that Unicode subscripts and superscripts are valid chracaters in identifiers, e.g., σ² is a valid variable name, and yₜ ← [0..10]; yⁱ ← e ^ yₜ is a valid expression. Note: By themselves d, f, k, x, z are not valid variable names -- they are dice operators. But x₀, x₁, x², etc. are now valid names.

Modified Tokenizer: 1/6 is lexed as the NUMBER 0.1666666... whereas 1 / 6 is lexed as the EXPRESSION 1 '/' 6. This is so I can write expressions like NB 5, 1/6 instead of NB 5, (1/6). I still need to write NB 5, (1/6^3+1/4). Note that 2/6^3 is (2/6)^3 but 2 / 6^3 is 2/(6^3). I'm not sure if I will keep this behavior or simply rewrite all the discrete probability distribution operators NB, geom, pois and so on as functions, in which case I would always need to use outer parens around the arg list: NB(5, 1/6) but could then write NB(5, 1/6^3+1/4) without parens around the second arg. In short, this behavior is experimental and perhaps temporary. This behavior will be stabilized when the code moves from the alpha to the beta stage.

Palamedes 1.0-a39 (20141007)

Fixed a bug in prob(max(a,b)) and prob(min(a,b)) that could result in bogus results when arguments a,b not iid.

Palamedes 1.0-a38 (20141007)

Calling mean[d6,d10,d20] returns a scalar with the mean of the array containing elements sampled randomly from the d6, d10, and d20 dice distributions, so you'll get a different result each time. But mean(d6,d10,d20) returns an array with the means of the d6, d10 and d20 dice distributions respectively. N.B. the difference is that [d6,d10,d20] is an array whereas (d6,d10,d20) is a tuple. Arrays are first class citizens in the Palamedes language. But tuples are just part of the grammar for function calls, only understood by the parser.

Additionally, mean[prob(d6),prob(d10),prob(d20)] returns an array of means. And mean([[1, 2, 3], [1, 3, 5], [0, 10, 20]]) returns an array with the means of each row in the given matrix.

These tricks only works with the functions mean, sd and var functions so far.

Added pair function to turn a pair of arrays into an array of pairs. Note that this...

Dice> pair([1..6],[1..6])
pair([1..6],[1..6]) →
[[ 1, 1 ], ...
 [ 2, 2 ], ...
 [ 3, 3 ], ...
 [ 4, 4 ], ...
 [ 5, 5 ], ...
 [ 6, 6 ]]

is the similar to this...

Dice> resize([6,2], [1..6] zip [1..6])
resize([6, 2],[1..6] ⇈ [1..6]) →
[[ 1, 1 ], ...
 [ 2, 2 ], ...
 [ 3, 3 ], ...
 [ 4, 4 ], ...
 [ 5, 5 ], ...
 [ 6, 6 ]]

...except that resize will repeat its inputs as often as necessary to create the matrix of the given size while pair will not. Also resize is more general and can create matrices of any size whereas pair(a,b) only creates Nx2 matrices where N=min(count a, count b).

Palamedes 1.0-a37 (20141006)

Previously, min and max functions only computed probabilities for samples of iid random variables. Now they work on tuples or arrays of independent rv's, even if they're not identical, e.g., prob(max(2d6,3d6))//rat//table or stats(max(4d6,5d6+1,7d6+1))//table.

Added var function to compute variance. Note that var [1..6] computes the sample variance of the given array while var(d6) computes the population variance of the d6 dice distribution. Added var to the list of descriptive statistics returned by stats.

Palamedes 1.0-a36 (20140915)

Exact probabilities for roll-and-keep dice mechanics

Example node.js command-line session using the Palamedes module:

Dice> prob(4d6k3)//table
table(prob(4d6k3)) →
                 key     value
                   3     0.0007716049382716049
                   4     0.0030864197530864196
                   5     0.007716049382716049
                   6     0.016203703703703703
                   7     0.029320987654320986
                   8     0.047839506172839504
                   9     0.07021604938271606
                  10     0.0941358024691358
                  11     0.11419753086419752
                  12     0.12885802469135801
                  13     0.13271604938271606
                  14     0.12345679012345678
                  15     0.10108024691358025
                  16     0.07253086419753087
                  17     0.041666666666666664
                  18     0.016203703703703703

Dice> 4d6k3 |> prob |> rat |> table
table(rat(prob(4d6k3))) →
                 key     value
                   3     1/1296
                   4     1/324
                   5     5/648
                   6     7/432
                   7     19/648
                   8     31/648
                   9     91/1296
                  10     61/648
                  11     37/324
                  12     167/1296
                  13     43/324
                  14     10/81
                  15     131/1296
                  16     47/648
                  17     1/24
                  18     7/432

Dice> prob(4d6k3)//stats//table
table(stats(prob(4d6k3))) →
                 key     value
               count     16
                 max     18
                mean     12.2445987654321
              median     12
                 min     3
                mode     13
                  sd     2.846844445311498

Dice> prob(4d6k3)//sum
sum(prob(4d6k3)) → 1

Percentages

The lexer now translates 99% into 0.99. But that only works with numeric literals: variable% is a parse error, while d% still rolls a percentile die (1-100), z% (0-99) and x% an exploding percentile (add and reroll on 100). Additionally, the variables bp and bps are now predefined as 10^-4, i.e., basis points, a percent of a percent. Added percent function to format output as percentage; an optional precision may be specified. Examples:

Dice> 12%
0.12 → 0.12

Dice> variable%
Caught exception executing 'variable%': ...

Dice> d%; 10000#d%//min; 10000#d%//max
d% → 99
min(10000 # d%) → 1
max(10000 # d%) → 100

Dice> z%; 10000#z%//min; 10000#z%//max
z% → 69
min(10000 # z%) → 0
max(10000 # z%) → 99

Dice> x%; 10000#x%//min; 10000#x%//max
x% → 32
min(10000 # x%) → 1
max(10000 # x%) → 299

Dice> LIBOR_1_yr gets 0.56%
LIBOR_1_yr ← 0.005600000000000001
Dice> LIBOR_1_yr + 50*bps
LIBOR_1_yr + 50 * bps → 0.010600000000000002

Dice> percent(0.0666,[0..3]) /* format with 0 to 3 digits after decimal */
percent(0.0666,[0..3]) → [7%, 6.7%, 6.66%, 6.660%]
Dice> 0.0666//percent /* default precision is two digits after decimal */
percent(0.0666) → 6.66%

Negative binomial distribution operator: NB

If the random variable S is distributed as NB r, p: r is number of failures until the experiment is stopped, and p is the success probability in each experiment. The r.v. S is the number of successes before r failures.

NB r, p generates a random variate from this distribution. size # NB r, p generates a sample of size size.

prob(NB r, p) or NB r, p//prob generates a JSON object with the probabilities of S successes. You can further format this expression with rat or human or percent, and then display it in a table with table.

stats(NB r, p) or NB r, p//stats generates a JSON object with summary statistics. Again, you can further format this expression with rat or human or percent, and then display it in a table with table.

Example: A door-to-door salesman has a 40% chance of success. What is the chance of n successes before 5 failures? We can make a table of probabilities, expressed as percents, as follows:

Dice> NB 5, 40%//prob//percent//table
table(percent(prob(NB 5, 0.4))) →
                 key     value
                   0     7.78%
                   1     15.55%
                   2     18.66%
                   3     17.42%
                   4     13.93%
                   5     10.03%
                   6     6.69%
                   7     4.20%
                   8     2.52%
                   9     1.46%
                  10     0.82%
                  11     0.45%
                  12     0.24%
                  13     0.12%
                  14     0.06%
                  15     0.03%
                  16     0.02%
                  17     0.01%

The table contains additional 0.00% values not shown here. They are actually greater than zero, however, the percent formatting function rounded them down to zero.

Poisson distribution operator: pois

Example: The average number of homes sold by the Acme Realty company is 2 homes per day. What is the probability that exactly 3 homes will be sold tomorrow?

Dice> prob(pois 2) after 3//percent
percent(prob(pois 2) ∘ 3) → 18.04%

Generate 10 random variates from this distribution:

Dice> 10#pois 2
10 # pois 2 → [3, 1, 5, 0, 2, 1, 2, 3, 1, 4]

Compute summary statistics for this distribution:

Dice> pois 2//stats//human//table
table(human(stats(pois 2))) →
                 key     value
               count     17
                 max     16
                mean     2
              median     2
                 min     0
                mode     1,2
                  sd     √2

Annuities and Perpetuities

Annuity-immediate functions for present value and future value: a(n,i)=(1-(1+i)^-n)/i and s(n,i)=((1+i)^n-1)/i. And annuity-due functions a_due(n,i)=(1+i)*a(n,i)=a(n-1,i)+1 and s_due(n,i)=(1+i)*s(n,i)=s(n+1,i)-1. Perpetuties: a(∞,i)=1/i and a_due(∞,i)=1/discount where discount=i/(1+i). See Annuities and Perpetuities for more details.

Example. List the present values of a 5 year annuity immediate with nominal annual interest rates at 6%, 12%, and 24% and monthly payments of $100:

Dice> 100*a(5*12,[6%,12%,24%]/12)//fix//commify
commify(fix(100 * a(5 * 12,[0.06, 0.12, 0.24] / 12))) → [5,172.56, 4,495.50, 3,476.09]

Example. List the present values of 1, 5 and 10 year annuities immediate with a nominal annual interest rates of 12% and monthly payments of $100:

Dice> 100*a([1,5,10]*12,12%/12)//fix//commify
commify(fix(100 * a([1, 5, 10] * 12,0.12 / 12))) → [1,125.51, 4,495.50, 6,970.05]

Note that if you input two arrays such as a([1,5,10]*12,[6%,12%,24%]/12), then the 1-year annuity gets paired with the 6% interest rate, the 5-year with 12% and the 10-year with 24%. If instead you want to see a 3x3 matrix with all possible matchups of the three terms with the three rates, use the outer product function as follows. Note that the 100*... payment size multiplies the outer function, not the a function. That's because at present the first argument to outer must be a function rather than an expression. In the output matrix, years are rows and rates are columns:

Dice> 100*outer(a, [1, 5, 10]*12, [6%,12%,24%]/12)//fix//commify
commify(fix(100 * outer(a,[1, 5, 10] * 12,[0.06, 0.12, 0.24] / 12))) →
[[ 1,161.89, 1,125.51, 1,057.53 ],
 [ 5,172.56, 4,495.50, 3,476.09 ],
 [ 9,007.35, 6,970.05, 4,535.54 ]]

You may input the unicode symbols ä and s̈ for the present and future values of annuities due, but the lexer changes ä to a_due and s̈ to s_due to avoid potential I/O issues with combining diacritics. Example:

Dice> s̈(84,0.0075)*100//fix//commify
commify(fix(s_due(84,0.0075) * 100)) → 11,730.01

If you input or inf as the term for a present value, it computes a the value of a perpetuity. Example:

Dice> ä(∞,6%)
a_due(∞,0.06) → 17.666666666666668
Dice> discount ← 6%/(1+6%)
discount ← 0.06 / (1 + 0.06)
Dice> a_due(∞,6%)=1/discount
a_due(∞,0.06) = 1 / discount → true

Note that using I and i for interest rates will overwrite their definitions for complex numbers and matrices. You can save the old values to temporary variables by doing oldi set i; oldI set I and restore them by doing the inverse.

Numerical differentiation and the Newton–Raphson method

Find the first derivative of the function 3*y^2 - 6 with respect to y at the point y=2:

Dice> deriv(3*y^2 - 6, y, 2, 1)
deriv(3 * y ^ 2 - 6,y,2,1) → 12.00000004046644

The function deriv takes 4 arguments:

deriv(3*y^2 - 6, y, 2, 1)
       ^         ^  ^  ^
       |         |  |  |__ nth derivative, n can be 1 to 4, first derivative in this case
       |         |  |_______ point at which we're taking derivative, y = 2
       |         |____________ variable wrt we're taking derivative
       |________________________ expression we're taking derivative of

Remember x means exploding die roll and z means zero-biased die roll, so do not use x or z as variable names!!!. Don't use d, f or k either, because they're part of dice roll syntax also.

We use a 9-point numerical technique to approximate the derivative. For a description of how this is done, read the articles on Numerical differentiation and Finite difference coefficient.

The function solve(expression,variable,starting_guess) tries to solve for the value of variable that makes the equation expression = 0 true using the given starting guess. The Newton–Raphson method is used. This in turn uses the deriv function just mentioned. The starting_guess should be chosen as close to the solution as possible, and it should not be an extremal point (i.e., a point where the expression has derivative zero). At most, one solution close to the starting guess will be found---this function will not find all solutions. Examples:

Approximate the square root of 2 by solving the formula y^2 - 2 = 0 for y. Remember not to enter the = 0 part. Then compare this appromination to JavaScript's value of Math.sqrt(2).

Dice> solve(y^2 - 2, y, 1)
solve(y ^ 2 - 2,y,1) → 1.414213562373095
Dice> abs(last - sqrt(2))
abs(last - sqrt(2)) → 2.220446049250313e-16

Note that the difference ε = 2.22e-16 is Machine epsilon, i.e., the smallest possible roundoff error---anything smaller added to 1 is just 1. Here is a node.js session which illustrates this:

> ε=2.22e-16
2.22e-16
> 1+ε
1.0000000000000002
> 1+ε/2
1

I put $1,000 at the end of each year in a bond that gets me 6% per year. How many years must I do this for the investment to have a present value of $4,212.36? Start with a guess of 3 years...

Dice> solve(1000*a(y,6%) - 4212.36, y, 3)//round
round(solve(1000 * a(y,0.06) - 4212.36,y,3)) → 5

I put $1,000 at the end of each year in a bond that for 5 years. What's the rate for the investment to have a present value of $4,212.36? Start with a guess of 3%...

Dice> solve(1000*a(5,y) - 4212.36, y, 3%)//percent
percent(solve(1000 * a(5,y) - 4212.36,y,0.03)) → 6.00%

Fixed: Matrix inverse/determinant algorithm

For matrix inversion/determinant, I had taken an algorithm from a journal article; it was supposed to be fast and efficient. However, it turned out unreliable and produced incorrect results in many cases, e.g., when there were zeros on the diagonal.

So I wrote a new matrix inversion/determinant function from scratch based on good-old Gauss-Jordan elimination. It has one optimization: it uses partial pivoting (exchanges rows to move the entry with the largest absolute value to the pivot position), in order to decrease JavaScript rounding error.

Anyway, this should fix any bugs encountered with the old matrix inversion/determinant code.

Fixed: 1-argument listable functions not threading over tensors

A bug in the internal r_op1 function for threading over tensors didn't return a result, e.g., in croots(3,c(1,0))//cstr the cstr function wouldn't return a result, so the GUI reports a result of undefined. This has been fixed.

Matrix output

Added ellipses to ends of lines in multi-line matrix output, to make it easier to copy and paste them back in as inputs.

Complex number updates

Updated cstr function to ouput a complex number in complex cartesian notation 2*I - 3*i//cstr yields c(2, -3). Note that cstr implicitly calls human to format numbers.

Added polar notation operator for complex numbers: r∠ϕ or r polar ϕ. Note that if either r (or ϕ) is not a number or an identifier, it must be wrapped in parens, e.g.,

Dice> 1 ∠ (pi/2)//human
human(1 ∠ (π / 2)) →
[[ 0, -1 ], ...
 [ 1,  0 ]]

Dice> 1 ∠ (pi/2)//cstr
cstr(1 ∠ (π / 2)) → c(0, 1)

Added cpolar function to ouput a complex number in polar notation. Note that cpolar implicitly calls human to format numbers. Example:

Dice> c(√2/2, √2/2)//cpolar
cpolar(c(√2 / 2,√2 / 2)) → 1 ∠ (1/4*π)

croots(n,z): nth roots of the complex number z.

/* The three third roots of 1 */
Dice> croots(3,c(1,0))//cstr
cstr(croots(3,c(1,0))) → [c(1, 0), c(-1/2, √3/2), c(-1/2, -√3/2)]

cexp function computes the complex exponential, e.g.,

Dice> cexp(c(2,pi/4))//cpolar
cpolar(cexp(c(2,π / 4))) → (e^2) ∠ (1/4*π)

As yet, e^c(r,ϕ) does not produce e^r*(cos ϕ + i sin ϕ). Instead it just computes elementwise expontiation of the matrix representation of c(r,ϕ). In the near future, I may alter the code so that e^c(r,ϕ) does complex exponentiation and e^matrix does matrix exponentiation---I'm debating whether elementwise exponentiation is more useful. In the meantime, call the cexp function.

cln(z,k): Complex logarithm. Unlike logarithms of real numbers, a complex number r∠ϕ has an infinite number of natural logs which correspond to the argument ϕ±2πk, where k is any integer. Therefore, you must specify k as the second parameter. E.g., cln(i,0)

/* Several branches of the natural logarithm of i calculated at once */
Dice> cln(i,[0..2])//cstr
cstr(cln(i,[0..2])) → [c(0, 1/2*π), c(0, 5/2*π), c(0, 9/2*π)]

Palamedes 1.0-a35 (20140909)

Forward pipe operator

Added F#'s forward pipe operator expression |> function. Gave it the name pipe, the single unicode character , and the alias // because it works like Mathematica's postfix function application ("slash slash") operator: expression // function, namely it yields function(expression). This can be very useful when you want to focus on an important calculation (the expression in this case), and push the formatting (the function in this case) out of the way. For example, 10*atan(1/7) + 4*atan(3/79) |> human first does a computation (the LHS expression) and then makes the result human-readable with the human function. The forward pipe operator has two restrictions:

In the following example, we compute the probability mass function of rolling 3d6. This normally returns a JSON object in which the values are floating-point decimals. So we use the forward pipe operator first with the rat function to convert the decimals to rationals and second with the table function to convert the JSON object to a tabular format.

Dice> prob(3d6) |> rat |> table
table(rat(prob (3d6))) →
                 key     value
                   3     1/216
                   4     1/72
                   5     1/36
                   6     5/108
                   7     5/72
                   8     7/72
                   9     25/216
                  10     1/8
                  11     1/8
                  12     25/216
                  13     7/72
                  14     5/72
                  15     5/108
                  16     1/36
                  17     1/72
                  18     1/216

You could have also written this 3d6 |> prob |> rat |> table or 3d6//prob//rat//table.

Scientific notation

Examples:

Dice> 1e+6
1e+6 → 1000000
Dice> 1e6
1e6 → 1000000
Dice> 1e-6
1e-6 → 0.000001
Dice> sci(1000000)
sci(1000000) → 1e+6
Dice> 1000//sci
sci(1000) → 1e+3
Dice> 1E6//commify
commify(1E6) → 1,000,000

Complex numbers

Since matrix math is already implemented, we decided to implement complex math via 2x2 rotation matrix representations... i is just a 90° counterclockwise rotation:

Dice> i
i →
[[ 0, -1 ],
 [ 1,  0 ]]
Dice> I
I →
[[ 1, 0 ],
 [ 0, 1 ]]
Dice> 3*I - 2*i
3 * I - 2 * i →
[[  3, 2 ],
 [ -2, 3 ]]

The convenience functions c and cstr allow for re-writing pairs of numbers as matrix reps of complex numbers and for re-writing a matrix rep of a complex number in terms of I and i:

Dice> c(2,-3)
c(2,-3) →
[[  2, 3 ],
 [ -3, 2 ]]

Dice> c(2,-3)//cstr
cstr(c(2,-3)) → 2*I - 3*i

The functions Re (real part), Im (imaginary part), mag (magnitude AKA modulus or absolute value), arg (argument), and conj (conjugate) are implemented:

Dice> Re(3*I - 2*i)
Re(3 * I - 2 * i) → 3
Dice> Im(3*I - 2*i)
Im(3 * I - 2 * i) → -2
Dice> mag(3*I - 2*i) |> human
human(mag(3 * I - 2 * i)) → √13
Dice> arg(3*I - 2*i) |> human
human(arg(3 * I - 2 * i)) → -1581/8447*π
Dice> conj(3*I - 2*i)
conj(3 * I - 2 * i) →
[[ 3, -2 ],
 [ 2,  3 ]]

Addition, subtraction, and complex^integer exponentiation work as usual. e^complex not implemented yet, since we don't have e^matrix implemented yet.

Dice> (3*I - 2*i)^2
(3 * I - 2 * i) ^ 2 →
[[   5, 12 ],
 [ -12,  5 ]]

Important: Since complex numbers are really matrices, you must remember to multiply them via matrix multiplication cross or × rather than elementwise multiplication *. And also to divide via multiplication by a matrix inverse. Examples:

Dice> (3*I - 2*i) cross (3*I - 2*i)
(3 * I - 2 * i) × (3 * I - 2 * i) →
[[   5, 12 ],
 [ -12,  5 ]]
Dice> (3*I - 2*i) cross (3*I - 2*i)^-1 |> human
human((3 * I - 2 * i) × (3 * I - 2 * i) ^ -1) →
[[ 1, 0 ],
 [ 0, 1 ]]

Date functions

Warning: These functions may not work consistently across browsers, because date-parsing algorithms are implementation-dependent.

Unless stated otherwise, the following date functions may take arguments in the following formats: - An empty/undefined argument. E.g., cal() returns a calendar for the current month. - A string such as "9/9/2014", "now", or "today". Or an array of such strings. E.g., cal ["10/1/2014", "now"]. "now" corresponds to the current date and time whereas "today" corresponds to the current date at 12am. - The number of milliseconds since the epoch; or an array of such numbers. - An array of arrays of years, months, days, hours, minutes, seconds, milliseconds, where not all components are mandatory. E.g., cal [[2014,10]] prints a calendar for October while cal [[2014,10],[2014,11]] prints calendars for October and November. Note that this is an array of arrays, so as not to be confused with an array of milliseconds since the epoch. - JSON object defining the date; or an array of such objects. For example, cal {"year": 2014, "month": 10} produces a calendar for October while cal [{"year": 2014, "month": 10}, {"year": 2014, "month": 11}] produces calendars for October and November.

The functions: - date() returns the milliseconds since the start of the epoch 1-Jan-1970 00:00:00 GMT. - datestr() returns a string representation of a date. E.g., datestr() returns something like "Tue Sep 09 2014 01:08:07 GMT-0400 (EDT)". - timestr() returns just the time portion, e.g., timestr() returns something like "01:08:11 GMT-0400 (EDT)". - dateobj() returns a JSON object representating a date. For example,

Dice> dateobj()//table
table(dateobj()) →
                 key     value
                date     Tue Sep 09 2014 01:10:08 GMT-0400 (EDT)
            day name     Tuesday
        day of month     9
         day of week     3
         day of year     252
       days in month     30
        days in year     365
                hour     1
         julian date     2456909.715375081
   julian day number     2456909
     last day of Feb     false
           leap year     false
         millisecond     407
              minute     10
               month     9
          month name     September
      ms since epoch     1410239408407
              second     8
                week     37
                year     2014
Dice> cal [[2014,10],[2014,11]]
cal([[2014, 10], [2014, 11]]) →
[
[[ Oct,    ,    ,    ,    ,    , 2014 ],
 [ Sun, Mon, Tue, Wed, Thu, Fri,  Sat ],
 [    ,    ,    ,   1,   2,   3,    4 ],
 [   5,   6,   7,   8,   9,  10,   11 ],
 [  12,  13,  14,  15,  16,  17,   18 ],
 [  19,  20,  21,  22,  23,  24,   25 ],
 [  26,  27,  28,  29,  30,  31,  ]],
[[ Nov,    ,    ,    ,    ,    , 2014 ],
 [ Sun, Mon, Tue, Wed, Thu, Fri,  Sat ],
 [    ,    ,    ,    ,    ,    ,    1 ],
 [   2,   3,   4,   5,   6,   7,    8 ],
 [   9,  10,  11,  12,  13,  14,   15 ],
 [  16,  17,  18,  19,  20,  21,   22 ],
 [  23,  24,  25,  26,  27,  28,   29 ],
 [  30,    ,    ,    ,    ,    ,  ]]
]

The following thee functions compute differences between two input days. They are not compatible with the [[year1,month1,day1],[year2,month2,day2]] or JSON object input format. You can pass in: - 2 strings or number representing ms since the epoch. - a string (or number representing ms since the epoch) and an array of strings (or numbers representing ms since the epoch), in which case the differences are computed between the scalar and each element of the array. - two arrays of strings (or numbers representing ms since the epoch). In this case, the differences are computed pairwise.

The addmonths(date,months) function adds the given number of months to the given date. The addyears(date,years) function adds the given number of years to the given date. The date argument to these functions must be a string (or number representing ms since the epoch) or an array of strings or number. JSON objects and dates in the array format [year1,month1,day1] are not supported.

There are also the following constants for datetime computations:

seconds: 1000
minutes: 1000*60
hours: 1000*60*60
days: 1000*60*60*24
weeks: 1000*60*60*24*7
year: 1000*60*60*24*365
leapyear: 1000*60*60*24*366
averageyear: 1000*60*60*24*365.2425

For example,

Dice> datestr(date("9/6/2014") + 3*days)
datestr(date("9/6/2014") + 3 * days) → "Tue Sep 09 2014 00:00:00 GMT-0400 (EDT)"
Dice> date("9/9/2014") + 4*weeks + 1*days//datestr
datestr(date("9/9/2014") + 4 * weeks + 1 * days) → "Wed Oct 08 2014 00:00:00 GMT-0400 (EDT)"

Simple interval arithmetic

Simple interval arithmetic has been implemented:

Dice> interval(plus,[1, 2],[3, 4])
interval(plus,[1, 2],[3, 4]) → [4, 6]
Dice> interval(times,[1, 2],[3, 4])
interval(times,[1, 2],[3, 4]) → [3, 8]
Dice> interval(minus,[1, 2],[3, 4])
interval(minus,[1, 2],[3, 4]) → [-3, -1]
Dice> interval(div,[1, 2],[3, 4])//human
human(interval(div,[1, 2],[3, 4])) → [1/4, 2/3]

Constants for large and small numbers

Predefined the following constants for convenience; you may overwrite them: - million, M: 10^6 - billion, B: 10^9 - trillion, T: 10^12 - trillionth, pico, p: 10^-12 - billionth, n, nano: 10^-9 - millionth, µ, micro: 10^-6 - thousandth, m, milli: 10^-3 - tenth, d, deci: 10^-1

Euclidean Division

Added the ÷ operator to perform Euclidean division. a÷b produces the array [q,r] where q is the quotient and r is the remainder. Example:

Dice> 13÷3
13 ÷ 3 → [4, 1]

This is just a handy shortcut for doing:

Dice> [floor(13/3),13 mod 3]
[floor(13 / 3), 13 mod 3] → [4, 1]

It works on array inputs to produce a matrix. TMTOWTDI: Here's another way to produce a 2x2 identity matrix:

Dice> [2,1]÷2
[2, 1] ÷ 2 →
[[ 1, 0 ],
 [ 0, 1 ]]

Note that the table of probabilities prob(d4÷d4) is not isomorphic to prob(d4/d4), because 1÷4=1÷3=1÷2=[0,1] but 1/4≠1/3≠1/2≠1/4.

Operators converted to functions

The following operators have been converted to functions: cdf, count, domain, freq, max, mean, median, min, mode, prob, range, sd, shuffle, sort, stats, tally, uniq. This means, among other things, their names are now case sensitive and they may be used with the forward pipe operator:

Dice> 2#d6|>min|>cdf|>human|>table
table(human(cdf(min(2 # d6)))) →
                 key     value
                   1     11/36
                   2     5/9
                   3     3/4
                   4     8/9
                   5     35/36
                   6     1

Dice> choose [0,0,0,5,5,5] + choose [1,2,3,4,5]//prob//human//table
table(human(prob(choose([0, 0, 0, 5, 5, 5]) + choose([1, 2, 3, 4, 5])))) →
                 key     value
                   1     1/10
                   2     1/10
                   3     1/10
                   4     1/10
                   5     1/10
                   6     1/10
                   7     1/10
                   8     1/10
                   9     1/10
                  10     1/10

Dice> d6 - d6 |> abs |> prob |> human |> table
table(human(prob(abs(d6 - d6)))) →
                 key     value
                   0     1/6
                   1     5/18
                   2     2/9
                   3     1/6
                   4     1/9
                   5     1/18

Dice> d6 - d6 |> abs |> prob |> sum
sum(prob(abs(d6 - d6))) → 1

Operators renamed

The QUOTE operator (AKA ') used to prevent an expression from evaluating was renamed LAZY. And the functions quote and unquote were added to wrap/unwrap strings in double quotes.

Palamedes 1.0-a34 (20140903)

/* Input */
score gets 3d6

(6 # score) ⊎ ...
[10 * score]

(6 # score) ⊎ …
[10 * score]
/* Output */
score ← 3d6

(6 # score) ⊎ [10 * score] → [11, 12, 10, 7, 9, 10, 130]

(6 # score) ⊎ [10 * score] → [6, 12, 10, 5, 9, 9, 70]

Example in the node.js command-line interface:

Dice> 1+2+...
Dice> 3+...
Dice> 4
1 + 2 + 3 + 4 → 10
    Dice> table(rat(prob(3d6)))
    table (rat(prob (3d6))) →
                 key     value
                   3     1/216
                   4     1/72
                   5     1/36
                   6     5/108
                   7     5/72
                   8     7/72
                   9     25/216
                  10     1/8
                  11     1/8
                  12     25/216
                  13     7/72
                  14     5/72
                  15     5/108
                  16     1/36
                  17     1/72
                  18     1/216

Examples:

/*Products*/
/*cartesian product*/
prod[["A","B","C"],[1,2,3],["i","ii","iii"]]
/*matrices*/
A gets [[3,2],[-1,0]]
B gets [[-2,3],[4,2]]
C gets [[-1,5],[1,2]]
/*matrix mult*/
prod[A,B,C]
/*mult list of nums*/
prod[1..5]=5!
/*mult values in a JSON obj*/
prod{"a":5,"b":1,"c":2}
/*compose JSON objs*/
prod[{"1": 5,"2":3},{"a":1,"b":1,"c":2}]
/*boolean prod*/
prod[1>0,0>-1,5>2]
    Dice> rat(ortho1 [3, 4, 5])
    rat(ortho1([3, 4, 5])) →
    [[ -4/3, 1, 0 ],
     [ -5/3, 0, 1 ]]

Check that the vectors in ortho1 u are indeed orthogonal to u via matrix multiplication cross. The arguments to cross must have compatible dimensions. So remember first to convert u from a vector to a 1x3 row matrix by wrapping it in square brackets [u], and to convert ortho1 u from a 2x3 matrix of row vectors to a 3x2 matrix of column vectors via trans:

   Dice> [u] cross trans(ortho1 u)
    [u] × trans(ortho1(u)) →
    [[ 0, 0 ]]
    Dice> table(mixed(10#d%/d10))
    table (mixed(10 # d% / d10)) →
               index     element
                   0     8 + 1/2
                   1     25
                   2     42
                   3     5
                   4     10 + 1/4
                   5     5
                   6     14 + 1/5
                   7     20
                   8     7 + 1/3
                   9     2 + 4/9
    Dice> table(commify(10#d% ^ d10))
    table (commify(10 # d% ^ d10)) →
               index     element
                   0     899,194,740,203,776
                   1     35,937
                   2     253,295,162,119,140,640
                   3     81
                   4     161,051
                   5     12,167
                   6     4,900
                   7     147,008,443
                   8     194,481
                   9     3,596,345,248,055,296
    Dice> human(outer(thread,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2]))
    human(outer(thread,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2])) →
    [[ 0,  1/2, √2/2, √3/2, 1 ],
     [ 1, √3/2, √2/2,  1/2, 0 ],
     [ 0, √3/3,    1,   √3, ∞ ]]

Palamedes 1.0-a33 (20140829)

Palamedes 1.0-a32 (20140821)

Lets say we have annualized drift of 8.6% and annualized volatility of 11.9%. Simulate prices for the next 10 years. Assume the price starts at 1:

/* Input */
GBM(0.086,0.119)
S(1,[1..10])
/* Output */
GBM(0.086,0.119) → OK. GBM setup function S(S0,t) using parameters pct_drift=0.086 and pct_volatility=0.119
S(1,[1..10]) → [1.1300624384470488, 1.0925122553647502, 1.1508143130841304, 1.44566922757506, 1.0780285016705622, 1.1258814206939816, 2.74690169911793, 2.415608120936365, 1.0069109537999272, 2.4611505066933206]

To convert annual drift to daily, divide by 250. The constant 250 is just the number of trading days in a year. Some use 252 instead. To convert annual drift to monthly, divide by 12. To convert annual volatility to daily, divide by the square root of 250. To convert annual volatility to monthy, divide by root 12.

Let's say all you have is a list of daily prices changes over some period of time in the past, and you want to compute daily (or annual) drift and volatility and drift from historical data. Use the following method:

/* Input */
/* Daily prices over 10 day period */
prices gets [14.40, 14.20, 14.25,14.00,13.75,13.80,13.60,13.75,13.70,13.90]
n gets count prices
final_prices gets drop prices, 1
initial_prices gets take prices, (n - 1)
/* Calculate the logarithmic price changes */
logarithmic_price_changes gets ln(final_prices/initial_prices)
/* Daily volatility is their standard deviation */
daily_volatility gets sd(logarithmic_price_changes); daily_volatility
/* Annual volatility is √250 times daily volatility */
annual_volatility gets root 250 * daily_volatility; annual_volatility

daily_drift gets mean(logarithmic_price_changes) + daily_volatility^2/2; daily_drift
annual_drift gets 250*daily_drift; annual_drift
/* Output */
prices ← [14.40, 14.20, 14.25, 14.00, 13.75, 13.80, 13.60, 13.75, 13.70, 13.90]
n ← count prices
final_prices ← drop prices, 1
initial_prices ← take prices, (n - 1)
logarithmic_price_changes ← ln(final_prices / initial_prices)

daily_volatility ← sd (logarithmic_price_changes)
daily_volatility → 0.012634706091210165

annual_volatility ← √250 * daily_volatility
annual_volatility → 0.19977224407513625

daily_drift ← mean (logarithmic_price_changes) + daily_volatility ^ 2 / 2
daily_drift → -0.003846778372695374

annual_drift ← 250 * daily_drift
annual_drift → -0.9616945931738435

For drift, we take the mean instead of the standard deviation. Then you must add the variance divided by 2. To annualize the result, multiply by 250. For formulae, see question #5 on Exam 2 for the class, the mathematics of contingent claims.

If we wanted the monthly volatility, we'd multiply daily volatility by the square root of 21, the number of trading days in a month. For monthly drift, we'd multiply daily drift by 21.

In the future, we may want to simulate correlated GBM in two dimensions. See Sigman 2007. This can wait until we add code to exponentiate matrices.

Palamedes 1.0-a31 (20140820)

/* Input */
A gets [[12, 6, 177], [34, 9, 54], [18, 8, 80]]
A
B gets [[12.1,6.66,177.54318],[34,9,54.44],[18,8,80.00001]]
fix(B)
/* Output */
A ← [[12, 6, 177], [34, 9, 54], [18, 8, 80]]

A →
[[ 12, 6, 177 ],
 [ 34, 9,  54 ],
 [ 18, 8,  80 ]]

B ← [[12.1, 6.66, 177.54318], [34, 9, 54.44], [18, 8, 80.00001]]

fix(B) →
[[ 12.10, 6.66, 177.54 ],
 [ 34.00, 9.00,  54.44 ],
 [ 18.00, 8.00,  80.00 ]]
/* Input */
sum A                 /* Row sums */
sum(trans A)          /* Column sums */
sum(sum A)            /* Total of all elements */
fix(sum (A,B, inv A)) /* Add three matrices */
sum[1..100]
/* Output */
sum(A) → [195, 97, 106]

sum(trans(A)) → [64, 23, 311]

sum(sum(A)) → 398

fix(sum(A,B,inv(A))) →
[[ 24.12, 12.74, 354.44 ],
 [ 67.86, 17.82, 108.87 ],
 [ 36.01, 16.00, 159.99 ]]

sum([1..100]) → 5050
/* Input */
fix(inv A cross A,0)
/* Output */
fix(inv(A) × A,0) →
[[  1, -0, -0 ],
 [  0,  1,  0 ],
 [ -0, -0,  1 ]]

The negative zeros are due to JavaScript rounding errors.

/* Input */
rot A
/* Output */
rot(A) →
[[ 18, 8,  80 ],
 [ 34, 9,  54 ],
 [ 12, 6, 177 ]]

If you want to see the Seven Words You Can Never Say on Television, evaluate the following NSFW expression:

rot["fuvg", "cvff", "shpx", "phag", "pbpxfhpxre", "zbgureshpxre", "gvgf"]
/* Input */
rev[1..10]
rev A
rev("A man, a plan, a canal: Panama")
/* Output */
rev([1..10]) → [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

rev(A) →
[[ 177, 6, 12 ],
 [  54, 9, 34 ],
 [  80, 8, 18 ]]

rev("A man, a plan, a canal: Panama") → "amanaP :lanac a ,nalp a ,nam A"
/* Input */
outer(times,[1..12],[1..12])
/* Output */
outer(times,[1..12],[1..12]) →
[[  1,  2,  3,  4,  5,  6,  7,  8,   9,  10,  11,  12 ],
 [  2,  4,  6,  8, 10, 12, 14, 16,  18,  20,  22,  24 ],
 [  3,  6,  9, 12, 15, 18, 21, 24,  27,  30,  33,  36 ],
 [  4,  8, 12, 16, 20, 24, 28, 32,  36,  40,  44,  48 ],
 [  5, 10, 15, 20, 25, 30, 35, 40,  45,  50,  55,  60 ],
 [  6, 12, 18, 24, 30, 36, 42, 48,  54,  60,  66,  72 ],
 [  7, 14, 21, 28, 35, 42, 49, 56,  63,  70,  77,  84 ],
 [  8, 16, 24, 32, 40, 48, 56, 64,  72,  80,  88,  96 ],
 [  9, 18, 27, 36, 45, 54, 63, 72,  81,  90,  99, 108 ],
 [ 10, 20, 30, 40, 50, 60, 70, 80,  90, 100, 110, 120 ],
 [ 11, 22, 33, 44, 55, 66, 77, 88,  99, 110, 121, 132 ],
 [ 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144 ]]
/* Input */
[1,3,-5].[4,-2,-1]  /* most concise way */
[1,3,-5] dot [4,-2,-1]
inner(plus,times,[1,3,-5],[4,-2,-1]) /* most verbose, but the most powerful */
/* Input */
fix[2,2.1,2.11,2.111]
round[2,2.1,2.11,2.111]
/* Output */
fix([2, 2.1, 2.11, 2.111]) → [2.00, 2.10, 2.11, 2.11]
round([2, 2.1, 2.11, 2.111]) → [2, 2.1, 2.11, 2.11]
/* Input */
table(round(prob(3d6),3))
/* Output */
table (round(prob (3d6),3)) →
                 key     value
                   3     0.005
                   4     0.014
                   5     0.028
                   6     0.046
                   7     0.069
                   8     0.097
                   9     0.116
                  10     0.125
                  11     0.125
                  12     0.116
                  13     0.097
                  14     0.069
                  15     0.046
                  16     0.028
                  17     0.014
                  18     0.005
/* Input */
fix(outer(apply,[sin, cos, tan],[0, π/6, π/4, π/3, π/2]),4)
outer(apply,[sin, cos, tan],[0, π/6, π/4, π/3, π/2]) approx [[0,1/2,√2/2,√3/2,1],[1,√3/2,√2/2,1/2,0],[0,1/√3,1,√3,anyfinitenumber]]
/* Output */
fix(outer(apply,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2]),4) →
[[ 0.0000, 0.5000, 0.7071, 0.8660,                 1.0000 ],
 [ 1.0000, 0.8660, 0.7071, 0.5000,                 0.0000 ],
 [ 0.0000, 0.5774, 1.0000, 1.7321, 16331778728383844.0000 ]]

outer(apply,[sin, cos, tan],[0, π / 6, π / 4, π / 3, π / 2]) ≈ [[0, 1 / 2, √2 / 2, √3 / 2, 1], [1, √3 / 2, √2 / 2, 1 / 2, 0], [0, 1 / √3, 1, √3, anyfinitenumber]] →
[[ true, true, true, true,  true ],
 [ true, true, true, true,  true ],
 [ true, true, true, true, false ]]
/* Input */
resize([10, 10],[1] ⊎ (10 # 0))
/* Output */
resize([10, 10],[1] ⊎ (10 # 0)) →
[[ 1, 0, 0, 0, 0, 0, 0, 0, 0, 0 ],
 [ 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 ],
 [ 0, 0, 1, 0, 0, 0, 0, 0, 0, 0 ],
 [ 0, 0, 0, 1, 0, 0, 0, 0, 0, 0 ],
 [ 0, 0, 0, 0, 1, 0, 0, 0, 0, 0 ],
 [ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0 ],
 [ 0, 0, 0, 0, 0, 0, 1, 0, 0, 0 ],
 [ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0 ],
 [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 0 ],
 [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 ]]
/* Input */
myFish gets ["angel", "clown", "mandarin", "surgeon"]
splice(myFish, 2, 0, "drum") /* Make a new array with the drum fish added after the second ("clown") fish */
splice(resize([3, 2],0),1,1,[1,1]) /* Create a new matrix with 0s on the first and third row and 1s sandwiched between */
/* Output */
myFish ← ["angel", "clown", "mandarin", "surgeon"]
splice(myFish,2,0,"drum") → ["angel", "clown", "drum", "mandarin", "surgeon"]
splice(resize([3, 2],0),1,1,[1, 1]) →
[[ 0, 0 ],
 [ 1, 1 ],
 [ 0, 0 ]]
/* Input */
/* Corrected linear regression example */
$x gets [1,2,3,4,5]                  /* independent data: array */
$y gets [1,2,1.3,3.75,2.25]          /* dependent data: array */
n gets count $y                      /* n: sample size */
p gets 2                             /* p: number of predictors */
ν gets n-p                           /* nu: degrees of freedom */
y gets trans [$y]                    /* n x 1 matrix */
ones gets n # 1
$X gets trans [ones, $x]             /* design matrix: n x p matrix */
y; $X                                /* show what we have so far */

/* y = $X cross β */
/* Therefore inv $X cross y = inv $X cross $X cross β = β */
/* N.B. No need to construct a pseudo-inverse anymore: this is done automatically */
β gets inv $X cross y; β             /* response coefficients: p x 1 matrix */

/* break out the slope and intercept then show them */
intercept gets β after 0 after 0; intercept
slope gets β after 1 after 0; slope

ŷ gets $X cross β; ŷ                 /* fitted values */
residuals gets ŷ - y; residuals      /* residuals from the regression */

/* ss: OLS estimate for the variance in the error term */
ss gets ((trans residuals cross residuals) after 0 after 0)/ν; ss

/* standard error of the regression (SER) AKA standard error of the equation (SEE) */
sqrt(ss)
/* Output */
$x ← [1, 2, 3, 4, 5]
$y ← [1, 2, 1.3, 3.75, 2.25]
n ← count $y
p ← 2
ν ← n - p
y ← trans([$y])
ones ← n # 1
$X ← trans([ones, $x])

y →
[[    1 ],
 [    2 ],
 [  1.3 ],
 [ 3.75 ],
 [ 2.25 ]]
$X →
[[ 1, 1 ],
 [ 1, 2 ],
 [ 1, 3 ],
 [ 1, 4 ],
 [ 1, 5 ]]

β ← inv($X) × y
β →
[[  0.7849999999999988 ],
 [ 0.42500000000000027 ]]

intercept ← β ∘ 0 ∘ 0
intercept → 0.7849999999999988

slope ← β ∘ 1 ∘ 0
slope → 0.42500000000000027

ŷ ← $X × β
ŷ →
[[  1.209999999999999 ],
 [ 1.6349999999999993 ],
 [ 2.0599999999999996 ],
 [              2.485 ],
 [               2.91 ]]

residuals ← ŷ - y
residuals →
[[  0.20999999999999908 ],
 [ -0.36500000000000066 ],
 [   0.7599999999999996 ],
 [  -1.2650000000000001 ],
 [   0.6600000000000001 ]]

ss ← ((trans(residuals) × residuals) ∘ 0 ∘ 0) / ν
ss → 0.93025

sqrt(ss) → 0.9644946863513557

Palamedes 1.0-a30 (20140814)

For vectors, norm([3,4]) is the same as norm([3,4],2), i.e., the Euclidean norm. In general, norm([a,b,...,c],p) is (abs(a)^p + abs(b)^p + ... + abs(c)^p)^(1/p). A special case is norm(v,∞) or norm(v,inf) which returns the max-norm of the given vector or matrix: norm([a,b,...,c],∞) is max [abs(a), abs(b), ..., abs(c)]. Note that is usually set to 100, so norm(v,100) would actually give the max-norm norm(v,∞), which is the limiting value of the p-norm as p goes to infinity. Another special case the 0-"norm", which returns the number of non-zero elements in the structure.

For matrices:

Note that norm doesn't thread over its arguments and that parens are required: norm([3,4]) is OK, but norm [3,4] is a syntax error. That's to eliminate ambiguity over what to do with expressions like norm [[1,2],[3,4]] -- is norm supposed to thread over two vectors or one matrix?

Here are some examples of the new features.

Take the square roots of 0 to 10:

Dice> sqrt [0..10]
sqrt([0..10]) → [0, 1, 1.4142135623730951, 1.7320508075688772, 2, 2.23606797749979, 2.449489742783178, 2.6457513110645907, 2.8284271247461903, 3, 3.1622776601683795]

Fix the vector of square roots above to 4 decimal places:

Dice> fix(sqrt [0..10],4)
fix(sqrt([0..10]),4) → [0.0000, 1.0000, 1.4142, 1.7321, 2.0000, 2.2361, 2.4495, 2.6458, 2.8284, 3.0000, 3.1623]

Fix pi to the default 2 decimal places:

Dice> fix(pi)
fix(π) → 3.14

Roll d6-d6 ten times and take the signs of the results. Save the results in vector v:

Dice> v<:sign(10#d6-d6)
v ↜ sign(10 # d6 - d6) → [1, 0, 1, -1, 1, 0, 1, -1, 0, -1]

Now count the non-zero elements in v using the 0-norm:

Dice> norm(v,0)
norm(v,0) → 7

Take the Euclidean-norm (2-norm) of the vector [3,4]:

Dice> norm([3,4])
norm([3, 4]) → 5

Find the trace of the given matrix:

Dice> m<:[[1,0,2],[0,3,4],[5,-12,0]]
m ↜ [[1, 0, 2], [0, 3, 4], [5, -12, 0]] → [[1, 0, 2], [0, 3, 4], [5, -12, 0]]
Dice> tr(m)
tr(m) → 4

Use matrix norms to find the non-zero count, maximum absolute column sum, maximum absolute row sum, and maximum absolute element of the same matrix m:

Dice> norm(m,0) /* non-zero count */
norm(m,0) → 6
Dice> norm(m,1) /* maximum absolute column sum */
norm(m,1) → 15
Dice> norm(m,inf) /* maximum absolute row sum */
norm(m,∞) → 17
Dice> norm(m,"max") /* maximum absolute element */
norm(m,"max") → 12

Recurrences

Generate the first ∞=100 elements of the Fibonacci sequence:

Dice> recur (_1+_2), [0,1]
recur (_1 + _2), [0, 1] → [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811, 514229, 832040, 1346269, 2178309, 3524578, 5702887, 9227465, 14930352, 24157817, 39088169, 63245986, 102334155, 165580141, 267914296, 433494437, 701408733, 1134903170, 1836311903, 2971215073, 4807526976, 7778742049, 12586269025, 20365011074, 32951280099, 53316291173, 86267571272, 139583862445, 225851433717, 365435296162, 591286729879, 956722026041, 1548008755920, 2504730781961, 4052739537881, 6557470319842, 10610209857723, 17167680177565, 27777890035288, 44945570212853, 72723460248141, 117669030460994, 190392490709135, 308061521170129, 498454011879264, 806515533049393, 1304969544928657, 2111485077978050, 3416454622906707, 5527939700884757, 8944394323791464, 14472334024676220, 23416728348467684, 37889062373143900, 61305790721611580, 99194853094755490, 160500643816367070, 259695496911122560, 420196140727489660, 679891637638612200, 1100087778366101900, 1779979416004714000, 2880067194370816000, 4660046610375530000, 7540113804746346000, 12200160415121877000, 19740274219868226000, 31940434634990100000, 51680708854858330000, 83621143489848430000, 135301852344706760000, 218922995834555200000]

Approximate the square root of 2. Note that by the fifth iteration this sequence converges to the same decimal approximation produced by JavaScript's Math.sqrt(2) function call:

Dice> recur ((1/2)*(_1+2/_1)), [1.5]
recur ((1 / 2) * (_1 + 2 / _1)), [1.5] → [1.5, 1.4166666666666665, 1.4142156862745097, 1.4142135623746899, 1.414213562373095, ..., 1.414213562373095]

Palamedes 1.0-a29 (20140813)

JavaScript's builtin Math.random() function isn't seedable, so it can't produce a repeatable pseudorandom sequence that is completely predictable. Therefore, we incorporated David Bau's seedrandom.js Javascript module to produce a determinstic sequence of pseudorandom numbers. It replaces Math.random() and adds a Math.seedrandom() function that allows us to set a random seed.

The Palamedes UI now has a seed variable to track the last value passed to Math.seedrandom() and a seed() function that allows us to pass a value to Math.seedrandom(). Here is a sample script that shows how to use this new functionality:

seed("Palamedes") /* Call the seed() function to set seed to "Palamedes" */
10 # d6           /* Generate some dice throws */
10 # d6
seed              /* seed variable saved the last seed value */
seed(seed)        /* reseed with the same value */
10 # d6           /* now running same dice throws produces same results */
10 # d6

The output from this script should look like this:

seed("Palamedes") → Ok
10 # d6 → [1, 3, 6, 3, 2, 2, 2, 2, 4, 2]
10 # d6 → [6, 6, 4, 6, 6, 5, 4, 3, 4, 1]
seed → "Palamedes"
seed(seed) → Ok
10 # d6 → [1, 3, 6, 3, 2, 2, 2, 2, 4, 2]
10 # d6 → [6, 6, 4, 6, 6, 5, 4, 3, 4, 1]

Calling seed() without a seed value will reseed the PRNG with a new seed value constructed from the date and time. You can see this value by inspecting the seed variable. You should see something like this:

seed → Ãﱓ.‚’â!¿Ö³y\-ðN9âún©‹½×p¶SC2ç¥(}¶D[XgÇôÆ«¾aý¡v®ö ÑBº¤_”ÂØÔ6Œœp¹2qœ•ò9V¶&”ž#É|U~k\!ï°`MwR7m@ŽLmÅíîÐ8QÌяj½»Móý•ßs€&fÒU׍O0єåÆå:gGzWûÍnÿÄßÓHpè®\=Ísä!ˆ—¬«š‡Öñà0ìè"¼âxeeS^ILVG€`›+Ûí±HÐ\ߗ<ygá֒ð"¾=EFFº&­B’õ7Ááÿs¡á(¦dÚ&n`VP×ÅrÞ

If you aren't interested in predictable/repeateable pseudorandom number sequences, Don't Worry! By default, Palamedes autoseeds the PRNG, and you never have to use any of this new functionality!!! This functionality only exists for people who need to run the same set of experiments over and over again and get the same results.

Palamedes 1.0-a28 (20140812)

The following code computes stats on a random sample of 1000 IQ scores:

Dice> stats(1000#floor(N(100,15)))
stats (1000 # floor (N(100,15))) → {"min":55,"max":142,"mean":99.305,"median":100,"mode":["101"],"sd":14.899260483761038,"count":1000}

This allows you to write:

Dice> N ← 5; N!; N(100,15); 2*N
N ← 5
N! → 120
N(100,15) → 134.2878484013724
2 * N → 10

Here are some examples:

Dice> $x ← 10; $X$ ← 100; A?B ← 50; C? ← 5
$x ← 10
$X$ ← 100
A?B ← 50
C? ← 5

News for Palamedes 1.0-a27 (20140711)

Concat does array concatenation. It uses the mathematical symbol for multiset union ⊎. It does not work the same as union, since order and duplicates matter:

Dice> [1,2,3] concat [3,3,1,4,5] /* array concatenation */
[1, 2, 3] ⊎ [3, 3, 1, 4, 5] → [1, 2, 3, 3, 3, 1, 4, 5]

Union, intersect, diff and sdiff work on mathematical sets, i.e., order and duplicates don't matter!

Dice> [1,2,3] union [3,3,1,4,5] /* set union */
[1, 2, 3] ∪ [3, 3, 1, 4, 5] → [1, 2, 3, 4, 5]
Dice> [1,2,3] intersect [3,3,1,4,5] /* set intersection */
[1, 2, 3] ∩ [3, 3, 1, 4, 5] → [3, 1]
Dice> [1,2,3] diff [3,3,1,4,5] /* set difference */
[1, 2, 3] \ [3, 3, 1, 4, 5] → [2]
Dice> [1,2,3] sdiff [3,3,1,4,5] /* set symmetric difference */
[1, 2, 3] Δ [3, 3, 1, 4, 5] → [2, 4, 5]

BEWARE! This new definition of union is not backward compatible with previous builds. To make old code work, you may need to change x union y to x concat y; further you may need to cast x or y as arrays; and you may need to flatten the result with the flatten operator, e.g., flatten ([x] concat [y])

Infix set and array operators now have precedence between relational expressions and additive expressions. No more are parentheses needed to string them together:

Dice> [1,2] union [3,4] union [5,6]
[1, 2] ∪ [3, 4] ∪ [5, 6] → [1, 2, 3, 4, 5, 6]

No more parens needed to access elements of matrices:

Dice> [[1,2],[3,4]] of 0 of 1
[[1, 2], [3, 4]] ∘ 0 ∘ 1 → 2
Dice> [[1,2],[3,4]] of 0 of 0
[[1, 2], [3, 4]] ∘ 0 ∘ 0 → 1
Dice> [[1,2],[3,4]] of 1 of 0
[[1, 2], [3, 4]] ∘ 1 ∘ 0 → 3
Dice> [[1,2],[3,4]] of 1 of 1
[[1, 2], [3, 4]] ∘ 1 ∘ 1 → 4

Subset/⊂ and subseteq/⊆ have same precedence as the relational operators >, <, ≤, ≥

Dice> [1,2,3] subset [3,2,3,1,4,5]
[1, 2, 3] ⊂ [3, 2, 3, 1, 4, 5] → true
Dice> [1,2,3,10] subset [3,2,3,1,4,5] /* fails b/c 10 not in RHS */
[1, 2, 3, 10] ⊂ [3, 2, 3, 1, 4, 5] → false
Dice> [1,2,3] subset [3,2,3,1] /* fails b/c LHS not a proper subset of RHS */
[1, 2, 3] ⊂ [3, 2, 3, 1] → false
Dice> [1,2,3] subseteq [3,2,3,1] /* OK even tho' LHS not a proper subset of RHS */
[1, 2, 3] ⊆ [3, 2, 3, 1] → true

Powerset finds the powerset of the given array...

Dice> powerset [1,2,3]
powerset [1, 2, 3] → [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]

Permute computes all permutations of the given array...

Dice> permute [1,2,3]
permute [1, 2, 3] → [[1, 2, 3], [1, 3, 2], [2, 1, 3], [2, 3, 1], [3, 1, 2], [3, 2, 1]]

Applying a logical relation between two matrices returns a matrix of booleans. To reduce this matrix of booleans to a single boolean value, use the new all and some quantifiers:

Dice> A gets [[3,2],[-1,0]]
A ← [[3, 2], [-1 * 1, 0]]
Dice> B gets [[-2,3],[4,2]]
B ← [[-1 * 2, 3], [4, 2]]
Dice> C gets [[-1,5],[1,2]]
C ← [[-1 * 1, 5], [1, 2]]
Dice> R gets [[11,36],[-5,4]]
R ← [[11, 36], [-1 * 5, 4]]
Dice> S gets [[11,35],[-5,4]]
S ← [[11, 35], [-1 * 5, 4]]
Dice> T gets [[10,35],[-3,7]]
T ← [[10, 35], [-1 * 3, 7]]
Dice> A cross B cross C
A × B × C → [[11, 36], [-5, 4]]
Dice> A cross B cross C = R
A × B × C = R → [[true, true], [true, true]]
Dice> A cross B cross C = S
A × B × C = S → [[true, false], [true, true]]
Dice> A cross B cross C = T
A × B × C = T → [[false, false], [false, false]]
Dice> all (A cross B cross C = R)
∀ (A × B × C = R) → true
Dice> all (A cross B cross C = S)
∀ (A × B × C = S) → false
Dice> some (A cross B cross C = S)
∃ (A × B × C = S) → true
Dice> some (A cross B cross C = T)
∃ (A × B × C = T) → false

Here's an example of the approx equality and the use of the tol variable, which is a ten-thousandth by default. You may set tol bigger or smaller to suit your needs:

Dice> tol
tol → 0.0001
Dice> 1 approx 1.0001
1 ≈ 1.0001 → true
Dice> 1 approx 1.001
1 ≈ 1.001 → false
Dice> tol gets 1  /* make the tolerance bigger */
tol ← 1
Dice> 1 approx 1.99999999999999
1 ≈ 1.99999999999999 → true
Dice> 1 approx 2
1 ≈ 2 → false

Though the cross operator is overloaded to take a cartesian product of its arguments (as long as they're not matrices or 3-vectors), this is only good for taking the cross product of two arrays. To take a cross product of more than two arrays, use the new prod/Π operator. This example demonstrates how to make tuples for a 3-level outline. Note that prod takes an array of arrays as its argument:

Dice> prod [["I","II","III"],["A","B","C"],[1,2,3]]
Π [["I", "II", "III"], ["A", "B", "C"], [1, 2, 3]] → [["I", "A", 1], ["I", "A", 2], ["I", "A", 3], ["I", "B", 1], ["I", "B", 2], ["I", "B", 3], ["I", "C", 1], ["I", "C", 2], ["I", "C", 3], ["II", "A", 1], ["II", "A", 2], ["II", "A", 3], ["II", "B", 1], ["II", "B", 2], ["II", "B", 3], ["II", "C", 1], ["II", "C", 2], ["II", "C", 3], ["III", "A", 1], ["III", "A", 2], ["III", "A", 3], ["III", "B", 1], ["III", "B", 2], ["III", "B", 3], ["III", "C", 1], ["III", "C", 2], ["III", "C", 3]]

Here are examples of gcd and lcm:

Dice> gcd [57,0,-45,-18,90,447]
gcd [57, 0, -1 * 45, -1 * 18, 90, 447] → 3
Dice> lcm [-50,25,-45,-18,90,447]
lcm [-1 * 50, 25, -1 * 45, -1 * 18, 90, 447] → 67050

News for Palamedes 1.0-a26 (20140709)

Comments

You can now intersperse C-style comments in your code. They'll be ignored by the parser:

Dice> /* hi there */

Dice> 1 + /* hi there */ 1
1 + 1 → 2
Dice> [1, /* two */ 2, 3]
[1, 2, 3] → [1, 2, 3]

Cartesian products

Here's a different way to simulate a deck of 52 cards, shuffling, and dealing out 5. This time, a "card" is a pair (i.e., a two-element array) whose first component is the rank and whose second is the suit:

Dice> ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
Dice> suits ← ["♠", "♡", "♢", "♣"]
suits ← ["♠", "♡", "♢", "♣"]
Dice> cards ←  ranks × suits
cards ← ranks × suits
Dice> count cards
count cards → 52
Dice> shuffle cards
shuffle cards → [["Q", "♡"], [4, "♣"], [10, "♣"], [8, "♣"], [2, "♣"], [4, "♢"], ["A", "♢"], [9, "♡"], [6, "♢"], [7, "♡"], [6, "♡"], [10, "♢"], ["J", "♡"], ["A", "♣"], ["Q", "♢"], [4, "♠"], [4, "♡"], ["J", "♣"], [7, "♢"], [3, "♢"], ["K", "♡"], [5, "♡"], [8, "♡"], [7, "♠"], [9, "♢"], [3, "♠"], [5, "♠"], [8, "♠"], ["J", "♢"], [9, "♠"], [9, "♣"], [10, "♠"], ["Q", "♠"], [2, "♠"], [8, "♢"], ["A", "♠"], [2, "♢"], [5, "♣"], [6, "♣"], ["A", "♡"], ["K", "♠"], [6, "♠"], ["K", "♣"], [5, "♢"], ["J", "♠"], [10, "♡"], ["Q", "♣"], [2, "♡"], [3, "♡"], ["K", "♢"], [3, "♣"], [7, "♣"]]
Dice> take (shuffle cards), 5
take (shuffle cards), 5 → [[2, "♡"], [10, "♢"], [3, "♡"], [10, "♠"], [4, "♡"]]

Here's a 1-line constructor for a Pinochle deck:

pinochle <- (flatten(2#[9,10,"J","Q","K","A"]))×["♠", "♡", "♢", "♣"]

Vector cross products

If a and b are 3-D vectors, then a × b is a vector that is perpendicular to a and b, whose direction is determined by the right-hand-rule, and whose magnitude is the area of the parallelogram spanned by a and b.

Dice> a gets [3,-3,1]; b gets [-12,12,-4]
a ← [3, -1 * 3, 1]
b ← [-1 * 12, 12, -1 * 4]
Dice> a cross b
a × b → [0, 0, 0]
Dice> b gets [4,9,2]
b ← [4, 9, 2]
Dice> a cross b
a × b → [-15, -2, 39]
Dice> √((a × b) . (a × b)) /* magnitude of a × b */
√((a × b) . (a × b)) → 41.83300132670378
Dice> √((a × b) . (a × b)) = 5*root 70
√((a × b) . (a × b)) = 5 * √70 → true

Transpose

Dice> trans [[1,2,3],[4,5,6],[7,8,9]]
trans [[1, 2, 3], [4, 5, 6], [7, 8, 9]] → [[1, 4, 7], [2, 5, 8], [3, 6, 9]]
Dice> trans [1,2,3]
trans [1, 2, 3] → [[1], [2], [3]]

Matrix multiplication

Dice> [[1, 2, 3], [4, 5, 6]] × [[7,8],[9,10],[11,12]]
[[1, 2, 3], [4, 5, 6]] × [[7, 8], [9, 10], [11, 12]] → [[58, 64], [139, 154]]
Dice> I ← [[1,0,0],[0,1,0],[0,0,1]]
I ← [[1, 0, 0], [0, 1, 0], [0, 0, 1]]
Dice> I × [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
I × [[1, 2, 3], [4, 5, 6], [7, 8, 9]] → [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
Dice> [[1, 2, 3], [4, 5, 6], [7, 8, 9]] × (trans [1,2,3])
[[1, 2, 3], [4, 5, 6], [7, 8, 9]] × (trans [1, 2, 3]) → [[14], [32], [50]]

Matrix inverse and determinant

Dice> inv [[1,2],[3,4]]
inv [[1, 2], [3, 4]] → [[-2, 1], [1.5, -0.5]]
Dice> (inv [[1,2],[3,4]]) cross [[1,2],[3,4]]
(inv [[1, 2], [3, 4]]) × [[1, 2], [3, 4]] → [[1, 0], [0, 1]]
Dice> det [[1,2],[3,4]]
det [[1, 2], [3, 4]] → -2

Solve a system of simultaneous linear equations

Solve the system A × _x = b, where A and b are given as follows:

Dice> A← [[2,-3],[4,1]]; b← trans[-2, 24]; _x← (inv A)×b; _x
A ← [[2, -1 * 3], [4, 1]]
b ← trans [-1 * 2, 24]
_x ← (inv A) × b
_x → [[4.999999999999999], [4]]

Check that the solution works (up to JavaScript rounding errors):

Dice> A × _x
A × _x → [[-2.0000000000000018], [23.999999999999996]]

Linear regression

We want to do a simple linear regression using the following model:

_Y = slope * _x + intercept
   = _X * _b

s.t.

_Y = trans [y1, y2, ..., yN]
_b = trans [intercept, slope]
_X = trans [[1, 1, ..., 1] [x1, x2, ..., xN]]

The following sample input...

_x gets [1,2,3,4,5]          /* independent data */
_Y gets [1,2,1.3,3.75,2.25]  /* dependent data */
_1 gets (count _x) # 1
_X gets trans [_1, _x]
_b gets ((inv ((trans _X) cross _X)) cross (trans _X)) cross (trans _Y)
intercept gets (_b after 0) after 0
slope gets (_b after 1) after 0
slope; intercept

prints out these results...

slope → [0.42500000000000027]
intercept → [0.7849999999999988]

Overloaded arithmetic operations for scalars, arrays, and matrices

Try these examples...

/* Addition */
1+1               /* scalar + scalar */
1+[1,2,3]         /* scalar + array */
[1,2,3] + 1       /* array + scalar */
"a" + [1, 2, 3]   /* string + array */
[1, 2, 3] + "a"   /* array + string */
[5,10,15]+[1,2,3] /* array + array */
[[5,10,15],[1,2,3]]+[[1,2,3],[5,10,15]] /* matrix + matrix */
1 + [[5,10,15],[1,2,3]]                 /* scalar + matrix */
[[5,10,15],[1,2,3]] + 1                 /* matrix + scalar */
"a" + [[5,10,15],[1,2,3]]               /* string + matrix */
[[5,10,15],[1,2,3]] + "a"               /* matrix + string */
[[5,10,15],[1,2,3]]+[1,2]               /* matrix + array */
[1,2] + [[5,10,15],[1,2,3]]             /* array + matrix */

/* Exponentiation */
[[ 1, 0, 0 ],[ 0, 1, 0 ],[ 0, 0, 1 ]]^5
[[1,2],[3,4]]^-1 /* matrix inverse */
([[1, 2], [3, 4]] ^ -1) × [[1, 2], [3, 4]] /* gives the identity*/
[[1,2],[3,4]]^-2 /* matrix inverse, squared */
[[2,0],[0,3]]^[-2,-1,0,1,2,3] /* diagonal matrix to a sequence of powers */
[-1,0,1,2,3]^2 /* sequence squared */
2^[-2,-1,0,1,2,3] /* 2 to a sequence of powers */
[0,1,2,3]^[0,1,2,3] /* element-wise */
[[1,2],[3,4]]^[[1,-2],[-1,4]] /* element-wise */
2^10

Gotchyas

Note that × doesn't yet parse as an aritmetic operator like +, so while you can do

1+2+3+4

you can't do

a×b×c×d

Until the grammar is altered, use parentheses when crossing more than two items:

((a×b)×c)×d

News for Palamedes 1.0-a25 (20140708)

News for Palamedes 1.0-a24 (20140705)

Mode function

Mode works with uni-modal and multi-modal arrays:

Dice> mode [3,1,2,1,3,1]
mode [3, 1, 2, 1, 3, 1] → [1]
Dice> mode [3,1,2,1,3,3,1]
mode [3, 1, 2, 1, 3, 3, 1] → [1, 3]
Dice> mode [1,2,3]
mode [1, 2, 3] → [1, 2, 3]

As well as objects:

Dice> prob(d6+d4)
prob (d6 + d4) → {"2":0.041666666666666664,"3":0.08333333333333333,"4":0.125,"5":0.16666666666666666,"6":0.16666666666666666,"7":0.16666666666666666,"8":0.125,"9":0.08333333333333333,"10":0.041666666666666664}
Dice> mode(prob(d6+d4))
mode (prob (d6 + d4)) → [5, 6, 7]
Dice> mode(prob(3d6))
mode (prob (3d6)) → [10, 11]
Dice> mode(prob(2d6))
mode (prob (2d6)) → [7]

Mode is now included in the stats function:

stats (3d6) → {"min":3,"max":18,"mean":10.500000000000002,"median":10,"mode":["10","11"],"sd":2.958039891549803,"count":16}

Fixed bug in stats

The bug prevented expressions like the following from working, because StatsAST incorrectly guessed that the user wants one random variate (in this case from abs(d6-d6)) rather than the entire distribution.

stats (abs (d6 - d6)) → {"min":0,"max":5,"mean":1.9444444444444446,"median":2,"mode":["1"],"sd":1.4326441064697364,"count":6}

Table

Changed the way table works. For a pmf, decimal points should line up (still have work todo to get decimals to line up for non pmfs):

table (cdf (score)) →
             key     value
               3     0.004629629629629629
               4     0.018518518518518517
               5     0.046296296296296294
               6     0.09259259259259259
               7     0.16203703703703703
               8     0.25925925925925924
               9     0.375
              10     0.5
              11     0.625
              12     0.7407407407407407
              13     0.8379629629629629
              14     0.9074074074074073
              15     0.9537037037037036
              16     0.9814814814814814
              17     0.9953703703703702
              18     0.9999999999999999

Fixed bug preventing tabling when the domain/key consists of strings, e.g., this works

table (stats (abs (d6 - d6))) →
             key     value
           count     6
             max     5
            mean     1.9444444444444446
          median     2
             min     0
            mode     1
              sd     1.4326441064697364

News for Palamedes 1.0-a23 (20140704)

Absolute value function

Dice> abs(-12)
abs (-1 * 12) → 12

Dice> abs [-1,5,-3]
abs [-1 * 1, 5, -1 * 3] → [1, 5, 3]

Dice> prob(abs(d6 - d6))
prob (abs (d6 - d6)) → {"0":0.16666666666666669,"1":0.2777777777777778,"2":0.2222222222222222,"3":0.16666666666666666,"4":0.1111111111111111,"5":0.05555555555555555}

Dice> sum(prob(abs(d6 - d6)))
sum (prob (abs (d6 - d6))) → 1

Table function

This is useful for displaying a JSON object or an array as a two-column table. Note that each column is 20 chars wide:

Dice> table(prob(abs(d6 - d6)))
table (prob (abs (d6 - d6))) →
             key                    value
               0      0.16666666666666669
               1       0.2777777777777778
               2       0.2222222222222222
               3      0.16666666666666666
               4       0.1111111111111111
               5      0.05555555555555555

Dice> table [1..6]
table [1..6] →
           index                  element
               0                        1
               1                        2
               2                        3
               3                        4
               4                        5
               5                        6

Overloaded * operator

Overloaded * operator for string*number and number*string:

Dice> "hello" * 3
"hello" * 3 → "hellohellohello"

Dice> 3 * "hello"
3 * "hello" → "hellohellohello"

Overloaded * operator: pmf1*pmf2 returns the convolution of pmfs. Here is the pmf for an astragalus:

Dice> pmf <: {"1": 0.1, "3": 0.4, "4": 0.4, "6": 0.1}
pmf ↜ {"1":0.1,"3":0.4,"4":0.4,"6":0.1} → {"1":0.1,"3":0.4,"4":0.4,"6":0.1}

Her is the pmf for two astragali:

Dice> pmf * pmf
pmf * pmf → {"2":0.010000000000000002,"4":0.08000000000000002,"5":0.08000000000000002,"6":0.16000000000000003,"7":0.3400000000000001,"8":0.16000000000000003,"9":0.08000000000000002,"10":0.08000000000000002,"12":0.010000000000000002}

Inverse function

Inverse of a hashmap. Note this will only work if the hashmap is one-to-one:

Dice> inv {"red":1,"blue":2,"green":3}
inv {"red":1,"blue":2,"green":3} → {"1":"red","2":"blue","3":"green"}

Inverse of a string is the reverse of the string:

Dice> inv "hello world!"
inv "hello world!" → "!dlrow olleh"

Inverse of a number is the additive inverse of the number, same as unary minus:

Dice> inv 12
inv 12 → -12

N.B. We need parens here, since unary minus is not a primary expression!

Dice> inv (-12)
inv (-1 * 12) → 12

Inverse of a boolean is logical negation of the boolean, same as '~' operator.

Dice> inv true
inv true → false

Sort function

Sorting a string converts it to an array and then sorts it alphabetically:

Dice> sort "hello"
sort "hello" → ["e", "h", "l", "l", "o"]

This may be useful for tallying the occurrences letters in the string:

Dice> tally(sort "hello")
tally (sort "hello") → {"e":1,"h":1,"l":2,"o":1}

Sorting a numeric array:

Dice> a <: 10#d%
a ↜ 10 # d% → [56, 10, 10, 9, 68, 66, 39, 16, 87, 74]
Dice> sort a
sort a → [9, 10, 10, 16, 39, 56, 66, 68, 74, 87]

Sorting a number (or a boolean) just converts it to a one-element array:

Dice> sort 3
sort 3 → [3]
Dice> sort true
sort true → [true]

Shuffle

Shuffle is the "inverse" of sort. It "sorts" an array in a random order:

Dice> shuffle [1..10]
shuffle [1..10] → [3, 8, 7, 1, 5, 2, 6, 9, 4, 10]

Here is a more elaborate example that makes a deck of 52 cards, shuffles them, and then deals out the first 5:

Dice> ranks <- [2..10] union ["J","Q","K","A"]
ranks ← [2..10] ∪ ["J", "Q", "K", "A"]
Dice> suits <- ["♠", "♡", "♢", "♣"]
suits ← ["♠", "♡", "♢", "♣"]
Dice> concat<- ranks+s
concat ← ranks + s
cards ← flatten (map concat, s, suits)
Dice> cards
cards → ["2♠", "3♠", "4♠", "5♠", "6♠", "7♠", "8♠", "9♠", "10♠", "J♠", "Q♠", "K♠", "A♠", "2♡", "3♡", "4♡", "5♡", "6♡", "7♡", "8♡", "9♡", "10♡", "J♡", "Q♡", "K♡", "A♡", "2♢", "3♢", "4♢", "5♢", "6♢", "7♢", "8♢", "9♢", "10♢", "J♢", "Q♢", "K♢", "A♢", "2♣", "3♣", "4♣", "5♣", "6♣", "7♣", "8♣", "9♣", "10♣", "J♣", "Q♣", "K♣", "A♣"]
Dice> take (shuffle cards), 5
take (shuffle cards), 5 → ["8♠", "4♡", "6♠", "7♡", "2♣"]


AUTHORS BUGS DISCUSSION LICENSE NAME NEWS README SOURCE TODO TRY VERSION

Last update: Fri Sep 23 2016