using Crayons
using StaticArrays

import Base.+,
    Base.-, Base.first, Base.in, Base.intersect, Base.isempty, Base.issubset, Base.union

export SquareSet

export bishopattacks,
    bishopattacksempty,
    filesquares,
    isempty,
    issingleton,
    kingattacks,
    knightattacks,
    onlyfirst,
    pawnattacks,
    pawnshift_n,
    pawnshift_ne,
    pawnshift_nw,
    pawnshift_s,
    pawnshift_sw,
    pawnshift_se,
    pprint,
    ranksquares,
    removefirst,
    rookattacks,
    rookattacksempty,
    queenattacks,
    queenattacksempty,
    shift_e,
    shift_n,
    shift_s,
    shift_w,
    squarecount,
    squares,
    squaresbetween,
    toarray

export SS_EMPTY,
    SS_FILE_A,
    SS_FILE_B,
    SS_FILE_C,
    SS_FILE_D,
    SS_FILE_E,
    SS_FILE_F,
    SS_FILE_G,
    SS_FILE_H,
    SS_RANK_1,
    SS_RANK_2,
    SS_RANK_3,
    SS_RANK_4,
    SS_RANK_5,
    SS_RANK_6,
    SS_RANK_7,
    SS_RANK_8

"""
    SquareSet

A type representing a set of squares on the chess board.

The most common ways of obtaining a square set are:

  * Initializing it with one or more squares, for instance `SquareSet(SQ_D4,
    SQ_E4, SQ_D5, SQ_E5)`.

  * From one of the predefined square set constants, like `SS_FILE_C` (the
    squares on the C file) or `SS_RANK_7` (the squares on the 7th rank).

  * By extracting it from a chess board. See the `Board` type for details
    about this.

  * By performing operations transforming or combining one or more square sets
    to a new square set.

The union or intersection of two sets can be computed by the functions `union`
and `intersect`, or by the corresponding binary operators `∪` and `∩`. The
complement of a square set is denoted by the unary `-` operator. The difference
between two set is obtained by the `setdiff` function or by the binary `-`
operator. Subset relationships can be tested by the `issubset` function or the
binary operator `⊆`.

To add or remove a square to a square set, use the `+` or `-` operators with
the square set as the left operand and the square as the right operand. To test
whether a square set contains a square, use `s in ss` or `s ∈ ss`.
"""
struct SquareSet
    val::UInt64
end

function Base.show(io::IO, ss::SquareSet)
    println(io, "SquareSet:")
    for ri ∈ 1:8
        r = SquareRank(ri)
        for fi ∈ 1:8
            f = SquareFile(fi)
            if Square(f, r) ∈ ss
                print(io, " # ")
            else
                print(io, " - ")
            end
        end
        if ri < 8
            println(io, "")
        end
    end
end

function Base.show(io::IO, ::MIME"text/html", ss::SquareSet)
    print(io, Chess.MIME.html(ss))
end

"""
    SquareSet(ss::Vararg{Square})

Construct a square set with the provided squares.

# Examples

```julia-repl
julia> SquareSet(SQ_A1, SQ_A2, SQ_A3, SQ_A4)
SquareSet:
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 #  -  -  -  -  -  -  -
 #  -  -  -  -  -  -  -
 #  -  -  -  -  -  -  -
 #  -  -  -  -  -  -  -
```
"""
function SquareSet(ss::Vararg{Square})
    result = UInt64(0)
    for s ∈ ss
        result |= UInt64(1) << (s.val - 1)
    end
    SquareSet(result)
end

"""
    SS_EMPTY

An empty square set, containing no squares.
"""
const SS_EMPTY = SquareSet(0x0)

"""
    SS_FILE_A

The square set containing all the squares along the A file.
"""
const SS_FILE_A = SquareSet(0xff)

"""
    SS_FILE_B

The square set containing all the squares along the B file.
"""
const SS_FILE_B = SquareSet(0xff00)

"""
    SS_FILE_C

The square set containing all the squares along the C file.
"""
const SS_FILE_C = SquareSet(0xff0000)

"""
    SS_FILE_D

The square set containing all the squares along the D file.
"""
const SS_FILE_D = SquareSet(0xff000000)

"""
    SS_FILE_E

The square set containing all the squares along the E file.
"""
const SS_FILE_E = SquareSet(0xff00000000)

"""
    SS_FILE_F

The square set containing all the squares along the F file.
"""
const SS_FILE_F = SquareSet(0xff0000000000)

"""
    SS_FILE_G

The square set containing all the squares along the G file.
"""
const SS_FILE_G = SquareSet(0xff000000000000)

"""
    SS_FILE_H

The square set containing all the squares along the H file.
"""
const SS_FILE_H = SquareSet(0xff00000000000000)

"""
    SS_RANK_1

The square set containing all the squares along the 1st rank.
"""
const SS_RANK_1 = SquareSet(0x8080808080808080)

"""
    SS_RANK_2

The square set containing all the squares along the 2nd rank.
"""
const SS_RANK_2 = SquareSet(0x4040404040404040)

"""
    SS_RANK_3

The square set containing all the squares along the 3rd rank.
"""
const SS_RANK_3 = SquareSet(0x2020202020202020)

"""
    SS_RANK_4

The square set containing all the squares along the 4th rank.
"""
const SS_RANK_4 = SquareSet(0x1010101010101010)

"""
    SS_RANK_5

The square set containing all the squares along the 5th rank.
"""
const SS_RANK_5 = SquareSet(0x0808080808080808)

"""
    SS_RANK_6

The square set containing all the squares along the 6th rank.
"""
const SS_RANK_6 = SquareSet(0x0404040404040404)

"""
    SS_RANK_7

The square set containing all the squares along the 7th rank.
"""
const SS_RANK_7 = SquareSet(0x0202020202020202)

"""
    SS_RANK_8

The square set containing all the squares along the 8th rank.
"""
const SS_RANK_8 = SquareSet(0x0101010101010101)

const FILE_SQUARES = SVector(
    SS_FILE_A,
    SS_FILE_B,
    SS_FILE_C,
    SS_FILE_D,
    SS_FILE_E,
    SS_FILE_F,
    SS_FILE_G,
    SS_FILE_H,
)

const RANK_SQUARES = SVector(
    SS_RANK_8,
    SS_RANK_7,
    SS_RANK_6,
    SS_RANK_5,
    SS_RANK_4,
    SS_RANK_3,
    SS_RANK_2,
    SS_RANK_1,
)

"""
    filesquares(f::SquareFile)

The set of all squares on the provided file.

# Examples

```julia-repl
julia> filesquares(FILE_G) == SS_FILE_G
true
```
"""
function filesquares(f::SquareFile)::SquareSet
    FILE_SQUARES[f.val]
end

"""
    ranksquares(r::SquareRank)

The set of all squares on the provided rank.

# Examples

```julia-repl
julia> ranksquares(RANK_2) == SS_RANK_2
true
```
"""
function ranksquares(r::SquareRank)::SquareSet
    RANK_SQUARES[r.val]
end

"""
    isempty(ss::SquareSet)

Determine whether a square set is the empty set.

# Examples

```julia-repl
julia> isempty(SS_RANK_1)
false

julia> isempty(SS_EMPTY)
true

julia> isempty(SS_RANK_1 ∩ SS_RANK_2)
true
```
"""
function isempty(ss::SquareSet)::Bool
    ss == SS_EMPTY
end

"""
    union(ss1::SquareSet, ss2::SquareSet)
    ∪(ss1::SquareSet, ss2::SquareSet)

Compute the union of two square sets.

The binary operator `∪` can be used instead of the named function.

# Examples

```julia-repl
julia> SS_FILE_C ∪ SS_RANK_3
SquareSet:
 -  -  #  -  -  -  -  -
 -  -  #  -  -  -  -  -
 -  -  #  -  -  -  -  -
 -  -  #  -  -  -  -  -
 -  -  #  -  -  -  -  -
 #  #  #  #  #  #  #  #
 -  -  #  -  -  -  -  -
 -  -  #  -  -  -  -  -
```
"""
function union(ss1::SquareSet, ss2::SquareSet)
    SquareSet(ss1.val | ss2.val)
end

"""
    intersect(ss1::SquareSet, ss2::SquareSet)
    ∩(ss1::SquareSet, ss2::SquareSet)

Compute the intersection of two square sets.

The binary operator `∩` can be used instead of the named function.

# Examples

```julia-repl
julia> SS_FILE_D ∩ SS_RANK_7
SquareSet:
 -  -  -  -  -  -  -  -
 -  -  -  #  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
```
"""
function intersect(ss1::SquareSet, ss2::SquareSet)
    SquareSet(ss1.val & ss2.val)
end

"""
    -(ss::SquareSet)

The complement of a square set.

# Examples

```julia-repl
julia> ss = SquareSet(SQ_C4);

julia> SQ_C4 ∈ ss
true

julia> SQ_D4 ∈ ss
false

julia> SQ_C4 ∈ -ss
false

julia> SQ_D4 ∈ -ss
true
```
"""
function -(ss::SquareSet)
    SquareSet(~ss.val)
end

"""
    setdiff(ss1::SquareSet, ss2::SquareSet)
    -(ss1::SquareSet, ss2::SquareSet)

The set of all squares that are in `ss1`, but not in `ss2`.

# Examples

```julia-repl
julia> SquareSet(SQ_A1, SQ_A2, SQ_A3, SQ_B1, SQ_B2, SQ_B3) - SS_RANK_2
SquareSet:
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 #  #  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 #  #  -  -  -  -  -  -
```
"""
function setdiff(ss1::SquareSet, ss2::SquareSet)
    ss1 ∩ -ss2
end, function -(ss1::SquareSet, ss2::SquareSet)
    setdiff(ss1, ss2)
end

"""
    +(ss::SquareSet, s::Square)

Add a square to a square set.

If a square is added that is already a member is added to the set, the set is
returned unchanged.

# Examples

```julia-repl
julia> SquareSet(SQ_A1) + SQ_H8 == SquareSet(SQ_A1, SQ_H8)
true

julia> SS_FILE_A + SQ_A1 == SS_FILE_A
true
```
"""
function +(ss::SquareSet, s::Square)
    SquareSet(ss.val | (UInt64(1) << (s.val - 1)))
end

"""
    -(ss::SquareSet, s::Square)

Remove a square from a square set.

If a non-member square is removed, the set is returned unchanged.

# Examples

```julia-repl
julia> SquareSet(SQ_A1, SQ_B1) - SQ_B1 == SquareSet(SQ_A1)
true

julia> SS_FILE_A - SQ_H8 == SS_FILE_A
true
```
"""
function -(ss::SquareSet, s::Square)
    SquareSet(ss.val & ~(UInt64(1) << (s.val - 1)))
end

"""
    issubset(ss1::SquareSet, ss2::SquareSet)
    ⊆(ss1::SquareSet, ss2::SquareSet)

Determine whether `ss1` is a subset of `ss2`.

# Examples

```julia-repl
julia> SquareSet(SQ_A1, SQ_A2) ⊆ SS_FILE_A
true

julia> SquareSet(SQ_A1, SQ_B1) ⊆ SS_FILE_A
false
```
"""
issubset(ss1::SquareSet, ss2::SquareSet) = ss1 ∩ ss2 == ss1

"""
    in(s::Square, ss::SquareSet)
    ∈(s::Square, ss::SquareSet)

Determine whether a square is a member of a square set.

# Examples

```julia-repl
julia> SQ_D7 ∈ SS_RANK_8
false

julia> SQ_D8 ∈ SS_RANK_8
true
```
"""
function in(s::Square, ss::SquareSet)::Bool
    (ss.val & (UInt64(1) << (s.val - 1))) ≠ 0
end

"""
    toarray(ss::SquareSet, T::Type{<:Number}=Float32)

Convert a square set to a two-dimensional array of the specified element type.

The returned array's columns corresponds to the files, and the rows to the
ranks. The array entries are 1 for the members of the square set, and 0
for non-members.

# Examples

```julia-repl
julia> toarray(SS_FILE_C ∪ SS_RANK_5, Int)
8×8 Array{Int64,2}:
 0  0  1  0  0  0  0  0
 0  0  1  0  0  0  0  0
 0  0  1  0  0  0  0  0
 1  1  1  1  1  1  1  1
 0  0  1  0  0  0  0  0
 0  0  1  0  0  0  0  0
 0  0  1  0  0  0  0  0
 0  0  1  0  0  0  0  0
```
"""
function toarray(ss::SquareSet, T::Type{<:Number} = Float32)::Array{T,2}
    reshape([Square(i) ∈ ss ? one(T) : zero(T) for i ∈ 1:64], 8, 8)
end

"""
    squarecount(ss::SquareSet)

The number of members of a square set.
"""
function squarecount(ss::SquareSet)::Int
    count_ones(ss.val)
end

"""
    first(ss::SquareSet)

The first square in a square set.

Returns SQ_NONE for an empty square set.
"""
function first(ss::SquareSet)::Square
    Square(trailing_zeros(ss.val) + 1)
end

"""
    removefirst(ss::SquareSet)

Remove the first member of a square set.

# Examples

```julia-repl
julia> removefirst(SquareSet(SQ_A4, SQ_D5, SQ_F6)) == SquareSet(SQ_D5, SQ_F6)
true
```
"""
function removefirst(ss::SquareSet)::SquareSet
    SquareSet(ss.val & (ss.val - 1))
end

"""
    issingleton(ss::SquareSet)

Determine whether `ss` contains exactly one square.

# Examples

```julia-repl
julia> issingleton(SquareSet(SQ_D5))
true

julia> issingleton(SquareSet(SQ_D5, SQ_C5))
false

julia> issingleton(SS_EMPTY)
false
```
"""
function issingleton(ss::SquareSet)::Bool
    ss ≠ SS_EMPTY && removefirst(ss) == SS_EMPTY
end

"""
    onlyfirst(ss::SquareSet)

Return a square set with all squares except the first removed.

# Examples

```julia-repl
julia> onlyfirst(SquareSet(SQ_A4, SQ_D5, SQ_F6)) == SquareSet(SQ_A4)
true
```
"""
function onlyfirst(ss::SquareSet)::SquareSet
    SquareSet(ss.val & -ss.val)
end

function Base.iterate(ss::SquareSet, state = ss)
    if isempty(state)
        nothing
    else
        (first(state), removefirst(state))
    end
end

"""
    squares(ss::SquareSet)

Convert a square set to a vector of squares.

# Examples

```julia-repl
julia> tostring.(squares(SS_RANK_1))
8-element Array{String,1}:
 "a1"
 "b1"
 "c1"
 "d1"
 "e1"
 "f1"
 "g1"
 "h1"
```
"""
function squares(ss::SquareSet)::Array{Square,1}
    result = Array{Square,1}()
    for s ∈ ss
        push!(result, s)
    end
    result
end

"""
    shift_n(ss::SquareSet)

Shift the square set one step in the 'north' direction.

Squares that are shifted off the edge of the board disappear.

```julia-repl
julia> shift_n(SS_RANK_2) == SS_RANK_3
true

julia> shift_n(SquareSet(SQ_D3, SQ_E4, SQ_F8)) == SquareSet(SQ_D4, SQ_E5)
true
```
"""
function shift_n(ss::SquareSet)::SquareSet
    SquareSet(ss.val >> 1) - SS_RANK_1
end

"""
    shift_s(ss::SquareSet)

Shift the square set one step in the 'south' direction.

Squares that are shifted off the edge of the board disappear.

# Examples

```julia-repl
julia> shift_s(SS_RANK_3) == SS_RANK_2
true

julia> shift_s(SquareSet(SQ_C3, SQ_D2, SQ_E1)) == SquareSet(SQ_C2, SQ_D1)
true
```
"""
function shift_s(ss::SquareSet)::SquareSet
    (SquareSet)(ss.val << 1) - SS_RANK_8
end

"""
    shift_e(ss::SquareSet)

Shift the square set one step in the 'east' direction.

Squares that are shifted off the edge of the board disappear.

# Examples

```julia-repl
julia> shift_e(SS_FILE_F) == SS_FILE_G
true

julia> shift_e(SquareSet(SQ_F5, SQ_G6, SQ_H7)) == SquareSet(SQ_G5, SQ_H6)
true
```
"""
function shift_e(ss::SquareSet)::SquareSet
    SquareSet(ss.val << 8)
end

"""
    shift_w(ss::SquareSet)

Shift the square set one step in the 'west' direction.

Squares that are shifted off the edge of the board disappear.

```julia-repl
julia> shift_w(SS_FILE_C) == SS_FILE_B
true

julia> shift_w(SquareSet(SQ_C5, SQ_B6, SQ_A7)) == SquareSet(SQ_B5, SQ_A6)
true
```
"""
function shift_w(ss::SquareSet)::SquareSet
    SquareSet(ss.val >> 8)
end

"""
    pawnshift_n(ss::SquareSet)

Shift a square set of pawns one step in the 'north' direction.

This is identical to the `shift_n` function except that `pawnshift_n` is a
little faster, but will not work for square sets containing squares on the 1st
or 8th rank.
"""
function pawnshift_n(ss::SquareSet)::SquareSet
    SquareSet(ss.val >> 1)
end

"""
    pawnshift_s(ss::SquareSet)

Shift a square set of pawns one step in the 'south' direction.

This is identical to the `shift_s` function except that `pawnshift_s` is a
little faster, but will not work for square sets containing squares on the 1st
or 8th rank.
"""
function pawnshift_s(ss::SquareSet)::SquareSet
    SquareSet(ss.val << 1)
end

"""
    pawnshift_nw(ss::SquareSet)

Shift a square set of pawns one step in the 'north west' direction.

This is identical to calling `shift_n` followed by `shift_w`, except that
`pawnshift_nw` is a little faster, but will not work for square sets
containing squares on the 1st or 8th rank.
"""
function pawnshift_nw(ss::SquareSet)::SquareSet
    SquareSet(ss.val >> 9)
end

"""
    pawnshift_ne(ss::SquareSet)

Shift a square set of pawns one step in the 'north east' direction.

This is identical to calling `shift_n` followed by `shift_e`, except that
`pawnshift_ne` is a little faster, but will not work for square sets
containing squares on the 1st or 8th rank.
"""
function pawnshift_ne(ss::SquareSet)::SquareSet
    SquareSet(ss.val << 7)
end

"""
    pawnshift_sw(ss::SquareSet)

Shift a square set of pawns one step in the 'south west' direction.

This is identical to calling `shift_s` followed by `shift_w`, except that
`pawnshift_sw` is a little faster, but will not work for square sets
containing squares on the 1st or 8th rank.
"""
function pawnshift_sw(ss::SquareSet)::SquareSet
    SquareSet(ss.val >> 7)
end

"""
    pawnshift_se(ss::SquareSet)

Shift a square set of pawns one step in the 'south east' direction.

This is identical to calling `shift_s` followed by `shift_e`, except that
`pawnshift_se` is a little faster, but will not work for square sets
containing squares on the 1st or 8th rank.
"""
function pawnshift_se(ss::SquareSet)::SquareSet
    SquareSet(ss.val << 9)
end

"""
    bishopattacks(blockers::SquareSet, s::Square)

The squares attacked by a bishop on `s`, with `blockers` being the occupied
squares.
"""
function bishopattacks(blockers::SquareSet, s::Square)::SquareSet
    SquareSet(bishopattacks(blockers.val, s.val))
end

"""
    rookattacks(blockers::SquareSet, s::Square)

The squares attacked by a rook on `s`, with `blockers` being the occupied
squares.
"""
function rookattacks(blockers::SquareSet, s::Square)::SquareSet
    SquareSet(rookattacks(blockers.val, s.val))
end

"""
    queenattacks(blockers::SquareSet, s::Square)

The squares attacked by a queen on `s`, with `blockers` being the occupied
squares.
"""
function queenattacks(blockers::SquareSet, s::Square)::SquareSet
    bishopattacks(blockers, s) ∪ rookattacks(blockers, s)
end

function computestepattacks(s, deltas)
    foldl((acc, d) -> distance(s, s + d) ≤ 2 ? acc + (s + d) : acc, deltas, init = SS_EMPTY)
end

function computeknightattacks(s::Square)::SquareSet
    computestepattacks(
        s,
        [
            2 * DELTA_N + DELTA_E,
            2 * DELTA_N + DELTA_W,
            DELTA_N + 2 * DELTA_E,
            DELTA_N + 2 * DELTA_W,
            DELTA_S + 2 * DELTA_E,
            DELTA_S + 2 * DELTA_W,
            2 * DELTA_S + DELTA_E,
            2 * DELTA_S + DELTA_W,
        ],
    )
end

function computekingattacks(s::Square)::SquareSet
    computestepattacks(
        s,
        [DELTA_NE, DELTA_N, DELTA_NW, DELTA_E, DELTA_W, DELTA_SE, DELTA_S, DELTA_SW],
    )
end

function computewpattacks(s::Square)::SquareSet
    computestepattacks(s, [DELTA_NE, DELTA_NW])
end

function computebpattacks(s::Square)::SquareSet
    computestepattacks(s, [DELTA_SE, DELTA_SW])
end

const N_ATTACKS = @SVector [computeknightattacks(Square(i)) for i ∈ 1:64]
const K_ATTACKS = @SVector [computekingattacks(Square(i)) for i ∈ 1:64]
const WP_ATTACKS = @SVector [computewpattacks(Square(i)) for i ∈ 1:64]
const BP_ATTACKS = @SVector [computebpattacks(Square(i)) for i ∈ 1:64]

"""
    knightattacks(s::Square)

The set of squares attacked by a knight on the square `s`.
"""
function knightattacks(s::Square)::SquareSet
    @inbounds N_ATTACKS[s.val]
end

"""
    kingattacks(s::square)

the set of squares attacked by a king on the square `s`.
"""
function kingattacks(s::Square)::SquareSet
    @inbounds K_ATTACKS[s.val]
end

"""
    pawnttacks(c::PieceColor, s::square)

the set of squares attacked by a pawn of color `c` on the square `s`.
"""
function pawnattacks(c::PieceColor, s::Square)::SquareSet
    if c == WHITE
        @inbounds WP_ATTACKS[s.val]
    else
        @inbounds BP_ATTACKS[s.val]
    end
end

const B_ATTACKS_EMPTY = @SVector [bishopattacks(SS_EMPTY, Square(i)) for i ∈ 1:64]

const R_ATTACKS_EMPTY = @SVector [rookattacks(SS_EMPTY, Square(i)) for i ∈ 1:64]

const Q_ATTACKS_EMPTY = @SVector [queenattacks(SS_EMPTY, Square(i)) for i ∈ 1:64]

"""
    bishopattacksempty(s::Square)

The set of squares a bishop on `s` would attack on an otherwise empty board.
"""
function bishopattacksempty(s::Square)::SquareSet
    @inbounds B_ATTACKS_EMPTY[s.val]
end

"""
    rookattacksempty(s::Square)

The set of squares a rook on `s` would attack on an otherwise empty board.
"""
function rookattacksempty(s::Square)::SquareSet
    @inbounds R_ATTACKS_EMPTY[s.val]
end

"""
    queenattacksempty(s::Square)

The set of squares a queen on `s` would attack on an otherwise empty board.
"""
function queenattacksempty(s::Square)::SquareSet
    @inbounds Q_ATTACKS_EMPTY[s.val]
end

function computesquaresbetween(s1::Square, s2::Square)::SquareSet
    if s2 ∉ queenattacksempty(s1)
        SS_EMPTY
    else
        result = SS_EMPTY
        si1 = min(s1.val, s2.val)
        si2 = max(s1.val, s2.val)
        for si3 ∈ (si1+1):(si2-1)
            s3 = Square(si3)
            ss = SquareSet(s3)
            if s2 ∈ queenattacksempty(s1) && s2 ∉ queenattacks(ss, s1)
                result += s3
            end
        end
        result
    end
end

const SQUARES_BETWEEN = [computesquaresbetween(Square(i), Square(j)) for i ∈ 1:64, j = 1:64]

"""
    squaresbetween(s1::Square, s2::Square)

The set of squares on the line, file or diagonal between `s1` and `s2`.

When a queen on `s1` would attack `s2` on an otherwise empty board, this
function returns the set of squares where a piece would block the queen
on `s1` from attacking `s2`.

# Examples

```julia-repl
julia> squaresbetween(SQ_A4, SQ_D4) == SquareSet(SQ_B4, SQ_C4)
true

julia> squaresbetween(SQ_F7, SQ_A2)
SquareSet:
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  #  -  -  -
 -  -  -  #  -  -  -  -
 -  -  #  -  -  -  -  -
 -  #  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
 -  -  -  -  -  -  -  -
```
"""
function squaresbetween(s1::Square, s2::Square)::SquareSet
    @inbounds SQUARES_BETWEEN[s1.val, s2.val]
end

function colorpprint(ss::SquareSet)
    for ri ∈ 1:8
        r = SquareRank(ri)
        for fi ∈ 1:8
            f = SquareFile(fi)
            bg = (ri + fi) % 2 == 0 ? 0x87CEFF : 0x6CA6CD
            bg = (ri + fi) % 2 == 0 ? 0x8accc0 : 0x66b0a3
            if Square(f, r) ∈ ss
                print(Crayon(background = bg, foreground = 0xff1654), " * ")
            else
                print(Crayon(background = bg), "   ")
            end
        end
        println(Crayon(reset = true), "")
    end
end

"""
    pprint(ss::SquareSet)

Pretty-print a square set to the standard output.
"""
function pprint(ss::SquareSet; color = false)
    if color
        colorpprint(ss)
        return
    end
    for ri ∈ 1:8
        r = SquareRank(ri)
        println("+---+---+---+---+---+---+---+---+")
        for fi ∈ 1:8
            f = SquareFile(fi)
            if Square(f, r) ∈ ss
                print("| # ")
            else
                print("|   ")
            end
        end
        println("|")
    end
    println("+---+---+---+---+---+---+---+---+")
end


abstract type EnergyCarrier end

mutable struct Electricity <: EnergyCarrier
    power::AbstractArray{Float64,3}
    Electricity() = new()
end

mutable struct Heat <: EnergyCarrier
    power::AbstractArray{Float64,3}
    Heat() = new()
end

mutable struct Hydrogen <: EnergyCarrier
    power::AbstractArray{Float64,3}
    Hydrogen() = new()
end


using ColorVectorSpace: AbstractGray, TransparentGray, TransparentRGB

# Specialize the low-level broadcasting machinery for ImageMeta.
# This make it work for all operators, rather than specializing
# each operator individually.
Base.BroadcastStyle(::Type{<:ImageMeta}) = Broadcast.ArrayStyle{ImageMeta}()
function Base.similar(bc::Broadcast.Broadcasted{Broadcast.ArrayStyle{ImageMeta}}, ::Type{ElType}) where ElType
    Mt = imagemeta(bc.args...)
    if length(Mt) > 0
        M = Mt[1]
        if length(Mt) > 1
            for i = 2:length(Mt)
                Mt[i] == M || return ambigop(:Broadcast)
            end
        end
        # Use the properties field of img to create the output
        ret = ImageMeta(similar(Array{ElType}, axes(bc)), properties(M))
    else
        ret = ImageMeta(similar(Array{ElType}, axes(bc)))
    end
    ret
end
# Select all the ImageMeta arrays
@inline imagemeta(As...) = _imagemeta((), As...)
_imagemeta(out) = out
@inline _imagemeta(out, A::ImageMeta, As...) = _imagemeta((out..., A), As...)
@inline _imagemeta(out, A, As...) = _imagemeta(out, As...)

(-)(img::ImageMeta) = shareproperties(img, -arraydata(img))

batch2 = (:+, :-)

for op in batch2
    @eval begin
        ($op)(img::ImageMeta, B::BitArray) = shareproperties(img, ($op)(arraydata(img), B))
        ($op)(B::BitArray, img::ImageMeta) = shareproperties(img, ($op)(B, arraydata(img)))
        ($op)(img::ImageMeta, B::ImageMeta) = ambigop(Symbol($op))
        ($op)(img::ImageMeta, B::AbstractArray) = shareproperties(img, ($op)(arraydata(img), B))
        ($op)(B::AbstractArray, img::ImageMeta) = shareproperties(img, ($op)(B, arraydata(img)))
    end

    for CV in (:AbstractGray, :TransparentGray, :AbstractRGB, :TransparentRGB)
        @eval begin
            ($op)(img::ImageMeta{CV}, n::$CV) where {CV<:$CV} =
                shareproperties(img, ($op)(arraydata(img), n))
            ($op)(n::$CV, img::ImageMeta{CV}) where {CV<:$CV} =
                shareproperties(img, ($op)(n, arraydata(img)))
        end
    end
end

(/)(img::ImageMeta, n::Number) = shareproperties(img, arraydata(img)/n)

ambigop(s::Symbol) = error("$s with two ImageMeta arrays: dictionary choice is ambiguous")


module CoffeeMachine

export Machine, Cup, makecoffee!, fillwith!, readdisplay

mutable struct Machine
    coffee::Float64
    milk::Float64
    message::String

    Machine(; coffee=0.0, milk=0.0) = new(coffee, milk, "")
end

struct Cup
    coffee::Float64
    milk::Float64
end

function fillwith!(m::Machine; coffee=0.0, milk=0.0)
    m.coffee += coffee
    m.milk += milk
end

function makecoffee!(m::Machine; withmilk=false) :: Union{Cup, Nothing}
    if m.coffee <= 0.0
        display!(m, "Out of coffee")
        return nothing
    end

    if withmilk && m.milk <= 0.0
        display!(m, "Out of milk")
        return nothing
    end

    milkincup = if withmilk
        1.0
    else
        0.0
    end

    m.coffee -= 1.0
    m.milk -= milkincup

    display!(m, "Enjoy")

    Cup(1.0, milkincup)
end

readdisplay(m::Machine) = m.message
display!(m::Machine, msg::String) = m.message = msg

end # module


### Abstract SMMALA state

abstract SMMALAState{F<:VariateForm} <: LMCSamplerState{F}

### SMMALA state subtypes

## UnvSMMALAState holds the internal state ("local variables") of the SMMALA sampler for univariate parameters

type UnvSMMALAState <: SMMALAState{Univariate}
  pstate::ParameterState{Continuous, Univariate} # Parameter state used internally by SMMALA
  tune::MCTunerState
  sqrttunestep::Real
  ratio::Real
  μ::Real
  newinvtensor::Real
  oldinvtensor::Real
  cholinvtensor::Real
  newfirstterm::Real
  oldfirstterm::Real

  function UnvSMMALAState(
    pstate::ParameterState{Continuous, Univariate},
    tune::MCTunerState,
    sqrttunestep::Real,
    ratio::Real,
    μ::Real,
    newinvtensor::Real,
    oldinvtensor::Real,
    cholinvtensor::Real,
    newfirstterm::Real,
    oldfirstterm::Real
  )
    if !isnan(ratio)
      @assert ratio > 0 "Acceptance ratio should be positive"
    end
    if !isnan(sqrttunestep)
      @assert sqrttunestep > 0 "Square root of tuned drift step is not positive"
    end
    new(pstate, tune, sqrttunestep, ratio, μ, newinvtensor, oldinvtensor, cholinvtensor, newfirstterm, oldfirstterm)
  end
end

UnvSMMALAState(pstate::ParameterState{Continuous, Univariate}, tune::MCTunerState=BasicMCTune()) =
  UnvSMMALAState(pstate, tune, NaN, NaN, NaN, NaN, NaN, NaN, NaN, NaN)

## MuvSMMALAState holds the internal state ("local variables") of the SMMALA sampler for multivariate parameters

type MuvSMMALAState <: SMMALAState{Multivariate}
  pstate::ParameterState{Continuous, Multivariate} # Parameter state used internally by SMMALA
  tune::MCTunerState
  sqrttunestep::Real
  ratio::Real
  μ::RealVector
  newinvtensor::RealMatrix
  oldinvtensor::RealMatrix
  cholinvtensor::RealLowerTriangular
  newfirstterm::RealVector
  oldfirstterm::RealVector

  function MuvSMMALAState(
    pstate::ParameterState{Continuous, Multivariate},
    tune::MCTunerState,
    sqrttunestep::Real,
    ratio::Real,
    μ::RealVector,
    newinvtensor::RealMatrix,
    oldinvtensor::RealMatrix,
    cholinvtensor::RealLowerTriangular,
    newfirstterm::RealVector,
    oldfirstterm::RealVector
  )
    if !isnan(ratio)
      @assert ratio > 0 "Acceptance ratio should be positive"
    end
    if !isnan(sqrttunestep)
      @assert sqrttunestep > 0 "Square root of tuned drift step is not positive"
    end
    new(pstate, tune, sqrttunestep, ratio, μ, newinvtensor, oldinvtensor, cholinvtensor, newfirstterm, oldfirstterm)
  end
end

MuvSMMALAState(pstate::ParameterState{Continuous, Multivariate}, tune::MCTunerState=BasicMCTune()) =
  MuvSMMALAState(
    pstate,
    tune,
    NaN,
    NaN,
    Array(eltype(pstate), pstate.size),
    Array(eltype(pstate), pstate.size, pstate.size),
    Array(eltype(pstate), pstate.size, pstate.size),
    RealLowerTriangular(Array(eltype(pstate), pstate.size, pstate.size)),
    Array(eltype(pstate), pstate.size),
    Array(eltype(pstate), pstate.size)
  )

### Metropolis-adjusted Langevin Algorithm (SMMALA)

immutable SMMALA <: LMCSampler
  driftstep::Real
  transform::Union{Function, Void} # Function for transforming metric tensor to a positive-definite matrix

  function SMMALA(driftstep::Real, transform::Union{Function, Void})
    @assert driftstep > 0 "Drift step is not positive"
    new(driftstep, transform)
  end
end

SMMALA(driftstep::Real=1.) = SMMALA(driftstep, nothing)

### Initialize SMMALA sampler

## Initialize parameter state

function initialize!(
  pstate::ParameterState{Continuous, Univariate},
  parameter::Parameter{Continuous, Univariate},
  sampler::SMMALA,
  outopts::Dict
)
  parameter.uptotensorlogtarget!(pstate)
  @assert isfinite(pstate.logtarget) "Log-target not finite: initial value out of support"
  @assert all(isfinite(pstate.gradlogtarget)) "Gradient of log-target not finite: initial values out of support"
  @assert all(isfinite(pstate.tensorlogtarget)) "Tensor of log-target not finite: initial values out of support"

  if !isempty(outopts[:diagnostics])
    pstate.diagnostickeys = copy(outopts[:diagnostics])
    pstate.diagnosticvalues = Array(Any, length(pstate.diagnostickeys))
  end
end

function initialize!(
  pstate::ParameterState{Continuous, Multivariate},
  parameter::Parameter{Continuous, Multivariate},
  sampler::SMMALA,
  outopts::Dict
)
  parameter.uptotensorlogtarget!(pstate)
  if sampler.transform != nothing
    pstate.tensorlogtarget[:, :] = sampler.transform(pstate.tensorlogtarget)
  end
  @assert isfinite(pstate.logtarget) "Log-target not finite: initial value out of support"
  @assert all(isfinite(pstate.gradlogtarget)) "Gradient of log-target not finite: initial values out of support"
  @assert all(isfinite(pstate.tensorlogtarget)) "Tensor of log-target not finite: initial values out of support"

  if !isempty(outopts[:diagnostics])
    pstate.diagnostickeys = copy(outopts[:diagnostics])
    pstate.diagnosticvalues = Array(Any, length(pstate.diagnostickeys))
  end
end

## Initialize SMMALA state

function sampler_state(
  parameter::Parameter{Continuous, Univariate},
  sampler::SMMALA,
  tuner::MCTuner,
  pstate::ParameterState{Continuous, Univariate},
  vstate::VariableStateVector
)
  sstate = UnvSMMALAState(generate_empty(pstate), tuner_state(parameter, sampler, tuner))
  sstate.sqrttunestep = sqrt(sstate.tune.step)
  sstate.oldinvtensor = inv(pstate.tensorlogtarget)
  sstate.cholinvtensor = chol(sstate.oldinvtensor, :L)
  sstate.oldfirstterm = sstate.oldinvtensor*pstate.gradlogtarget
  sstate
end

function sampler_state(
  parameter::Parameter{Continuous, Multivariate},
  sampler::SMMALA,
  tuner::MCTuner,
  pstate::ParameterState{Continuous, Multivariate},
  vstate::VariableStateVector
)
  sstate = MuvSMMALAState(generate_empty(pstate), tuner_state(parameter, sampler, tuner))
  sstate.sqrttunestep = sqrt(sstate.tune.step)
  sstate.oldinvtensor[:, :] = inv(pstate.tensorlogtarget)
  sstate.cholinvtensor[:, :] = chol(sstate.oldinvtensor, Val{:L})
  sstate.oldfirstterm[:] = sstate.oldinvtensor*pstate.gradlogtarget
  sstate
end

### Reset SMMALA sampler

## Reset parameter state

function reset!(
  pstate::ParameterState{Continuous, Univariate},
  x::Real,
  parameter::Parameter{Continuous, Univariate},
  sampler::SMMALA
)
  pstate.value = x
  parameter.uptotensorlogtarget!(pstate)
end

function reset!(
  pstate::ParameterState{Continuous, Multivariate},
  x::RealVector,
  parameter::Parameter{Continuous, Multivariate},
  sampler::SMMALA
)
  pstate.value = copy(x)
  parameter.uptotensorlogtarget!(pstate)
end

## Reset sampler state

function reset!(
  sstate::UnvSMMALAState,
  pstate::ParameterState{Continuous, Univariate},
  parameter::Parameter{Continuous, Univariate},
  sampler::SMMALA,
  tuner::MCTuner
)
  reset!(sstate.tune, sampler, tuner)
  sstate.sqrttunestep = sqrt(sstate.tune.step)
  sstate.oldinvtensor = inv(pstate.tensorlogtarget)
  sstate.cholinvtensor = chol(sstate.oldinvtensor, :L)
  sstate.oldfirstterm = sstate.oldinvtensor*pstate.gradlogtarget
end

function reset!(
  sstate::MuvSMMALAState,
  pstate::ParameterState{Continuous, Multivariate},
  parameter::Parameter{Continuous, Multivariate},
  sampler::SMMALA,
  tuner::MCTuner
)
  reset!(sstate.tune, sampler, tuner)
  sstate.sqrttunestep = sqrt(sstate.tune.step)
  sstate.oldinvtensor[:, :] = inv(pstate.tensorlogtarget)
  sstate.cholinvtensor[:, :] = chol(sstate.oldinvtensor, Val{:L})
  sstate.oldfirstterm[:] = sstate.oldinvtensor*pstate.gradlogtarget
end

Base.show(io::IO, sampler::SMMALA) = print(io, "SMMALA sampler: drift step = $(sampler.driftstep)")

Base.writemime(io::IO, ::MIME"text/plain", sampler::SMMALA) = show(io, sampler)


#!/usr/bin/julia

# Sidef concept expressed in Julia

import Base.sqrt
import Base./;

mutable struct S_Number
    value::Number;

    function S_Number(x::Number)
        this = new();
        this.value = x;
        return this;
    end
end

# S_number / S_Number
function /(x::S_Number, y::S_Number)
    S_Number(x.value / y.value);
end

# int(S_Number)
function int(x::S_Number)
    S_Number(round(Int, x.value));
end

# sqrt(S_Number)
function sqrt(x::S_Number)
    S_Number(sqrt(x.value));
end

n1 = S_Number(42);
n2 = S_Number(2);

println(n1);
println(n2);

println(n1 / n2);

method = Symbol("/");
println(eval(:($method(n1, n2))));

#########################################

d = Symbol("/");
x = S_Number(12);
y = S_Number(4);
println("$x / $y == ", eval(:($d(x, y))));

code = "25.3->int->sqrt";

if (m = match(r"\G(\d+(?:\.\d+)?)", code)) != nothing
    str = m.captures[1];
    n = S_Number(parse(Float64, str));
    pos = length(str);

    while (m = match(r"\G(->|\.)", code, pos)) != nothing
        pos += length(m.captures[1]);
        if (m = match(r"\G(\w+)", code, pos)) != nothing
            cap = m.captures[1];
            pos += length(cap);
            method = Symbol(cap);
            print(n, "->", cap);
            n = eval(:($method(n)));
            println(" == ", n);
        end
    end

end

abstract type Animal
end

struct Horse <: Animal
  name::String
end

run(::Animal) = println("Run, animal, run!")
sleep(a::Animal) = println("It's late; ", a.name, " is going to bed...")

function //(z::Horse)
    println("Run horsie, run!")
    println("Horsie's name is ", z.name, ".")
end

foo = Horse("Hashy");
//(foo);

sleep(Horse("Hashy"))


@enum RobotCmd::Cint begin
    NO_COMMAND = -1

    # Commands to increase/decrease the value of scalar parameters:
    # velocity, yaw, height, pitch, and roll
    INCREASE_VELOCITY   # Increase forward trot
    DECREASE_VELOCITY   # Decrease forward trot (reverse)
    INCREASE_YAW        # Veer right
    DECREASE_YAW        # Veer left
    INCREASE_HEIGHT     # Increase ride height
    DECREASE_HEIGHT     # Decrease ride height
    PITCH_NOSE_UP       # Nose up, tail down
    PITCH_NOSE_DOWN     # Nose down, tail up
    ROLL_LEFT           # Roll body to the left
    ROLL_RIGHT          # Roll body to the right

    # Commands to change the behavior state
    TOGGLE_ACTIVATION   # DEACTIVATED->REST, REST->DEACTIVATED
    TOGGLE_TROT         # REST->TROT, TROT->REST, HOP->TROT, FINISHHOP->TROT
    CYCLE_HOP           # REST->HOP, HOP->FINISHHOP, FINISHHOP->REST, TROT->HOP

    # Synthesized commands - not currently directly accessible via joystick
    TURN_LEFT           # Turn left while trotting in place
    TURN_RIGHT          # Turn right while trotting in place
end

function execute_robotcmd(s::mjSim, robotcmd::RobotCmd)
    c = s.robot.command # shorthand
    # set_height!, adjust_height!

    # Velocity PgUp / PgDn
    if robotcmd == INCREASE_VELOCITY        c.horizontal_velocity[1] += 0.01
    elseif robotcmd == DECREASE_VELOCITY    c.horizontal_velocity[1] -= 0.01

    # Height Home / End
    elseif robotcmd == INCREASE_HEIGHT      adjust_height!(s.robot, +1)
    elseif robotcmd == DECREASE_HEIGHT      adjust_height!(s.robot, -1)

    # Yaw left / right arrow
    elseif robotcmd == INCREASE_YAW         c.yaw_rate -= 0.02
    elseif robotcmd == DECREASE_YAW         c.yaw_rate += 0.02

    # Pitch up / down arrow
    elseif robotcmd == PITCH_NOSE_UP        set_pitch!(s.robot, -1)
    elseif robotcmd == PITCH_NOSE_DOWN      set_pitch!(s.robot, +1)

    # Roll left (/) / right (+)
    elseif robotcmd == ROLL_LEFT            adjust_roll!(s.robot, -1)
    elseif robotcmd == ROLL_RIGHT           adjust_roll!(s.robot, +1)

    # Toggle activate (-) / trot (+) / hop (Enter)
    elseif robotcmd == TOGGLE_ACTIVATION    toggle_activate(s.robot)
    elseif robotcmd == TOGGLE_TROT          toggle_trot(s.robot)
    elseif robotcmd == CYCLE_HOP            toggle_hop(s.robot)

    # Turn left/right (0/.)
    elseif robotcmd == TURN_LEFT            turn_left(s.robot)
    elseif robotcmd == TURN_RIGHT           turn_right(s.robot)
    end
end


module NorahPhd
include("./Algorithm1.jl")
# include("../Examples/Example1.jl")
# c1 = [1; 2]
# c = [1; 1]
# z1 = [1; 0]
# z2 = [0; 1]
# C = [c1 c]
# Z = [z1 z2]
# Xbar, Tbar, V, p = primal(Γ, P1, P2, D2, C, Z, ϵ = 0.05)
# println(Vertex2D.(V))

# include("../Examples/Example2.jl")
# c1 = [1; 0]
# c = [1; 1]
# z1 = [1; 0]
# z2 = [0; 1]
# C = [c1 c]
# Z = [z1 z2]
# # v0 = [0; 0]
# # # w, u = D2(v0)
# # println(P1(z1))
# Xbar, Tbar, V, p = primal(Γ, P1, P2, D2, C, Z, ϵ = 0.01)
end


################################################################################
# This set of functions prepares OpenMendel to evaluate the likelihood
# of a collection of pedigrees by the Elston-Stewart algorithm.
################################################################################
#
# Required OpenMendel packages and modules.
#
# using DataStructures
# using GeneralUtilities
# using GeneticUtilities
# using ReadData

mutable struct Instruction
  instructions :: Int
  instructstart :: Vector{Int}
  instructfinish :: Vector{Int}
  operation :: Vector{Int}
  site :: Vector{Int}
  rankarray :: Vector{Int}
  space :: Vector{Int}
  extra :: Array{Vector{Int}, 1}
end # mutable struct Instruction

export Instruction
export allele_consolidation, genotype_elimination!
export orchestrate_likelihood, model_recombination_fractions

penetrance_and_prior_array = 1
transmission_array         = 2
pure_add                   = 3
multiply_and_add           = 4
pure_multiply              = 5
quit_processing_pedigree   = 6

export penetrance_and_prior_array, transmission_array, pure_add
export multiply_and_add, pure_multiply, quit_processing_pedigree

"""
Orchestrate allele lumping, genotype consolidation,
and creation of the Elston-Stewart instructions.
"""
function orchestrate_likelihood(pedigree::Pedigree, person::Person,
  nuclear_family::NuclearFamily, locus::Locus,
  keyword::Dict{AbstractString, Any})

  pedigrees = pedigree.pedigrees
  populations = person.populations
  model_loci = locus.model_loci
  eliminate_genotypes = keyword["eliminate_genotypes"]
  lump_alleles = keyword["lump_alleles"]
  product_mode = keyword["product_mode"]
  complexity_threshold = keyword["complexity_threshold"]
  #
  # Define the frequency of the lumped allele at each model locus.
  #
##
#   for i = 1:person.people
#     for l = 1:locus.loci
#       println(person.name[i],"  ",locus.name[l],"  ",person.genotype[i, l])
#     end
#   end
  if lump_alleles
    lumped_frequency = zeros(pedigrees, populations, model_loci)
    for l = 1:model_loci
      loc = locus.model_locus[l]
##
      if loc != locus.mutable_locus
##println(" Allele lumping at locus = ",locus.name[loc])
        lumped_frequency[:, :, l] =
          allele_consolidation(pedigree, person, locus, loc)
      end
    end
  else
    lumped_frequency = zeros(0, 0, 0)
  end
  #
  # Perform genotype elimination over the model loci.
  #
  if eliminate_genotypes
    for l = 1:model_loci
      loc = locus.model_locus[l]
##
      if loc != locus.mutable_locus
##println(" Genotype elimination at locus = ",locus.name[loc])
        genotype_elimination!(pedigree, person, nuclear_family, locus, loc)
      end
    end
  end
  #
  # Find an upper bound on the number of instructions.
  #
  bound = person.people
  if product_mode
    bound = bound * model_loci
  end
  bound = 6 * bound + pedigrees
  #
  # Allocate the components of the instruction vector and the first
  # and last instruction associated with each pedigree.
  #
  elston_stewart_count = zeros(pedigrees)
  instruction_start = zeros(Int, pedigrees)
  instruction_finish = zeros(Int, pedigrees)
  operation = zeros(Int, bound)
  site = zeros(Int, bound)
  rankarray = zeros(Int, bound)
  space = zeros(Int, bound)
  extra = Array{Array{Int, 1}}(undef, bound)
  for i = 1:bound
    extra[i] = zeros(Int, 0)
  end
  #
  # Find a good summation sequence.
  #
  instructions = prepare_elston_stewart(pedigree, person, locus,
    elston_stewart_count, instruction_start, instruction_finish, operation,
    site, rankarray, space, extra, bound, product_mode, complexity_threshold)
  #
  # Return the instructions for the Elston-Stewart algorithm.
  #
  instruction = Instruction(instructions, instruction_start,
    instruction_finish, operation, site, rankarray, space, extra)
  locus.lumped_frequency = lumped_frequency
  return (instruction, elston_stewart_count)
end  # function orchestrate_likelihood

"""
Consolidate alleles in each pedigree at locus number loc.
The lumped allele is numbered alleles + 1,
where alleles counts the number of alleles at the locus.
The frequency of the lumped allele and the lumping status
of each pedigree is returned along with the altered
genotype set for each person.
Warning: Allele consolidation should precede genotype elimination.
"""
function allele_consolidation(pedigree::Pedigree, person::Person,
  locus::Locus, loc::Int)
  #
  # Initialize variables.
  #
  xlinked = locus.xlinked[loc]
  pedigrees = pedigree.pedigrees
  (populations, alleles) = size(locus.frequency[loc])
  maximum_genotypes = alleles^2
  lumped_allele = alleles + 1
  lumped = falses(pedigrees)
  lumped_frequency = ones(pedigrees, populations)
  #
  # Find the alleles observed or partially observed in
  # the current pedigree.
  #
  for ped = 1:pedigrees
    observed = BitSet()
    for i = pedigree.pedstart[ped]:pedigree.twin_finish[ped]
      if xlinked && person.male[i]
        if length(person.genotype[i, loc]) < alleles
          for (gm, gp) in person.genotype[i, loc]
            push!(observed, gm)
          end
        end
      else
        if length(person.genotype[i, loc]) < maximum_genotypes
          for (gm, gp) in person.genotype[i, loc]
            push!(observed, gm)
            push!(observed, gp)
          end
        end
      end
    end
    #
    # Find the frequency of the lumped allele.
    #
    if length(observed) < alleles - 1
      lumped[ped] = true
      for a in observed
        f = locus.frequency[loc][:, a]
        lumped_frequency[ped, :] = lumped_frequency[ped, :] - f
      end
    else
      lumped_frequency[ped, :] .= 0.0
    end
    #
    # Construct a new genotype set for each person, replacing
    # each unobserved allele by the lumped allele.
    #
    for i = pedigree.pedstart[ped]:pedigree.twin_finish[ped]
      new_genotype_set = Set{Tuple{Int, Int}}()
      for (gm, gp) in person.genotype[i, loc]
        if in(gm, observed)
          hm = gm
        else
          hm = lumped_allele
        end
        if in(gp, observed)
          hp = gp
        else
          hp = lumped_allele
        end
        push!(new_genotype_set, (hm, hp))
      end
      person.genotype[i, loc] = deepcopy(new_genotype_set)
    end
  end
  #
  # Recount the number of homozygous genotypes per person and return
  # the lumped genotypes and lumped allele frequencies.
  #
  count_homozygotes!(person, loc)
  return lumped_frequency
end # function allele_consolidation

"""
Perform genotype elimination at locus number loc.
Genotypes for person i are stored in the set person.genotype[i, loc].
"""
function genotype_elimination!(pedigree::Pedigree, person::Person,
  nuclear_family::NuclearFamily, locus::Locus, loc::Int)

  people = person.people
  pedigrees = pedigree.pedigrees
  families = nuclear_family.families
  xlinked = locus.xlinked[loc]
  stable_pedigree = falses(pedigrees)
  stable_family = falses(families)
  saved_genotype = Array{Set{Tuple{Int, Int}}}(undef, people)
  #
  # Begin elimination for locus loc.
  #
  progress = true
  while progress
  #
  # Loop over all nuclear families.
  #
    for fam = 1:families
      stable_family[fam] = true
      ped = nuclear_family.pedigree[fam]
      if stable_pedigree[ped]; continue; end
      #
      # Initialize the saved sets of genotypes to be empty.
      #
      mom = nuclear_family.mother[fam]
      dad = nuclear_family.father[fam]
      saved_genotype[mom] = Set{Tuple{Int, Int}}()
      saved_genotype[dad] = Set{Tuple{Int, Int}}()
      for sib in nuclear_family.sib[fam]
        saved_genotype[sib] = Set{Tuple{Int, Int}}()
      end
      #
      # Loop over all combinations of maternal and paternal genotypes.
      #
      for (m1, m2) in person.genotype[mom, loc]
        for (d1, d2) in person.genotype[dad, loc]
          family_match = true
          #
          # Loop over all siblings.
          #
          for sib in nuclear_family.sib[fam]
            #
            # Loop over all genotypes of the current sib.
            #
            sib_match = false
            for (s1, s2) in person.genotype[sib, loc]
              #
              # Check for compatibility between the sib genotype and
              # the parental genotypes.
              #
              maternal_match = (s1 == m1 || s1 == m2)
              if !xlinked || !person.male[sib]
                paternal_match = (s2 == d1 || s2 == d2)
                sib_match = maternal_match && paternal_match
              else
                sib_match = maternal_match
              end
              #
              # As soon as a compatible sib genotype is found, stop looking.
              #
              if sib_match; break; end
            end  # sib genotype loop
            #
            # If no genotype of the current sib matches, exit.
            #
            family_match = family_match && sib_match
            if !family_match; break; end
          end  # sib loop
          #
          # If all sibs provide a match, save all matching parental and
          # sibling genotypes.
          #
          if !family_match; continue; end
          push!(saved_genotype[mom], (m1, m2))
          push!(saved_genotype[dad], (d1, d2))
          for sib in nuclear_family.sib[fam]
            for (s1, s2) in person.genotype[sib, loc]
              maternal_match = (s1 == m1 || s1 == m2)
              if !xlinked || !person.male[sib]
                paternal_match = (s2 == d1 || s2 == d2)
              else
                paternal_match = true
              end
              if maternal_match && paternal_match
                push!(saved_genotype[sib], (s1, s2))
              end
            end
          end  # sib loop
        end  # dad loop
      end  # mom loop
      #
      # Replace the current genotype sets in the family by the saved
      # genotype sets.
      #
      if length(saved_genotype[mom]) < length(person.genotype[mom, loc])
        stable_family[fam] = false
        person.genotype[mom, loc] = intersect(saved_genotype[mom],
          person.genotype[mom, loc])
      end
      if length(saved_genotype[dad]) < length(person.genotype[dad, loc])
        stable_family[fam] = false
        person.genotype[dad, loc] = intersect(saved_genotype[dad],
          person.genotype[dad, loc])
      end
      for sib in nuclear_family.sib[fam]
        if length(saved_genotype[sib]) < length(person.genotype[sib, loc])
          stable_family[fam] = false
          person.genotype[sib, loc] = intersect(saved_genotype[sib],
            person.genotype[sib, loc])
        end
      end  # sib loop
    end  # family loop
    #
    # Genotype elimination continues until genotypes stabilize
    # across all pedigrees.
    #
    fill!(stable_pedigree, true)
    for fam = 1:families
      ped = nuclear_family.pedigree[fam]
      stable_pedigree[ped] = stable_pedigree[ped] && stable_family[fam]
    end
    progress = !all(stable_pedigree)
  end # progress loop
  #
  # Recount the number of homozygous genotypes per person.
  #
  count_homozygotes!(person, loc)
end # function genotype_elimination!

"""
Specify the steps of the Elston-Stewart algorithm for each pedigree.
"""
function prepare_elston_stewart(pedigree::Pedigree, person::Person,
  locus::Locus, elston_stewart_count::Vector{Float64},
  instruction_start::Vector{Int}, instruction_finish::Vector{Int},
  operation::Vector{Int}, site::Vector{Int}, rankarray::Vector{Int},
  space::Vector{Int}, extra::Vector{Vector{Int}}, bound::Int,
  product_mode::Bool, complexity_threshold::Float64)

  pedigrees = pedigree.pedigrees
  model_loci = locus.model_loci
  free_recombination = locus.free_recombination
  instructions = 0
  #
  # Record the homozygote counts at the model loci.
  #
  homozygotes = zeros(Int, person.people, model_loci)
  for l = 1:model_loci
    homozygotes[:, l] = person.homozygotes[:, locus.model_locus[l]]
  end
  #
  # Create the instructions pedigree by pedigree.
  #
  for ped = 1:pedigrees
    ped_start = pedigree.pedstart[ped]
    ped_finish = pedigree.pedfinish[ped]
    ped_size = ped_finish - ped_start + 1
    pedigree_product_mode = product_mode
    #
    # Set up the adjacency graph of the current pedigree. From the adjacency
    # graph, determine a summation sequence by the greedy algorithm.
    # Note that greedy changes the set array graph.
    #
    (nodes, graph, weight) = adjacency_graph(pedigree, person, locus,
      homozygotes, pedigree_product_mode, ped, model_loci, free_recombination)
    (lowest_cost_sum, permutation) = greedy(nodes, graph, weight, ped_size)
    #
    # When likelihood evaluation can proceed in product mode, check if
    # non-product mode will do better.
    #
    if product_mode && !free_recombination
      product_mode_cost = lowest_cost_sum
      pedigree_product_mode = false
      (nodes, graph, weight) = adjacency_graph(pedigree, person, locus,
        homozygotes, pedigree_product_mode, ped, model_loci, free_recombination)
      (lowest_cost_sum, permutation) = greedy(nodes, graph, weight, ped_size)
      #
      # When product mode is superior, revert to it.
      #
      if lowest_cost_sum > product_mode_cost
        pedigree_product_mode = true
        (nodes, graph, weight) = adjacency_graph(pedigree, person, locus,
          homozygotes, pedigree_product_mode, ped, model_loci,
          free_recombination)
        (lowest_cost_sum, permutation) = greedy(nodes, graph, weight, ped_size)
      end
    end
    #
    # Decide whether the current pedigree is too demanding for likelihood
    # computation. If not, set up the index sets corresponding to the
    # various arrays encountered in likelihood evaluation.
    #
    if lowest_cost_sum <= complexity_threshold
      (work_arrays, index_set) = create_index_set(pedigree, person, locus,
        nodes, ped, pedigree_product_mode)
      #
      # Prepare the instructions for the Elston-Stewart algorithm.
      #
      instruction_start[ped] = instructions + 1
      elston_stewart_count[ped] = 0.0
      instructions = prepare_instructions(pedigree, person, locus,
        elston_stewart_count, index_set, weight, permutation, extra,
        operation, rankarray, space, site, work_arrays, instructions, nodes,
        bound, ped, pedigree_product_mode)
      instruction_finish[ped] = instructions
    #
    # If the current pedigree is too complex, set the Elston-Stewart count
    # equal to the lowest cost sum.
    #
    else
      instructions = instructions + 1
      instruction_start[ped] = instructions
      elston_stewart_count[ped] = lowest_cost_sum
      instruction_finish[ped] = instructions
    end
  end
  return instructions
end # function prepare_elston_stewart

"""
Define the adjacency graph connecting the indices of summation.
"""
function adjacency_graph(pedigree::Pedigree, person::Person, locus::Locus,
  homozygotes::Matrix{Int}, pedigree_product_mode::Bool, ped::Int,
  model_loci::Int, free_recombination::Bool)

  ped_start = pedigree.pedstart[ped]
  ped_finish = pedigree.pedfinish[ped]
  ped_size = ped_finish - ped_start + 1
  offset = ped_start - 1
  #
  # In product mode, nodes of the adjacency graph are person-locus pairs.
  # In non-product mode, nodes are simply people.
  #
  if pedigree_product_mode
    nodes = ped_size * model_loci
  else
    nodes = ped_size
  end
  #
  # Each node has a set of neighbors graph[node]. The weight of a node equals
  # the number of its associated genotypes or multi-locus genotypes.
  #
  weight = ones(Int, nodes)
  graph = empties(nodes)
  #
  # In product mode, loop over all person-locus pairs. Number nodes by
  # putting them in a matrix with loci on rows and people on columns.
  # A node is numbered by the its column-major position in the matrix.
  #
  if pedigree_product_mode
    for i = ped_start:ped_finish
      mom = person.mother[i]
      dad = person.father[i]
      for j = 1:model_loci
        jloc = locus.model_locus[j]
        node = (i - ped_start) * model_loci + j
        weight[node] = length(person.genotype[i, jloc])
        for k = 1:model_loci
          kloc = locus.model_locus[k]
          self_connected = false
          if mom != 0
            if !loci_split(homozygotes[mom, :], j, k, free_recombination)
              if j != k; self_connected = true; end
              push!(graph[node], (mom - ped_start) * model_loci + k)
            end
          end
          if dad != 0
            if !loci_split(homozygotes[dad, :], j, k, free_recombination)
              if j != k; self_connected = true; end
              push!(graph[node], (dad - ped_start) * model_loci + k)
            end
          end
          if length(person.children[i]) > 0 &&
            !loci_split(homozygotes[i, :], j, k, free_recombination)
            if j != k; self_connected = true; end
            for kid in person.children[i]
              push!(graph[node], (kid - ped_start) * model_loci + k)
            end
          end
          if self_connected
            push!(graph[node], (i - ped_start) * model_loci + k)
          end
        end
      end
    end
  else
  #
  # In non-product mode, two people are neighbors if and only if one
  # is the parent of the other. If this is true, then they occur as
  # indices in the same transmission array. To insure that nodes are
  # numbered starting with 1, subtract an offset from each person number.
  #
    for node = 1:nodes
      i = node + offset
      for kid in person.children[i]
        push!(graph[node], kid - offset)
      end
      if person.mother[i] != 0
        push!(graph[node], person.mother[i] - offset)
        push!(graph[node], person.father[i] - offset)
      end
      for l = 1:model_loci
        loc = locus.model_locus[l]
        weight[node] = weight[node] * length(person.genotype[i, loc])
      end
    end
  end
  return (nodes, graph, weight)
end # function adjacency_graph

"""
Decide if two model loci are split within person i
in the sense of being separated by an obligate heterozygous locus.
Under free recombination, the likelihood factors over different loci.
"""
function loci_split(homozygotes, locus_1::Int, locus_2::Int,
                    free_recombination::Bool)

  if free_recombination
    split_loci = locus_1 != locus_2
  else
    split_loci = false
    for loc = min(locus_1, locus_2) + 1:max(locus_1, locus_2) - 1
      if homozygotes[loc] == 0
        return true
      end
    end
  end
  return split_loci
end # function loci_split

"""
Select a good summation sequence by the greedy method
and store it in a permutation. Observe that the cost
of removing a node from the adjacency graph is a simple surrogate
for the number of arithmetic operations involved in summing over
the genotypes possible for the node.
"""
function greedy(nodes::Int, graph::Vector{BitSet},
                weight::Vector{Int}, ped_size::Int)

  log_cost = zeros(nodes)
  log_weight = zeros(nodes)
  #
  # Initialize the permutation and the log weight attached to each node.
  #
  permutation = collect(1:nodes)
  for node = 1:nodes
    log_weight[node] = log(weight[node])
  end
  lowest_cost_sum = 0.0
  #
  # Compute the log cost of eliminating a node.
  #
  for node = 1:nodes
    log_sum = log_weight[node]
    for element in graph[node]
      log_sum = log_sum + log_weight[element]
    end
    log_cost[node] = log_sum
  end
  #
  # Find the lowest cost node to eliminate. Note that the variable last
  # limits the search to nodes representing the current locus when we are
  # in product mode. Nodes are therefore considered in clumps starting
  # with the leftmost locus and ending with the rightmost locus.
  #
  for i = 1:nodes
    lowest_cost = log_cost[permutation[i]]
    lowest_cost_index = i
    last = (div(i - 1, ped_size) + 1) * ped_size
    for j = i + 1:last
      competing_cost = log_cost[permutation[j]]
      if competing_cost < lowest_cost
        (lowest_cost, lowest_cost_index) = (competing_cost, j)
      end
    end
    low = exp(lowest_cost)
    lowest_cost_sum = lowest_cost_sum + min(low, 1e20)
    #
    # Insert the lowest cost node into the permutation.
    #
    (permutation[i], permutation[lowest_cost_index]) =
      (permutation[lowest_cost_index], permutation[i])
    best = permutation[i]
    #
    # Expand the neighborhood of each neighbor of the lowest cost node and
    # Delete the lowest cost node and neighbor from each revised neighborhood.
    #
    for neighbor in graph[best]
      graph[neighbor] = union(graph[neighbor], graph[best])
      delete!(graph[neighbor], best)
      delete!(graph[neighbor], neighbor)
      #
      # Recompute the log cost of removing each neighbor.
      #
      log_sum = log_weight[neighbor]
      for element in graph[neighbor]
        log_sum = log_sum + log_weight[element]
      end
      log_cost[neighbor] = log_sum
    end
  end
  return (lowest_cost_sum, permutation)
end  # function greedy

"""
Create the index sets for all initial arrays in likelihood computation.
"""
function create_index_set(pedigree::Pedigree, person::Person,
  locus::Locus, nodes::Int, ped::Int, pedigree_product_mode::Bool)
  #
  # Allocate the index sets and a variable n tracking the current index set.
  #
  model_loci = locus.model_loci
  free_recombination = locus.free_recombination
  ped_start = pedigree.pedstart[ped]
  ped_finish = pedigree.pedfinish[ped]
  offset = ped_start - 1
  index_set = empties(6 * nodes)
  n = 0
  #
  # In product mode, loop over all person-locus pairs. Prepare for the
  # penetrance-prior arrays first.
  #
  if pedigree_product_mode
    for i = ped_start:ped_finish
      for l = 1:model_loci
        n = n + 1
        push!(index_set[n], (i - ped_start) * model_loci + l)
      end
    end
    #
    # Next prepare for the transmission arrays by looping over all children.
    # Transmission arrays will be considered parent by parent.
    #
    for i = ped_start:ped_finish
      if person.mother[i] != 0
        for parent = 1:2
          if parent == 1
            j = person.mother[i]
          else
            j = person.father[i]
          end
          #
          # Find a first and last locus in a block of loci not split by
          # an obligate heterozygous locus in the parent.
          #
          (first, last) = (1, 0)
          while last < model_loci
          #
          # Under free recombination, each transmission arrays completely
          # factors.
          #
            if free_recombination
              last = first
            else
              last = model_loci
              for l = first + 1:model_loci
                loc = locus.model_locus[l]
                if person.homozygotes[j, loc] == 0
                  last = l
                  break
                end
              end
            end
            #
            # Insert the parent-locus pairs into the index set before the
            # child-locus pairs.
            #
            n = n + 1
            spread = last - first + 1
            for l = first:last
              push!(index_set[n], (j - ped_start) * model_loci + l)
              push!(index_set[n], (i - ped_start) * model_loci + l)
            end
            #
            # Exit if the last locus has been hit. Otherwise, reset the
            # first locus to the current last locus and find another block
            # of loci.
            #
            if last == model_loci; break; end
            if free_recombination; last = first + 1; end
            first = last
          end
        end
      end
    end
  else
    #
    # In nonproduct mode, prepare for the penetrance-prior arrays first.
    #
    for i = ped_start:ped_finish
      n = n + 1
      push!(index_set[n], i - offset)
    end
    #
    # Next prepare for the transmission arrays by looping over all children.
    #
    for i = ped_start:ped_finish
      if person.mother[i] != 0
        n = n + 1
        push!(index_set[n], person.mother[i] - offset)
        push!(index_set[n], i - offset)
        n = n + 1
        push!(index_set[n], person.father[i] - offset)
        push!(index_set[n], i - offset)
      end
    end
  end
  #
  # Return the initial index sets.
  #
  return (n, index_set)
end # function create_index_set

"""
Prepare the instructions for performing the Elston-Stewart algorithm.
"""
function prepare_instructions(pedigree::Pedigree, person::Person, locus::Locus,
  elston_stewart_count::Vector{Float64}, index_set::Vector{BitSet},
  weight::Vector{Int}, permutation::Vector{Int}, extra::Vector{Vector{Int}},
  operation::Vector{Int}, rankarray::Vector{Int}, space::Vector{Int},
  site::Vector{Int}, work_arrays::Int, instructions::Int, nodes::Int,
  bound::Int, ped::Int, pedigree_product_mode::Bool)

  model_loci = locus.model_loci
  ped_start = pedigree.pedstart[ped]
  offset = ped_start - 1
  #
  # Let n be the index of the current instruction. Prepare a set
  # to hold which arrays are involved in the current operation.
  #
  n = instructions
  active_array = trues(6 * nodes)
  involved = BitSet()
  union_set = BitSet()
  #
  # Give the instructions for creating the initial arrays.
  #
  for i = 1:work_arrays
    n = n + 1
    if i <= nodes
      operation[n] = penetrance_and_prior_array
      extra[n] = zeros(Int, 3)
      #
      # Load the starting and finishing loci and the person involved
      # in a penetrance and prior array.
      #
      if pedigree_product_mode
        j = first(index_set[i]) - 1
        extra[n][1] = mod(j, model_loci) + 1
        extra[n][2] = extra[n][1]
        extra[n][3] = div(j, model_loci) + ped_start
      else
        extra[n][1] = 1
        extra[n][2] = model_loci
        extra[n][3] = first(index_set[i]) + offset
      end
    else
      operation[n] = transmission_array
      extra[n] = zeros(Int, 4)
      #
      # Load the starting and finishing loci and the parent and child
      # involved in a transmission array.
      #
      if pedigree_product_mode
        j = first(index_set[i]) - 1
        k = last(index_set[i]) - 1
        extra[n][1] = mod(j, model_loci) + 1
        extra[n][2] = mod(k, model_loci) + 1
        extra[n][3] = div(j, model_loci) + ped_start
        extra[n][4] = div(k, model_loci) + ped_start
      else
        extra[n][1] = 1
        extra[n][2] = model_loci
        extra[n][3] = first(index_set[i]) + offset
        extra[n][4] = last(index_set[i]) + offset
      end
    end
    #
    # Load the site, the rank, and the size of the current array.
    #
    site[n] = i
    rankarray[n] = length(index_set[i])
    space[n] = product_weights(index_set[i], weight)
  end
  #
  # Give the instructions for creating and destroying arrays in computing
  # the likelihood of the current pedigree.
  #
  for node = 1:nodes
    i = permutation[node]
    #
    # Search through the index sets and find those containing the node i
    # to be eliminated.
    #
    for iteration = 1:100000
      involved = BitSet()
      for k = 1:work_arrays
        if active_array[k] && i in index_set[k]
          push!(involved, k)
        end
      end
      #
      # Exit the loop if no index containing i remains.
      #
      if isempty(involved); break; end
      #
      # Update the instruction index.
      #
      n = n + 1
      #
      # For a pure add, record the array to be summed.
      #
      if length(involved) == 1
        operation[n] = pure_add
        j = first(involved)
        site[n] = j
        rankarray[n] = length(index_set[j])
        space[n] = product_weights(index_set[j], weight)
        elston_stewart_count[ped] = elston_stewart_count[ped] + space[n]
        active_array[j] = false
        break
      #
      # For a pure multiply or a multiply and add,
      # choose a pair of arrays to combine.
      #
      else
        best_1 = 1
        best_2 = 2
        pivot = i
        #
        # For a pure multiply, find the best pair of arrays
        # to multiply by a greedy algorithm.
        #
        if length(involved) > 2
          pivot = 0
          best_size = Inf
          for j = 1:length(involved)
            k = select_set_element(involved, j)
            for l = j + 1:length(involved)
              m = select_set_element(involved, l)
              union_set = union(index_set[k], index_set[m])
              criterion = product_weights(union_set, weight)
              if criterion < best_size
                best_1 = j
                best_2 = l
                best_size = criterion
              end
            end
          end
        end
        #
        # Form the union of the index sets corresponding to
        # the only or best pair of arrays.
        #
        j = select_set_element(involved, best_1)
        k = select_set_element(involved, best_2)
        union_set = union(index_set[j], index_set[k])
        #
        # Combine nodes and insert the instructions for a pure multiply or
        # a multiply and add.
        #
        (rankarray[n], extra[n]) = combine_nodes(index_set[j], index_set[k],
          union_set, weight, j, k, n, pivot)
        #
        # Delete node i in a multiply and add. Also record the current
        # operation.
        #
        space[n] = product_weights(union_set, weight)
        if length(involved) == 2
          elston_stewart_count[ped] = elston_stewart_count[ped] + 2 * space[n]
          delete!(union_set, i)
          operation[n] = multiply_and_add
          space[n] = product_weights(union_set, weight)
        else
          elston_stewart_count[ped] = elston_stewart_count[ped] + space[n]
          operation[n] = pure_multiply
        end
        #
        # Activate the array created and determine its location and space
        # requirement.
        #
        work_arrays = work_arrays + 1
        index_set[work_arrays] = deepcopy(union_set)
        active_array[work_arrays] = true
        site[n] = work_arrays
        #
        # Deactivate the two arrays destroyed.
        #
        active_array[j] = false
        active_array[k] = false
      end
    end
  end
  #
  # Give the instruction for finishing the current pedigree and update the
  # number of instructions
  #
  n = n + 1
  operation[n] = quit_processing_pedigree
  return n
end # function prepare_instructions

"""
Consolidate the nodes for the multiplicand and product arrays into blocks.
Generate the instructions for a pure multiply or a multiply and add.
a and b are the index sets for the multiplicand arrays,
and a_union_b is the index set for the product array.
n is the current instruction index, and pivot is the node to
be eliminated in a multiply and add. a_location and b_location give
the locations of the multiplicand arrays in the evolving sequence of arrays.
"""
function combine_nodes(a::BitSet, b::BitSet, a_union_b::BitSet,
  weight::Vector{Int}, a_location::Int, b_location::Int, nodes::Int, pivot::Int)
  #
  # Initialize current positions in the original index sets and
  # the block index sets. Also initialize the position of the pivot
  # and the type indicator for block construction.
  #
  extent = zeros(Int, length(a_union_b))
  a_set_position = 1
  b_set_position = 1
  a_block_position = 0
  b_block_position = 0
  union_block_position = 0
  pivot_block_position = 0
  previous_type = -2
  a_stride = 0
  b_stride = 0
  #
  # Create and initialize arrays.
  #
  c = length(a_union_b)
  a_block = zeros(Int, c)
  b_block = zeros(Int, c)
  a_increment = zeros(Int, c)
  b_increment = zeros(Int, c)
  #
  # March through the union of the two original index sets, defining
  # blocks as we go. If the a set has been exhausted, flag its current
  # node as impossible; likewise for the b set.
  #
  while a_set_position <= length(a) || b_set_position <= length(b)
    if a_set_position > length(a)
      current_a_node = nodes + 1
    else
      current_a_node = select_set_element(a, a_set_position)
    end
    if b_set_position > length(b)
      current_b_node = nodes + 1
    else
      current_b_node = select_set_element(b, b_set_position)
    end
    #
    # A new block involving indices from the a set must be started.
    #
    if current_a_node < current_b_node
      new_type = 1
      current_node = current_a_node
      a_set_position = a_set_position + 1
    #
    # A new block involving indices from the b set must be started.
    #
    elseif current_a_node > current_b_node
      new_type = -1
      current_node = current_b_node
      b_set_position = b_set_position + 1
    #
    # The current block continues.
    #
    else
      new_type = 0
      current_node = current_a_node
      a_set_position = a_set_position + 1
      b_set_position = b_set_position + 1
    end
    #
    # A block boundary is hit whenever the pivot node for a multiply and
    # add is encountered.
    #
    if current_node == pivot
      previous_type = -2
      pivot_block_position = union_block_position + 1
    end
#
# Update the product of the weights associated with a continuing block.
#
    if new_type == previous_type
      extent[union_block_position] = extent[union_block_position] *
        weight[current_node]
    #
    # For a new block, record its position, initial weight, and its membership
    # in the a and/or b block index sets.
    #
    else
      union_block_position = union_block_position + 1
      extent[union_block_position] = weight[current_node]
      if new_type == 1 || new_type == 0
        a_block_position = a_block_position + 1
        a_block[a_block_position] = union_block_position
      end
      if new_type == -1 || new_type == 0
        b_block_position = b_block_position + 1
        b_block[b_block_position] = union_block_position
      end
      #
      # Record whether the current block involves a indices, b indices, or
      # both. The pivot marks the beginning of a new block.
      #
      previous_type = new_type
      if current_node == pivot; previous_type = -2; end
    end
  end
  #
  # Insert the rank of the product array into the current instruction.
  #
  rankarray = union_block_position
  a_blocks = a_block_position
  b_blocks = b_block_position
  #
  # Allocate the extra array for the current instruction. Record the
  # locations of the multiplicand arrays. Also load the pivot range, which
  # determines whether a pure multiply or a multiply and add is performed.
  #
  extra = zeros(Int, 5 + 3 * rankarray)
  extra[1] = a_location
  extra[2] = b_location
  if pivot_block_position == 0
    pivot_range = 0
  else
    pivot_range = extent[pivot_block_position] - 1
  end
  extra[3] = pivot_range
  #
  # Calculate the increments for the multiplicand arrays. Also calculate
  # their strides for a multiply and sum.
  #
  a_block_position = 1
  b_block_position = 1
  a_prod = 1
  b_prod = 1
  for union_block_position = 1:rankarray
    if union_block_position == pivot_block_position
      a_stride = a_prod
      b_stride = b_prod
    end
    if a_block_position <= a_blocks && a_block[a_block_position] == union_block_position
      a_prod = a_prod * extent[union_block_position]
      a_increment[union_block_position] = 1
      a_block_position = a_block_position + 1
    else
      a_increment[union_block_position] = 1 - a_prod
    end
    if b_block_position <= b_blocks && b_block[b_block_position] == union_block_position
      b_prod = b_prod * extent[union_block_position]
      b_increment[union_block_position] = 1
      b_block_position = b_block_position + 1
    else
      b_increment[union_block_position] = 1 - b_prod
    end
  end
  #
  # Load the strides into the current instruction.
  #
  extra[4] = a_stride
  extra[5] = b_stride
  #
  # Adjust the increment of each index pivot to correct for the add
  # operation in a multiply and add.
  #
  for i = 1:pivot_block_position - 1
    a_increment[i] = a_increment[i] - a_stride * pivot_range
    b_increment[i] = b_increment[i] - b_stride * pivot_range
  end
  #
  # Load the extents and increments into the current instruction.
  #
  true_rank = rankarray
  if pivot_block_position != 0; true_rank = rankarray - 1; end
  j = 5
  for i = 1:rankarray
    if i != pivot_block_position
      j = j + 1
      extra[j] = extent[i]
      extra[j + true_rank] = a_increment[i]
      extra[j + 2 * true_rank] = b_increment[i]
    end
  end
  rankarray = true_rank
  return (rankarray, extra)
end # function combine_nodes

"""
Find the product of the weights of the elements of the integer set S.
"""
function product_weights(s::BitSet, weight::Vector{Int})

  p = 1
  for element in s
    p = p * weight[element]
  end
  return p
end # function product_weights

"""
Compute recombination fractions between adjacent model loci.
"""
function model_recombination_fractions(locus::Locus,
  keyword::Dict{AbstractString, Any})

  n = locus.model_loci - 1
  theta = zeros(2, n)
  choice = keyword["genetic_map_function"]
  for l = 1:n
    j = locus.model_locus[l]
    k = locus.model_locus[l + 1]
    d = abs(locus.morgans[1, k] - locus.morgans[1, j])
    theta[1, l] = map_function(d, choice)
    d = abs(locus.morgans[2, k] - locus.morgans[2, j])
    theta[2, l] = map_function(d, choice)
  end
  return theta
end # function model_recombination_fractions


import .Zygote

struct ZygoteGradientLogDensity{L} <: ADGradientWrapper
    ℓ::L
end

"""
    ADgradient(:Zygote, ℓ)
    ADgradient(Val(:Zygote), ℓ)

Gradient using algorithmic/automatic differentiation via Zygote.
"""
ADgradient(::Val{:Zygote}, ℓ) = ZygoteGradientLogDensity(ℓ)

Base.show(io::IO, ∇ℓ::ZygoteGradientLogDensity) = print(io, "Zygote AD wrapper for ", ∇ℓ.ℓ)

function logdensity_and_gradient(∇ℓ::ZygoteGradientLogDensity, x::AbstractVector)
    @unpack ℓ = ∇ℓ
    y, back = Zygote.pullback(_logdensity_closure(ℓ), x)
    y, first(back(Zygote.sensitivity(y)))
end


module NonIterableCellMaps

using Gridap
using Gridap.Helpers

export UnimplementedMap
export NonIterableCellMap
import Base: iterate
import Gridap: evaluate
import Gridap: return_size

struct UnimplementedMap{S,M,T,N} <: Map{S,M,T,N} end

function evaluate!(
  this::UnimplementedMap{S,M,T,N},
  points::AbstractArray{<:S,M},
  v::AbstractArray{T,N}) where {S,M,T,N}
  @notimplemented
end

function return_size(::UnimplementedMap{S,M,T,N},::NTuple{M,Int}) where {S,M,T,N}
  @notimplemented
end

abstract type NonIterableCellMap{S,M,T,N} <: IterCellValue{UnimplementedMap{S,M,T,N}} end

function iterate(::NonIterableCellMap)
  _error()
end

function iterate(::NonIterableCellMap,state)
  _error()
end

function _error()
  error("Iteration is intentionally disabled for this type.")
end

end # module


#=
Base code for processing the drums dataset.
=#
#######################
# Channeling
#######################
#map pitches to destination channels (INVALID for discarding)
const INVALID = 9
const TOCHANNEL = Dict{UInt8,UInt8}(
    0x24=>1, #"Kick",
    0x26=>2, #"Snare",
    0x28=>2, #"Snare Rimshot",
    0x1a=>3, #"Hihat Rim",
    0x2e=>3, #"Hihat Head",
    0x33=>4, #"Ride Head",
    0x35=>4, #"Ride Bell",
    0x27=>5, #Tom 4 Rimshot,
    0x29=>5, #Tom 4,
    0x2b=>5, #"Tom 3",
    0x2d=>5, #"Tom 2",
    0x2f=>5, #"Tom 2 Rimshot",
    0x30=>5, #"Tom 1",
    0x32=>5, #"Tom 1 Rimshot",
    0x3a=>5, #"Tom 3 Rimshot",
    0x25=>INVALID, #"Snare RimClick",
    0x2c=>INVALID, #"Hihat Foot Close",
    0x31=>INVALID, #"Cymbal 1",
    0x3b=>INVALID, #"Ride Rim",
    0x37=>INVALID, #"Cymbal 1",
    0x39=>INVALID, #"Cymbal 2",
    0x34=>INVALID, #"Cymbal 2",
    0x2a=>INVALID,
    0x16=>INVALID,
)

const NOTANALYZABLE = [x for x in keys(TOCHANNEL) if TOCHANNEL[x] == INVALID]

const CHANNELNAMES = Dict{UInt8,String}(
    1=>"Kick",
    2=>"Snare",
    3=>"Hihat",
    4=>"Ride",
    5=>"Toms",
    6=>"All in one",
    INVALID=>"Not analyzable"
)

function channel_direct!(notes::Notes, map = TOCHANNEL)
    for note in notes
        note.pitch = map[note.pitch]
    end
    return notes
end

"""
    channel_direct(notes::Notes, map = TOCHANNEL)
Channel `notes` into the mapping specified by `map`
(by changing their pitch).
"""
channel_direct(notes, map = TOCHANNEL) = channel_direct!(copy(notes), map)

#######################
# Cleanup
#######################
function drums_cleanup(drums;
    back = 200, forw = 200, cutoff_fc = 30, cutoff_vel = 20)

    Drums.rm_hihatfake!(drums;
    back = back, forw = forw, cutoff_fc = cutoff_fc, cutoff_vel = cutoff_vel,
    cut_pitches=[0x2e])

    remove = NOTANALYZABLE
    drums = removepitches(drums, remove)
    return drums
end

function remove_peaking_velocities!(drums)
    digital = Drums.DIGITAL
    peak(v) = v ∈ digital ? 159 : 127
    remove = Int[]
    for i in 1:length(drums)
        note = drums[i]
        if note.velocity ≥ peak(note.velocity)
            push!(remove, i)
        end
    end
    deleteat!(drums.notes, remove)
    return drums
end


# __BEGIN_LICENSE__
#
# WaveOptics.jl
#
# Copyright (c) 2015, Stanford University
#
# All rights reserved.
#
# Redistribution and use in source and binary forms for academic and other
# non-commercial purposes with or without modification, are permitted provided
# that the following conditions are met:
#
# * Redistributions of source code, including modified source code, must retain
#   the above copyright notice, this list of conditions and the following
#   disclaimer.
#
# * Redistributions in binary form or a modified form of the source code must
#   reproduce the above copyright notice, this list of conditions and the
#   following disclaimer in the documentation and/or other materials provided with
#   the distribution.
#
# * Neither the name of The Leland Stanford Junior University, any of its
#   trademarks, the names of its employees, nor contributors to the source code
#   may be used to endorse or promote products derived from this software without
#   specific prior written permission.
#
# * Where a modified version of the source code is redistributed publicly in
#   source or binary forms, the modified source code must be published in a freely
#   accessible manner, or otherwise redistributed at no charge to anyone
#   requesting a copy of the modified source code, subject to the same terms as
#   this agreement.
#
# THIS SOFTWARE IS PROVIDED BY THE TRUSTEES OF THE LELAND STANFORD JUNIOR
# UNIVERSITY "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE LELAND STANFORD JUNIOR
# UNIVERSITY OR ITS TRUSTEES BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR
# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
# IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
# OF SUCH DAMAGE.
#
# __END_LICENSE__

using Interpolations

# -----------------------------------------------------------------------------
#           AMPLITUDE POINT SPREAD FUNCTION BASE TYPE & METHODS
# -----------------------------------------------------------------------------

# Base Type
#
# All PointSpreadFunctionModel subtypes will inherit from this base type.
abstract type PointSpreadFunctionModel end

# In a lateral shift invariant models, the image at each z plane can
# be computed using a convolution with a PSF for that z-plane.
#
# Examples: DebyeModel(), VectorialDebyeModel(), and DefocusModel().
#
abstract type LateralShiftInvariantModel <: PointSpreadFunctionModel end

# In a periodic shift invariant models, the point spread function
# displays some periodic lateral shift invariance, but is not completey
# shift invariant.  Projection operators for such models can still be
# computed fairly efficiently using a sequence of convolution operations.
#
# Subtypes of this type must define 'shift_invariance_subpixels' and
# 'shift_invariance_dimensions' to indicate the periodicity of that
# particular model.
#
# Examples: LightFieldModel()
#
abstract type PeriodicShiftInvariantModel <: PointSpreadFunctionModel end

# Shift variant models cannot be projected using convolution, since
# they allow for PSFs that change with every 3D point location.
#
# Examples: CodedApertureLightFieldModel()
#
abstract type ShiftVariantModel <: PointSpreadFunctionModel end

# --------------
# Shared Methods
#
function num_observations(model::PointSpreadFunctionModel)
    return Int64(model.num_observations)
end

function atf(model::PointSpreadFunctionModel, p, theta = Float64[]; na_limit = true, apply_tilt = true)
    error("The atf() method is not implemented for this model type.")
end

function otf_2d(model::PointSpreadFunctionModel, p, theta = Float64[])
    error("The atf() method is not implemented for this model type.")
end

function apsf(model::PointSpreadFunctionModel, p, theta = Float64[])
    error("The apsf() method is not implemented for this model type.")
end

function psf(model::PointSpreadFunctionModel, p, theta = Float64[])
    wf = WaveOptics.apsf(model, p, theta)
    return abs2.(wf)
end

"""
Estimate the diameter (in meters) of an image large enough to contain the point
spread function for a wide field microscope without any phase mask.  Most
(but not all) point spread functions with back aperture masks will fit in
this diameter.

Here, p = (x,y,z) is a tuple containing the point generating this PSF.
"""
function estimate_psf_size(model::PointSpreadFunctionModel, p)

        # Check length and unpack p
        assert(length(p) == 3)
        x,y,z = p

        objective_theta = asin(model.recipe.objective_na / model.recipe.medium_index)
        aperture_diameter = abs( 2 * z * tan(objective_theta) )
        rayleigh_limit = model.recipe.wavelength / (2 * model.recipe.objective_na)

        # Choose a PSF size that comfortably fits the full PSF, with special
        # handling for when z = 0. (The min size is 8 * rayleigh_limit,
        # otherwise it is 2 * aperture_diameter.)
        final_diameter_m = max(8*rayleigh_limit, 2*aperture_diameter)
        return final_diameter_m
end

function optimal_sampling(model::PointSpreadFunctionModel, sim_size_m; oversampling = nothing)
        """
        Given a desired simulation size, this function returns a sampling
        rate (in pixels) that will yield an accurate wave optics simulation.

        Note that this sampling rate is only an estimate based on the abbe
        limit. It may be too high in some cases, and too low in others. Use it
        as a starting point.
        """

        if oversampling == nothing
            oversampling = 1
        end

        # We use the abbe limit as an estimate of the optimal sampling rate. We
        # recommend sampling at 2x the Abbe limit.
        abbe_limit = model.recipe.wavelength / (2. * model.recipe.objective_na);
    return sim_size_m / (2*abbe_limit) * oversampling
end

# --------------------------------------------------------------------------
#                COMMON PHASE MASKS & APSF UTILITIES
# --------------------------------------------------------------------------

"""
This function takes an APSF lookup up table computed using apsf_scalar_debye
or apsf_defocus_atf() and uses the translational invariance property of the
APSF to interpolate and place it at any location x, y at the depth z for
which the apsf_template was computed.

Note, the translation could have been accomplished with a tilt in the back
aperture plane via translation_mask(), but this would have potentially led
to sampling problems if a large tilt (due to a large x or y translation)
exceeded the nyquist sampling rate of the back aperture. For general
translations which may be large, this method is more robust.
"""
function translate_apsf(sim_size_m, sim_size_px, apsf::AbstractArray{T, 2}, dx, dy) where T<:Number
    sample_period = sim_size_m / sim_size_px

    # Use 2D interpolation to shift the translationally invariant microscope PSF
    apsf_real_interp = interpolate(real(apsf), BSpline(Interpolations.Linear()))
    apsf_imag_interp = interpolate(imag(apsf), BSpline(Interpolations.Linear()))

    result = zeros(eltype(apsf), size(apsf))
    for c in 1:size(apsf,2)
        for r in 1:size(apsf,1)
            result[r,c] = apsf_real_interp(r + dy/sample_period, c - dx/sample_period) + im * apsf_imag_interp(r + dy/sample_period, c - dx/sample_period)
        end
    end
    return result
end

"""
Creates a phase mask in the back aperture plane corresponding to a
translation in the image plane. This translation mask is based on the
Fourier shift theorem. For small shifts, it should match with the
translate_apsf() function below.

fx, fy must be in 'normalized' cartesian coordinates (see fourier_plane_meshgrid()).
"""
function translation_mask(dx, dy, fx::Array{T, 2}, fy::Array{T, 2},
                          objective_na::Float64,
                          wavelength::Float64) where T<:AbstractFloat
    result = Array{ComplexF32}(size(fx))
    omega = -2. * im * pi * objective_na/wavelength
    for i in eachindex(result)
        result[i] = exp(omega * (fx[i] * dx + fy[i] * (-dy)))
    end
    return result
end

"""
Implements the defocus phase function described in

Hanser, B. M., Gustafsson, M., & Agard, D. A. (2004). Phase-retrieved pupil
functions in wide-field fluorescence microscopy. Journal of Microscopy. Vol.
216. pp. 32-48. 2004.

This phase function models roughly what happens if the microscope objective
were to be refocused to a different depth 'dz' in the object.

r, theta must be in 'normalized' polar coordinates (see fourier_plane_meshgrid()).
"""
function defocus_mask(dz::S, r::AbstractArray{T,2}, theta::AbstractArray{T,2},
                      objective_na::Float64, wavelength::Float64, medium_index::Float64) where T<:AbstractFloat where S<:Number
    M,N = size(r)
    k = medium_index/wavelength
    omega = T(k^2) .- T(objective_na/wavelength)^2 * r.^2

    # Ensure we don't try to take the sqrt() of a negative number. This should
    # only happen outside the boundary of the aperture, so this check amounts to
    # zeroing out the phase of mask pixels outside the numerical aperture.
    binary_mask = omega .>= 0

    # Since CuArrays.jl doesn't have a function to compute exp(ComplexFloat),
    # we manually compute it.
    phi = T(-2π * dz) * sqrt.(max.(omega, 0))
    phase =  (cos.(phi) + 1im .* sin.(phi)) .* binary_mask
    return phase
end

# --------------------------------------------------------------------------
#                       APODIZATION FUNCTIONS
# --------------------------------------------------------------------------

"""
Apply a the apodization function for the Abbe sine condition. This
implements eqn. 6.3.7 from Min Gu's "Advanced Optical Imaging Theory"

r, theta must be in "normalized" polar coordinates (see fourier_plane_meshgrid()).
"""
function abbe_sine_apodization(rho::Array{T}, theta::Array{T}, objective_na::AbstractFloat, medium_index::AbstractFloat) where T<:AbstractFloat

    result = zeros(ComplexF32, size(rho))
    omega = objective_na/medium_index
    for i in eachindex(rho)
        if rho[i] <= 1.0
            alpha = asin(omega * rho[i])    # Gross eq. 21-3 and 21-4
            apodization = sqrt(cos(alpha))                # Gu eq. 6.3.10
            result[i] = apodization + 0. * im
        else
            result[i] = 0.
        end
    end
    return result
end


#-------------------------------------------------------------------
#* EMSO Model Library (EML) Copyright (C) 2004 - 2007 ALSOC.
#*
#* This LIBRARY is free software; you can distribute it and/or modify
#* it under the therms of the ALSOC FREE LICENSE as available at
#* http://www.enq.ufrgs.br/alsoc.
#*
#* EMSO Copyright (C) 2004 - 2007 ALSOC, original code
#* from http://www.rps.eng.br Copyright (C) 2002-2004.
#* All rights reserved.
#*
#* EMSO is distributed under the therms of the ALSOC LICENSE as
#* available at http://www.enq.ufrgs.br/alsoc.
#*--------------------------------------------------------------------
#* Author: Gerson Balbueno Bicca
#* $Id$
#*--------------------------------------------------------------------
type Shell_Side_Main
	Shell_Side_Main()=begin
		new(
			length(Dict{Symbol,Any}(
				:Brief=>"Inside Shell Diameter",
				:Lower=>1E-6
			)),
			positive(Dict{Symbol,Any}(
				:Brief=>"Shellside Fouling Resistance",
				:Unit=>"m^2*K/kW",
				:Symbol=>"Rf_{shell}",
				:Default=>1E-6 ,
				:Lower=>0
			)),
			length(Dict{Symbol,Any}(
				:Brief=>"Inlet Nozzle Inside Diameter",
				:Lower=>1E-6
			)),
			length(Dict{Symbol,Any}(
				:Brief=>"Outlet Nozzle Inside Diameter",
				:Lower=>1E-6
			)),
			Shell_Pdrop(Dict{Symbol,Any}(
				:Brief=>"Shell Side Pressure Drop",
				:Symbol=>" "
			)),
			Shell_Heat_Transfer(Dict{Symbol,Any}(
				:Brief=>"Shell Side Heat Transfer",
				:Symbol=>" "
			)),
			Physical_Properties(Dict{Symbol,Any}(
				:Brief=>"ShellSide Properties",
				:Symbol=>" "
			)),
			[:ShellID,:Fouling,:InletNozzleID,:OutletNozzleID,],
			[:PressureDrop,:HeatTransfer,:Properties,]
		)
	end
	ShellID::length
	Fouling::positive
	InletNozzleID::length
	OutletNozzleID::length
	PressureDrop::Shell_Pdrop
	HeatTransfer::Shell_Heat_Transfer
	Properties::Physical_Properties
	parameters::Array{Symbol,1}
	variables::Array{Symbol,1}
	attributes::Dict{Symbol,Any}
end
export Shell_Side_Main
function atributes(in::Shell_Side_Main,_::Dict{Symbol,Any})
	fields::Dict{Symbol,Any}=Dict{Symbol,Any}()
	fields[:Pallete]=false
	fields[:Brief]="Main variables in the Shell Side section of a shell and tube heat exchanger."
	drive!(fields,_)
	return fields
end
Shell_Side_Main(_::Dict{Symbol,Any})=begin
	newModel=Shell_Side_Main()
	newModel.attributes=atributes(newModel,_)
	newModel
end


6.829243291077833e-5
3.434878183968345e-5
0.0008216229330892649
-0.000581988879049661
0.06155840513482077
0.06554710709090356
-0.09866394333901657
0.116947264552445
-0.25878586618325355
0.03177577190964304
0.14412170022133045
0.030454378554912186
0.024718718097048634
-0.3414334420976965
-0.040071052414555476
0.218330958433261
-0.13956620885729176
-0.12911808740763595
-0.09683117732331718
-0.07359701187251337
-0.022446414348095328
0.32761035972021996
0.036733190050246035
0.06785148610066312
-0.09843328991138175
-0.0421606099190659
-0.0371341751516621
0.22929193892003766
-0.4000593695131863
0.00652260929226957
0.06282041174124728
0.03904605046767447
0.14163148255064506
0.18833975652478777
-0.1365359715867163
0.022684456874556766
-0.019144770663276856
-3.985044048692918e-5
-0.009480480395882075
0.09461567515805262
0.08790013212266694
-0.05489329126368636
-0.08732267393192181
-0.002225277782904033
0.07557102031193645
-0.14570704930547385
0.01663193075833319
-0.05542271179121729
-0.11986411718444501
0.21799883385697374
0.003006109097820892
0.06252709509228656