text:
module TestArrayUtil

using FactCheck
using DataCubes
using DataCubes: AbstractArrayWrapper, FloatNAArray, simplify_array,
                     type_array, wrap_array, gtake, gdrop

facts("ArrayUtil tests") do
  context("type_array tests") do
    @fact type_array([1,2,3]) --> [1,2,3]
    @fact type_array(Any[1,2,3]) --> [1,2,3]
    @fact type_array(Any[1,2.0,3]) --> [1.0,2.0,3.0]
  end
  context("AbstractArrayWrapper tests") do
    @fact AbstractArrayWrapper(type_array([Nullable(1), Nullable(2.0)])) --> AbstractArrayWrapper([Nullable(1.0), Nullable(2.0)])
    @fact type_array(AbstractArrayWrapper(FloatNAArray([1.0,2.0,NaN]))) --> AbstractArrayWrapper(FloatNAArray([1.0,2.0,NaN]))
    @fact type_array(AbstractArrayWrapper(FloatNAArray([1.0,2.0,NaN]))) --> AbstractArrayWrapper([Nullable(1.0),Nullable(2.0),Nullable{Float64}()])
    @fact AbstractArrayWrapper(type_array(FloatNAArray([1.0,2.0,NaN]))) --> AbstractArrayWrapper([Nullable(1.0),Nullable(2.0),Nullable{Float64}()])
  end
  context("Array transformation tools tests") do
    @fact DataCubes.expand_dims([1 2 3], (2,), (3,)) --> reshape(repmat([1,1,2,2,3,3],3),(2,1,3,3))
    @fact DataCubes.expand_dims(@darr(a=[1 2 3]), (2,), (3,)) --> @darr(a=reshape(repmat([1,1,2,2,3,3],3),(2,1,3,3)))
    @fact DataCubes.expand_dims(@larr(a=[1 2 3],axis1[k=[:x]]), (DataCubes.DefaultAxis(2),), (DataCubes.DefaultAxis(3),)) --> @larr(a=reshape(repmat([1,1,2,2,3,3],3),(2,1,3,3)), axis2[k=[:x]])
    @fact collapse_axes(reshape(collect(1:100), 1,2,5,10), 2, 3) --> reshape(collect(1:100), 1,10,10)
    @fact collapse_axes(reshape(collect(1:100), 1,2,5,10), 2, 3) --> reshape(collect(1:100), 1,10,10)
    @fact collapse_axes(@darr(a=reshape(collect(1:100), 1,2,5,10)), 2, 3) --> @darr(a=reshape(collect(1:100), 1,10,10))
    @fact collapse_axes(@larr(a=reshape(collect(1:100), 1,2,5,10)), 2, 3) --> @larr(a=reshape(collect(1:100), 1,10,10))
    @fact collapse_axes(LabeledArray(DictArray(a=nalift(reshape(collect(1:100), 1,2,5,10))),
                                          axis1=DictArray(k1=nalift([:sym])),
                                          axis2=DictArray(r1=@nalift(["x","y"])),
                                          axis3=@nalift([100,99,98,97,96])), 1, 3) -->
          @larr(a=reshape(collect(1:100), 10,10),
                axis1[k1=fill(:sym, 10),r1=repmat(["x","y"],5),x1=[100,100,99,99,98,98,97,97,96,96]])
    @fact collapse_axes(LabeledArray(DictArray(a=nalift(reshape(collect(1:100), 1,2,5,10))),
                                          axis1=DictArray(1=>nalift([:sym])),
                                          axis2=DictArray(:x1=>@nalift(["x","y"])),
                                          axis3=@nalift([100,99,98,97,96]),
                                          axis4=nalift(collect(31:40))), 1, 4) -->
          @larr(a=collect(1:100),
                axis1[1=>fill(:sym, 100),:x1=>repmat(["x","y"],50),:x2=>repmat([100,100,99,99,98,98,97,97,96,96],10),:x3=>repeat(collect(31:40),inner=[10])])
    @fact (a=[1,2,3,4,5];collapse_axes(a) === a) --> true
    @fact (a=darr(a=[1,2,3,4,5]);collapse_axes(a) === a) --> true
    @fact (a=larr(a=[1,2,3,4,5],axis=[:a,:b,:c,:d,:e]);collapse_axes(a) === a) --> true
    @fact DataCubes.create_additional_fieldname(@larr(a=[1,2,3])) --> :x1
    @fact DataCubes.create_additional_fieldname(@larr(x1=[1,2,3])) --> :x2
    @fact DataCubes.create_additional_fieldname(@larr(x1=[1,2,3],x3=[:a,:b,:c])) --> :x2
    @fact DataCubes.create_additional_fieldname(@larr(x1=[1,2,3],axis1[x2=[:a,:b,:c]])) --> :x3
    @fact mapvalues(x->x+1,[3,2,1]) --> [4,3,2]
    @fact mapvalues(LDict(:a=>1,:b=>3,:c=>5)) do x;x*2 end --> LDict(:a=>2,:b=>6,:c=>10)
    @fact mapvalues(x->broadcast(+, x, 1), darr(a=[1,2,3], b=[4,5,6])) --> darr(a=[2,3,4], b=[5,6,7])
    @fact mapvalues(x->broadcast(+, x, 1), larr(a=[1,2,3], b=[4,5,6], axis1=[:m,:n,:p])) --> larr(a=[2,3,4], b=[5,6,7], axis1=[:m,:n,:p])
    @fact mapvalues(sum, darr(a=[1,2,3], b=[4,5,6])) --> LDict(:a=>Nullable(6), :b=>Nullable(15))
    @fact mapvalues(sum, larr(a=[1,2,3], b=[4,5,6], axis1=[:m,:n,:p])) --> LDict(:a=>Nullable(6), :b=>Nullable(15))
    @fact replace_axes(@larr(c1=[1 2 3;4 5 6],
                             c2=[:a :b :c;:d :e :f],
                             axis1[k1=["x","y"],k2=['a','b']],
                             axis2[r1=[10.0,11.0,12.0]]), 1=>[:c1,:k1], 2=>[:c2]) -->
          @larr(k2=['a' 'a' 'a';'b' 'b' 'b'],r1=[10.0 11.0 12.0;10.0 11.0 12.0],axis1[c1=[1,4],k1=["x","y"]],axis2[c2=[:a,:b,:c]])
    @fact replace_axes(@larr(c1=[1 2 3;4 5 6],
                             c2=[:a :b :c;:d :e :f],
                             axis1[k1=["x","y"],k2=['a','b']],
                             axis2[r1=[10.0,11.0,12.0]]), 1=>[:c1,:k1], 2=>[:c2,:k2]) -->
          @larr(r1=[10.0 11.0 12.0;10.0 11.0 12.0],axis1[c1=[1,4],k1=["x","y"]],axis2[c2=[:a,:b,:c],k2=['a','a','a']])
    @fact replace_axes(@larr(c1=[1 2 3;4 5 6],
                             c2=[:a :b :c;:d :e :f],
                             axis1[k1=["x","y"],k2=['a','b']],
                             axis2[r1=[10.0,11.0,12.0]]), [:c1,:k1], 2=>[:c2,:k2]) -->
          @larr(r1=[10.0 11.0 12.0;10.0 11.0 12.0],axis1[c1=[1,4],k1=["x","y"]],axis2[c2=[:a,:b,:c],k2=['a','a','a']])
    @fact replace_axes(@larr(c1=[1 2 3;4 5 6],
                             c2=[:a :b :c;:d :e :f],
                             axis1[k1=["x","y"],k2=['a','b']],
                             axis2[r1=[10.0,11.0,12.0]]), 1=>[:c1,:k1], [:c2,:k2]) -->
          @larr(r1=[10.0 11.0 12.0;10.0 11.0 12.0],axis1[c1=[1,4],k1=["x","y"]],axis2[c2=[:a,:b,:c],k2=['a','a','a']])
    @fact replace_axes(@larr(c1=[1 2 3;4 5 6],
                             c2=[:a :b :c;:d :e :f],
                             axis1[k1=["x","y"],k2=['a','b']],
                             axis2[r1=[10.0,11.0,12.0]]), [:c1,:k1], [:c2,:k2]) -->
          @larr(r1=[10.0 11.0 12.0;10.0 11.0 12.0],axis1[c1=[1,4],k1=["x","y"]],axis2[c2=[:a,:b,:c],k2=['a','a','a']])
    @fact replace_axes(larr('a'=>[1,2,3], b=[:x,:y,:z]), 1=>['a']) --> larr(b=[:x,:y,:z], axis1=darr('a'=>[1,2,3]))
    @fact replace_axes(larr('a'=>[1,2,3], b=[:x,:y,:z]), 1=>[:b]) --> larr(axis1=darr(b=[:x,:y,:z]), 'a'=>[1,2,3])
    @fact replace_axes(larr('a'=>[1,2,3], b=[:x,:y,:z]), 1=>'a') --> larr(b=[:x,:y,:z], axis1=darr('a'=>[1,2,3]))
    @fact replace_axes(larr('a'=>[1,2,3], b=[:x,:y,:z]), 1=>:b) --> larr(axis1=darr(b=[:x,:y,:z]), 'a'=>[1,2,3])
    @fact replace_axes(larr(a=[1 2;3 4;5 6], axis=[:x,:y,:z]),1=>[:a]) --> @larr(axis[a=[1,3,5]], x1=[:x :x;:y :y;:z :z])
    @fact replace_axes(larr(a=[1 2;3 4;5 6], axis=[:x,:y,:z]),1=>[]) --> @larr(a=[1 2;3 4;5 6], x1=[:x :x;:y :y;:z :z])
    @fact permutedims(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]])),(2,3,1))[:,:,1] -->
          LabeledArray(nalift([1 'a';2 'b';3 'c']), axis1=@darr(k=[100,200,300]), axis2=nalift([:X,:Y]))
    @fact permutedims(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]]), axisname="newaxis"),(2,3,1))[:,:,1] -->
          LabeledArray(nalift([1 'a';2 'b';3 'c']), axis1=@darr(k=[100,200,300]), axis2=@darr("newaxis"=>[:X,:Y]))
    @fact permutedims(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]]), fieldname="newfield"),(2,3,1))[:,:,1] -->
          LabeledArray(@darr("newfield"=>nalift([1 'a';2 'b';3 'c'])), axis1=@darr(k=[100,200,300]), axis2=nalift([:X,:Y]))
    @fact permutedims(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]]), axisname="newaxis", fieldname="newfield"),(2,3,1))[:,:,1] -->
          LabeledArray(@darr("newfield"=>nalift([1 'a';2 'b';3 'c'])), axis1=@darr(k=[100,200,300]), axis2=@darr("newaxis"=>[:X,:Y]))
    @fact axis2flds(@larr(col=[1 2;3 4],axis1[k=[:a,:b]],axis2[r=[:x,:y]])) --> @larr(x_col=[1,3],:y_col=>[2,4],axis1[k=[:a,:b]])
    @fact axis2flds(@larr(col=[1 2;3 4],axis1[k=[:a,:b]],axis2[r=[:x,NA]]), default_axis_value=3) --> @larr(x_col=[1,3],Symbol("3_col")=>[2,4],axis1[k=[:a,:b]])
    @fact axis2flds(permutedims(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]]),fieldname="newfield",axisname=:newaxis),(3,2,1))[:,:,1]) --> @larr(Symbol("100_newfield")=>[1,'a'],Symbol("200_newfield")=>[2,'b'],Symbol("300_newfield")=>[3,'c'], axis1[newaxis=[:X,:Y]])
    @fact axis2flds(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]]),fieldname="newfield",axisname=:newaxis)) --> @larr(Symbol("X_newfield")=>[1 2 3],Symbol("Y_newfield")=>['a' 'b' 'c'], axis2[k=[100,200,300]])
    @fact axis2flds(flds2axis(@larr(X=[1 2 3],Y=['a' 'b' 'c'], axis2[k=[100,200,300]]),fieldname="newfield")) --> @larr(Symbol("X_newfield")=>[1 2 3],Symbol("Y_newfield")=>['a' 'b' 'c'], axis2[k=[100,200,300]])
    @fact flds2axis(@larr(a=[1,2,3,4,5],b=[:x,:y,:z,:v,:w],c=['a','b','c','d','e'])) --> LabeledArray(transpose(nalift([1 2 3 4 5;:x :y :z :v :w;'a' 'b' 'c' 'd' 'e'])), axis2=nalift([:a,:b,:c]))
    @fact axis2flds(flds2axis(@select(@larr(a=[1,2,3,4,5],b=[:x,:y,:z,:v,:w],c=['a','b','c','d','e']),:a))) --> @select(@larr(a=[1,2,3,4,5],b=[:x,:y,:z,:v,:w],c=['a','b','c','d','e']),:a)
    @fact axis2flds(larr(reshape(1:10,5,2), axis1=darr(k=['a','b','c','d','e']), axis2=darr(r1=[:M,:N],r2=["A","A"]))) --> larr(M_A=[1,2,3,4,5],N_A=[6,7,8,9,10], axis1=darr(k=['a','b','c','d','e']))
  end
  context("Misc functions tests") do
    rand_array = rand(10,5,30,20)
    @fact DataCubes.permutedims_if_necessary(rand_array,(1,2,3,4)) === rand_array --> true
    @fact DataCubes.permutedims_if_necessary(rand_array,(2,3,4,1)) == permutedims(rand_array,(2,3,4,1)) --> true
    @fact DataCubes.dropna(@larr(a=[1 2 NA;NA 3 NA],b=[10 11 NA;13 14 NA],axis1[k=['a','b']],axis2[r=[:x,:y,:z]])) --> @larr(a=[1 2;NA 3],b=[10 11;13 14],axis1[k=['a','b']],axis2[r=[:x,:y]])
    @fact DataCubes.dropna(@larr(a=[1 2 NA;NA 3 NA],b=[10 11 NA;13 14 NA])) --> @larr(a=[1 2;NA 3],b=[10 11;13 14])
    @fact DataCubes.dropna(@larr(a=[1 2 NA;NA 3 NA],b=[10 11 12;13 14 15])) --> @larr(a=[1 2 NA;NA 3 NA],b=[10 11 12;13 14 15])
    @fact DataCubes.dropna(@larr(a=[1 2 NA;NA 3 NA],b=[10 11 12;13 14 NA])) --> @larr(a=[1 2 NA;NA 3 NA],b=[10 11 12;13 14 NA])
    @fact DataCubes.dropna(@larr(a=[1 2 NA;NA 3 NA])) --> @larr(a=[1 2;NA 3])
    @fact DataCubes.dropna(@darr(a=[1 2 NA;NA 3 NA])) --> @darr(a=[1 2;NA 3])
    @fact DataCubes.dropna(@nalift([1 2 NA;NA 3 NA])) --> @nalift([1 2;NA 3])
    @fact sum(DataCubes.igna(DataCubes.reducedim((x,y)->DataCubes.naop_plus(x,y[:a]),@darr(a=reshape(1:5*5*2*3, 5,5,2,3),b=reshape(1:5*5*2*3, 5,5,2,3)),(1,2),Nullable(0.0)))) --> roughly(11325.0)
    @fact (@rap _.value (@rap sum peel _) reducedim((x,y)->DataCubes.naop_plus(x,y[:a]),@larr(a=reshape(1:5*5*2*3, 5,5,2,3),b=reshape(1:5*5*2*3, 5,5,2,3)),(1,2),Nullable(0.0))) --> roughly(11325.0)
    #@fact sum(DataCubes.igna(DataCubes.reducedim((x,y)->DataCubes.naop_plus(x,y.second.second.second.second[:a]),@larr(a=reshape(1:5*5*2*3, 5,5,2,3),b=reshape(1:5*5*2*3, 5,5,2,3)),(1,2),Nullable(0.0)))) --> roughly(11325.0)
    @fact (@rap sin cos sin 1) --> sin(cos(sin(1)))
    @fact (@rap sin(_) cos sin 1) --> sin(cos(sin(1)))
    @fact (@rap sin cos(_) sin 1) --> sin(cos(sin(1)))
    @fact (@rap sin cos(_) sin(_) 1) --> sin(cos(sin(1)))
    @fact (@rap _*8 _*2 3) --> 48
    #TODO ' seems to conflict with transpose in julia 0.6.
    #@fact (@rap sum sin' cos(_)' nalift([1,2,3])).value --> sum(map(sin, map(cos, [1,2,3])))
    @fact delete(@larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]])) --> @larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]])
    @fact delete(@larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]]), :a) --> @larr(b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]])
    @fact delete(@larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]]), :k2) --> @larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"]])
    @fact delete(@larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]]), :k1,:k2) --> @larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA])
    @fact delete(@larr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA], axis1[k1=["m","n"], k2=[:u,:u]]), :k1,:k2,:b) --> @larr(a=reshape(1:6,2,3))
    @fact delete(@darr(a=reshape(1:6,2,3), b=[:x NA :y;:z :w NA]), :a) --> @darr(b=[:x NA :y;:z :w NA])
    @fact delete([1,2,3,4,5]) --> [1,2,3,4,5]
    @fact delete(LDict('a'=>1,'b'=>3), 'a', 'b') --> isempty
    @fact mapna(x->x+1, @nalift([1,NA,NA])) --> @nalift([2,NA,NA])
    @fact mapna(x->x+1, @nalift([NA, NA,NA])) --> @nalift([NA,NA,NA])
    @fact mapna(x->repmat([:a],x), @nalift([NA, 3,NA])) --> wrap_array(Nullable{Array{Symbol,1}}[Nullable{Array{Symbol,1}}(),Nullable([:a,:a,:a]),Nullable{Array{Symbol,1}}()])
    @fact mapna((x,y)->x+y+1, @nalift([1,NA,NA]), @nalift([5,NA,2])) --> @nalift([7,NA,NA])
    @fact mapna((x,y)->"abc", @nalift([1,NA,NA]), @nalift([5,NA,2])) --> @nalift(["abc",NA,NA])
    @fact mapna((x,y)->Nullable("abc"), @nalift([1,NA,NA]), @nalift([5,NA,2])) --> @nalift(["abc",NA,NA])
    @fact eltype(mapna((x,y)->x+y+1, @nalift([1 2 3;4 5 NA]), @nalift([NA 2 3;4 NA NA]))[1,1]) --> Int64
    @fact eltype(mapna((x,y)->Nullable(x+y+1), @nalift([1 2 3;4 5 NA]), @nalift([NA 2 3;4 NA NA]))[1,1]) --> Int64
    @fact providenames(larr(1:10)) --> larr(x1=1:10, axis1=darr(x2=1:10))
    @fact providenames(larr(reshape(1:10,2,5), axis1=darr(k=[:x,:y]))) --> larr(x1=reshape(1:10,2,5), axis1=darr(k=[:x,:y]), axis2=darr(x2=1:5))
    @fact providenames(larr(a=reshape(1:10,2,5)), i->string("column",i)) --> larr(a=reshape(1:10,2,5), axis1=darr("column1"=>[1,2]), axis2=darr("column2"=>[1,2,3,4,5]))
    @fact withdrawnames(providenames(larr(1:10))) --> larr(1:10)
    @fact withdrawnames(providenames(larr(reshape(1:10,2,5), axis1=darr(k=[:x,:y])))) --> larr(reshape(1:10,2,5), axis1=darr(k=[:x,:y]))
    @fact withdrawnames(providenames(larr(a=reshape(1:10,2,5)), i->string("column",i)), name->isa(name, String) && startswith(name, "column")) --> larr(a=reshape(1:10,2,5))
  end
  context("darr/larr tests") do
    @fact @larr(@larr(a=[1,2,3]),b=[10,11,12], axis1[r=[:a,:b,"x"]]) --> @larr(a=[1,2,3],b=[10,11,12], axis1[r=[:a,:b,"x"]])
    @fact @larr(@larr(a=[1,2,3]), axis1[r=[:a,:b,"x"]]) --> @larr(a=[1,2,3], axis1[r=[:a,:b,"x"]])
    @fact @larr(@larr(a=[1,2,3]),b=[10,11,12]) --> @larr(a=[1,2,3],b=[10,11,12])
    @fact @larr(@larr(a=[1,2,3])) --> @larr(a=[1,2,3])
    @fact larr(a=[:a,:b,:c], axis1=[3,2,1]) --> @larr(a=[:a,:b,:c], axis1[[3,2,1]])
    @fact larr([:a,:b,:c], axis1=[3,2,1]) --> @larr([:a,:b,:c], axis1[[3,2,1]])
    @fact larr([1 2 3;4 5 6], :axis2=>["x","y","z"]) --> @larr([1 2 3;4 5 6], axis2[["x","y","z"]])
    @fact larr(@larr(a=[1,2,3], axis1[r=[:a,:b,:c]]), :axis1=>[3,2,1]) --> @larr(a=[1,2,3], axis1[[3,2,1]])
    @fact larr(@larr(a=[1,2,3], axis1[r=[:a,:b,:c]]), axis1=[3,2,1]) --> @larr(a=[1,2,3], axis1[[3,2,1]])
    @fact larr(larr(darr(darr(darr(a=[1,2,3]),b=[2,3,4])))) --> @larr(a=[1,2,3], b=[2,3,4])
    @fact darr(a=[1,2,3],b=[2,3,4]) --> @darr(a=[1,2,3], b=[2,3,4])
    @fact darr(darr(a=[1,2,3]),b=[2,3,4]) --> @darr(a=[1,2,3], b=[2,3,4])
    @fact darr(darr(darr(a=[1,2,3]),b=[2,3,4])) --> @darr(a=[1,2,3], b=[2,3,4])
    @fact darr(darr(larr(a=[1 2;3 4;5 6],axis1=darr(r=['x','y','z']))),:k=>[:m :n;:o :r;:x :y]) --> @darr(hcat(darr(r=['x','y','z']),darr(r=['x','y','z'])), :a=>[1 2;3 4;5 6], k=[:m :n;:o :r;:x :y])
    @fact larr(a=[1,2,3], b=10) --> larr(a=[1,2,3], b=[10,10,10])
    @fact darr(a=[1,2,3], b=10) --> darr(a=[1,2,3], b=[10,10,10])
    @fact tensorprod(larr(a=[1,2,3],axis1=['a','b','c']), larr(b=[2 3;0 1],axis1=[:x,:y])) --> larr(a=reshape(repmat([1,2,3],4),3,2,2),b=reshape([2,2,2,0,0,0,3,3,3,1,1,1],3,2,2),axis1=['a','b','c'],axis2=[:x,:y])
    @fact tensorprod(darr(a=[1,2,3]), darr(b=[2 3;0 1])) --> darr(a=reshape(repmat([1,2,3],4),3,2,2),b=reshape([2,2,2,0,0,0,3,3,3,1,1,1],3,2,2))
    @fact tensorprod(@nalift([1,NA,0]), nalift([1,0]), nalift([1,0])) --> reshape(@nalift([(1,1,1),NA,(0,1,1),(1,0,1),NA,(0,0,1),(1,1,0),NA,(0,1,0),(1,0,0),NA,(0,0,0)]),3,2,2)
    @fact_throws tensorprod()
    @fact mapvalues(namerge, 10, @larr(a=[3 5 NA;1 1 1]),@larr(a=[1 2 NA;4 NA 6])) --> @larr(a=[1 2 10;4 1 6])
  end
  context("enumeration array tests") do
    @fact wrap_array(EnumerationArray(nalift([:c,:b,:b,:a]), [:b,:c,:a])) --> wrap_array(enumeration([:c,:b,:b,:a], [:b,:c,:a]))
    @fact wrap_array(@enumeration(["x", NA, "y", NA], ["y", "x"])) --> wrap_array(EnumerationArray(([2,0,1,0], ["y","x"])))
    # temporarily suppressing two tests that give an error in v0.5.
    #@fact wrap_array(enumeration(enumeration([:a,:a,:c,:b],[:c,:b,:a]), [:a,:c,:b])) --> wrap_array(enumeration([:a,:a,:c,:b],[:c,:b,:a]))
    #@fact wrap_array(enumeration(enumeration([:a,:a,:c,:b],[:c,:b,:a]), [:a,:c])) --> wrap_array(enumeration([:a,:a,:c,:b],[:c,:b,:a]))
  end
  context("extract/discard tests") do
    @fact gtake(collapse_axes(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))), 2) --> larr(a=[1,4],b=['x','u'],axis1=darr(x1=[:m,:n],r=["A","A"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), 2) --> larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))
    @fact gtake(collapse_axes(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))), 8) --> larr(a=[1,4,2,5,3,6,1,4],b=['x','u','y','v','z','w','x','u'],axis1=darr(x1=[:m,:n,:m,:n,:m,:n,:m,:n],r=["A","A","B","B","C","C","A","A"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), 8) --> repeat(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])),outer=[4,1])
    @fact gtake(collapse_axes(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))), -8) --> larr(a=[3,6,1,4,2,5,3,6],b=['z','w','x','u','y','v','z','w'],axis1=darr(x1=[:m,:n,:m,:n,:m,:n,:m,:n],r=["C","C","A","A","B","B","C","C"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), -8) --> repeat(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])),outer=[4,1])
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), 1) --> larr(a=[1 2 3],b=['x' 'y' 'z'], axis1=[:m], axis2=darr(r=["A","B","C"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), -1) --> larr(a=[4 5 6],b=['u' 'v' 'w'], axis1=[:n], axis2=darr(r=["A","B","C"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :, 1) --> larr(a=reshape([1 4],2,1),b=reshape(['x' 'u'],2,1), axis1=[:m,:n], axis2=darr(r=["A"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :, -1) --> larr(a=reshape([3 6],2,1),b=reshape(['z' 'w'],2,1), axis1=[:m,:n], axis2=darr(r=["C"]))
    @fact gtake(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), -2) --> larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))
    @fact gtake(nalift([1 3 2 5 4]), :, 2:3) --> nalift([3 2])
    @fact gtake(nalift([1 3 2 5 4]), :, 2) --> nalift([1 3])
    @fact gtake(nalift([1 3 2 5 4]), :, -2) --> nalift([5 4])
    @fact gtake(LDict(:a=>3, :b=>3, :c=>2), 2) --> LDict(:a=>3, :b=>3)
    @fact gtake(LDict(:a=>3, :b=>3, :c=>2), -2) --> LDict(:b=>3, :c=>2)
    @fact_throws gtake(LDict(:a=>3, :b=>3, :c=>2), 4)
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :, darr(r=["X","A"])) --> @larr(a=[NA 1;NA 4],b=[NA 'x';NA 'u'], axis1[:m,:n], axis2[darr(r=["X","A"])])
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), [:p,:m]) --> @larr(a=[NA NA NA;1 2 3],b=[NA NA NA;'x' 'y' 'z'], axis1[:p,:m], axis2[darr(r=["A","B","C"])])
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), [:n, :o], darr(r=["X","A"])) --> @larr(a=[NA 4;NA NA],b=[NA 'u';NA NA], axis1[:n,:o], axis2[darr(r=["X","A"])])

    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :n, (Nullable("A"),)) --> LDict(:a=>Nullable(4), :b=>Nullable('u'))
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), Nullable(:m), LDict(:r=>Nullable("A"))) --> LDict(:a=>Nullable(1), :b=>Nullable('x'))
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), Nullable(:m), LDict(:r=>Nullable("X"))) --> LDict(:a=>Nullable{Int}(), :b=>Nullable{Char}())
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :m) --> larr(a=[1,2,3],b=['x','y','z'], axis1=darr(r=["A","B","C"]))
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :, LDict(:r=>Nullable("A"))) --> larr(a=[1,4], b=['x','u'], axis1=[:m,:n])
    @fact extract(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), :, (Nullable("A"),)) --> larr(a=[1,4], b=['x','u'], axis1=[:m,:n])

    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)),1:2,nalift(1:3)) --> larr(a=[101 104 107;102 105 108], b=[1 4 7;2 5 8])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5),axis2=[:x,:y,:z,:u,:v]),2) --> larr(a=[102,105,108,111,114],b=[2,5,8,11,14],axis1=[:x,:y,:z,:u,:v])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)),d->broadcast(>, wrap_array(d), 2)) --> larr(a=[103 106 109 112 115], b=[3 6 9 12 15])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)),d->broadcast(>, wrap_array(d), 3)) --> isempty
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z])),d->broadcast(==, d[:k], :y)) --> larr(a=[102 105 108 111 114], b=[2 5 8 11 14], axis1=darr(k=[:y]))
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z])),d->broadcast(==, d[:k], :w)) --> isempty
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),:,d->broadcast(>, d, 8)) --> larr(a=[101 104;102 105;103 106],b=[1 4;2 5;3 6],axis1=darr(k=[:x,:y,:z]),axis2=[10,9])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),:,d->broadcast(>, d, 10)) --> isempty
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->1:1,d->2:3) --> larr(a=[104 107],b=[4 7],axis1=darr(k=[:x]),axis2=[9,8])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2:4,d->0:1) --> @larr(a=[NA 102;NA 103;NA NA],b=[NA 2;NA 3;NA NA],axis1[@darr(k=[:y,:z,NA])],axis2[@nalift([NA,10])])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2:4,d->1) --> @larr(a=[102,103,NA],b=[2,3,NA],axis1[@darr(k=[:y,:z,NA])])
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2,d->1) --> LDict(:a=>Nullable(102), :b=>Nullable(2))
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2,d->0) --> LDict(:a=>Nullable{Int}(), :b=>Nullable{Int}())
    @fact extract(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)), d->-1:1,d->[2,3]) --> @larr(a=[NA NA;NA NA;104 107],b=[NA NA;NA NA;4 7])
    @fact gtake(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable(5)),2) --> LDict(:a=>Nullable(1),:b=>Nullable(3))
    @fact gtake(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),2) --> LDict(:a=>Nullable(1),:b=>Nullable(3))
    @fact gtake(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),-2) --> LDict(:b=>Nullable(3),:c=>Nullable("x"))
    @fact gtake(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable(5)),-2) --> LDict(:b=>Nullable(3),:c=>Nullable(5))
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),[:a,:c]) --> LDict(:a=>Nullable(1),:c=>Nullable("x"))
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),[:a,:b]) --> LDict(:a=>Nullable(1),:b=>Nullable(3))
    @fact extract(LDict(:a=>Nullable(1.0),:b=>Nullable(3.0),:c=>Nullable(5.0)),[:a,:b]) --> LDict(:a=>Nullable(1.0),:b=>Nullable(3.0))
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),[:a,:b,:d]) --> LDict(:a=>Nullable(1),:b=>Nullable(3),:d=>Nullable{Any}())
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable(5)),[:a,:b,:d]) --> LDict(:a=>Nullable(1),:b=>Nullable(3),:d=>Nullable{Int}())
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),:a).value --> 1
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),:x) --> isnull
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),d->broadcast(==, d, :a)) --> LDict(:a=>Nullable(1))
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),_->2:3) --> LDict(:b=>Nullable(3), :c=>Nullable("x"))
    @fact extract(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),_->2).value --> 3
    @fact extract(nalift(LDict(:a=>1,:b=>3)), :a).value --> 1
    @fact extract(nalift(LDict(:a=>1,:b=>3,:c=>5)), x->2).value --> 3
    @fact extract(nalift(LDict(:a=>1,:b=>3,:c=>5)), x->[2,3]) --> nalift(LDict(:b=>3,:c=>5))

    @fact extract(nalift(LDict(:a=>1,:b=>3,:c=>'x')), x->2).value --> 3
    @fact extract(nalift(LDict(:a=>1,:b=>3,:c=>'x')), x->[2,3]) --> nalift(LDict(:b=>3,:c=>'x'))
    @fact extract(nalift(LDict(:a=>1,:b=>3,:c=>'x')), x->[1,2]) --> nalift(LDict(:a=>1,:b=>3))
    @fact dcube.gdrop(nalift(LDict(:a=>1,:b=>3,:c=>5)),2) --> LDict(:c=>Nullable(5))
    @fact dcube.gdrop(nalift(LDict(:a=>1,:b=>3,:c=>5)),3) --> isempty
    @fact discard([7,5,3,1],1:2) --> nalift([3,1])
    @fact discard([7,5,3,1],x->[1,3]) --> nalift([5,1])

    @fact gdrop(collapse_axes(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))), 2) --> larr(a=[2,5,3,6],b=['y','v','z','w'],axis1=darr(x1=[:m,:n,:m,:n],r=["B","B","C","C"]))
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), 2) --> isempty
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), 1) --> larr(a=[4 5 6],b=['u' 'v' 'w'], axis1=[:n], axis2=darr(r=["A","B","C"]))
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), -1) --> larr(a=[1 2 3],b=['x' 'y' 'z'], axis1=[:m], axis2=darr(r=["A","B","C"]))
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), [], 1) --> larr(a=[2 3;5 6],b=['y' 'z';'v' 'w'], axis1=[:m,:n], axis2=darr(r=["B","C"]))
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), [], -1) --> larr(a=[1 2;4 5],b=['x' 'y';'u' 'v'], axis1=[:m,:n], axis2=darr(r=["A","B"]))
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), 8) --> isempty
    @fact gdrop(collapse_axes(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"]))), -2) --> larr(a=[1,4,2,5],b=['x','u','y','v'],axis1=darr(x1=[:m,:n,:m,:n],r=["A","A","B","B"]))
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), -2) --> isempty
    @fact gdrop(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), -8) --> isempty
    @fact discard(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), [], darr(r=["X","A"])) --> larr(a=[2 3;5 6],b=['y' 'z';'v' 'w'],axis1=[:m,:n],axis2=darr(r=["B","C"]))
    @fact discard(larr(a=[1 2 3;4 5 6],b=['x' 'y' 'z';'u' 'v' 'w'], axis1=[:m,:n], axis2=darr(r=["A","B","C"])), [:n, :o], darr(r=["X","A"])) --> larr(a=[2 3],b=['y' 'z'],axis1=[:m],axis2=darr(r=["B","C"]))
    @fact gdrop(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),2) --> LDict(:c=>Nullable("x"))
    @fact gdrop(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),-2) --> LDict(:a=>Nullable(1))
    @fact size(gdrop(larr(a=rand(5,3), b=reshape(1:15,5,3), axis1=[:X,:Y,:Z,:U,:V]),5)) --> (0,3)
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),[:a,:c]) --> LDict(:b=>Nullable(3))
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),[:a,:b]) --> LDict(:c=>Nullable("x"))
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),[:a,:b,:d]) --> LDict(:c=>Nullable("x"))
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable(5)),[:a,:b,:d]) --> LDict(:c=>Nullable(5))
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)),1:2,nalift(1:3)) --> larr(a=[112 115],b=[12 15])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5),axis2=[:x,:y,:z,:u,:v]),2) --> larr(a=[101 104 107 110 113;103 106 109 112 115],b=[1 4 7 10 13;3 6 9 12 15],axis2=[:x,:y,:z,:u,:v])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)),d->broadcast(>, wrap_array(d), 2)) --> larr(a=[101 104 107 110 113;102 105 108 111 114],b=[1 4 7 10 13;2 5 8 11 14])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)),d->broadcast(>, wrap_array(d), 3)) --> larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5))
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z])),d->broadcast(==, d[:k], :y)) --> discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z])),(Nullable(:y),))
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z])),d->broadcast(==, d[:k], :w)) --> larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]))
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),[],d->broadcast(>, d, 8)) --> larr(a=reshape(107:115,3,3),b=reshape(7:15,3,3),axis1=darr(k=[:x,:y,:z]),axis2=[8,7,6])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),[],d->broadcast(>, d, 10)) --> larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->1:1,d->2:3) --> discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),LDict(:k=>Nullable(:x)),[8,9])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2:4,d->0:1) --> discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),darr(k=[:y,:z]), [100,10])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2:4,d->1:1) --> discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),darr(k=[:y,:z]), [100,10])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),d->2:4,d->1) --> discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5), axis1=darr(k=[:x,:y,:z]), axis2=[10,9,8,7,6]),darr(k=[:y,:z]), [100,10])
    @fact discard(larr(a=reshape(101:115,3,5),b=reshape(1:15,3,5)), d->-1:1,d->[2,3]) --> larr(a=[102 111 114;103 112 115],b=[2 11 14;3 12 15])
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),:a) --> LDict(:b=>Nullable(3), :c=>Nullable("x"))
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),:x) --> LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x"))
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),d->broadcast(==, d, :a)) --> LDict(:b=>Nullable(3), :c=>Nullable("x"))
    @fact discard(LDict(:a=>Nullable(1),:b=>Nullable(3),:c=>Nullable("x")),_->2:30) --> LDict(:a=>Nullable(1))
    @fact discard(larr(a=[1 2 3;4 5 6]), 1:1, 2:3) --> larr(a=[4]')

    @fact gdrop(nalift([1 3 2 5 4]), :, 2:3) --> isempty
    @fact gdrop(nalift([1 3 2 5 4]), [], 2:3) --> nalift([1 5 4])
    @fact gdrop(nalift([1 3 2 5 4]), [], 2) --> nalift([2 5 4])
    @fact gdrop(nalift([1 3 2 5 4]), [], -2) --> nalift([1 3 2])
    @fact gdrop(LDict(:a=>3, :b=>3, :c=>2), 2) --> LDict(:c=>2)
    @fact gdrop(LDict(:a=>3, :b=>3, :c=>2), -2) --> LDict(:a=>3)

    @fact namerge(@nalift([1,2,NA,4,NA]), @nalift([11,12,13,NA,NA])) --> @nalift([11,12,13,4,NA])
    @fact namerge(@nalift([1,2,NA,4,NA]), @nalift([11,12,13,NA,NA]), @nalift([NA,21,22,NA,25])) --> @nalift([11,21,22,4,25])
    @fact namerge(@nalift([1,2,NA,4,NA]), Nullable(3)) --> @nalift([3,3,3,3,3])
    @fact namerge(@nalift([1,2,NA,4,NA]), 3) --> @nalift([3,3,3,3,3])
    @fact namerge(@nalift([1,2,NA,4,NA]), Nullable{Int}()) --> @nalift([1,2,NA,4,NA])
    @fact namerge(Nullable(3), @nalift([1,2,NA,4,NA])) --> @nalift([1,2,3,4,3])
    @fact namerge(3, @nalift([1,2,NA,4,NA])) --> @nalift([1,2,3,4,3])
    @fact namerge(Nullable{Int}(), @nalift([1,2,NA,4,NA])) --> @nalift([1,2,NA,4,NA])
    @fact namerge(Nullable(1), Nullable{Int}()).value --> 1
    @fact namerge(1, Nullable{Int}()).value --> 1
    @fact namerge(Nullable{Int}(), Nullable(1)).value --> 1
    @fact namerge(Nullable{Int}(), 1).value --> 1
    @fact namerge(Nullable(1), Nullable(2)).value --> 2
    @fact namerge(1, 2).value --> 2
    @fact namerge(Nullable{Int}(), Nullable{Int}()) --> isnull
    @fact namerge(@nalift([1 2 NA;4 5 6])) --> @nalift([1 2 NA;4 5 6])
    @fact namerge(@nalift([1 2 NA;4 5 6])) --> @nalift([1 2 NA;4 5 6])
    @fact namerge(@larr([1 2 NA;4 5 6])) --> @larr([1 2 NA;4 5 6])
    @fact namerge(@larr([1 2 NA;4 5 6])) --> @larr([1 2 NA;4 5 6])
    @fact namerge(@darr(a=[1 2 NA;4 5 6])) --> @darr(a=[1 2 NA;4 5 6])
    @fact namerge(@darr(a=[1 2 NA;4 5 6])) --> @darr(a=[1 2 NA;4 5 6])
    @fact namerge(@larr(a=[1 2 NA;4 5 6],axis[:x,:y])) --> @larr(a=[1 2 NA;4 5 6],axis[:x,:y])
    @fact namerge(@larr(a=[1 2 NA;4 5 6],axis[:x,:y])) --> @larr(a=[1 2 NA;4 5 6],axis[:x,:y])
    @fact_throws namerge()
    @fact mapvalues(+, 1, darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0])) --> darr(a=[2 3;4 5],b=1.0*[2 3;4 5])
    @fact mapvalues(+, Nullable(1), darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0])) --> darr(a=[2 3;4 5],b=1.0*[2 3;4 5])
    @fact mapvalues(+, darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0]), 1) --> darr(a=[2 3;4 5],b=1.0*[2 3;4 5])
    @fact mapvalues(+, darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0]), Nullable(1)) --> darr(a=[2 3;4 5],b=1.0*[2 3;4 5])
    @fact mapvalues(+, darr(b=[11 12;13 14],a=[1 2;3 4]), darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0])) --> darr(b=1.0*[12 14;16 18],a=1.0*[2 4;6 8])
    @fact mapvalues(+, larr(b=[11 12;13 14],a=[1 2;3 4],axis1=[:x,:y]), darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0])) --> larr(b=1.0*[12 14;16 18],a=1.0*[2 4;6 8],axis1=[:x,:y])
    @fact mapvalues(+, larr(b=[11 12;13 14],a=[1 2;3 4],axis1=[:x,:y]), darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0]), 1) --> larr(b=1.0*[13 15;17 19],a=1.0*[3 5;7 9],axis1=[:x,:y])
    @fact mapvalues(+, larr(b=[11 12;13 14],a=[1 2;3 4],axis1=[:x,:y]), darr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0]), Nullable(1)) --> larr(b=1.0*[13 15;17 19],a=1.0*[3 5;7 9],axis1=[:x,:y])
    @fact mapvalues((x,y,z)->Nullable(1),1,larr(a=[1,2,3]),darr(a=[4,5,6])) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),1,darr(a=[1,2,3]),darr(a=[4,5,6])) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),darr(a=[1,2,3]),darr(a=[4,5,6]),2) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),darr(a=[4,5,6]),3,1) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),larr(a=[4,5,6]),2,1) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),larr(a=[4,5,6]),1,Nullable(3)) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),larr(a=[4,5,6]),1,Nullable(5)) --> nalift(LDict(:a=>1))
    @fact mapvalues((x,y,z)->Nullable(1),3,2,1).value --> 1
    @fact mapvalues(x->DataCubes.naop_plus(x,2), larr([1 2 3])) --> larr([3 4 5])
    @fact_throws mapvalues(+, larr(b=[11 12;13 14],a=[1 2;3 4],axis1=[:x,:y]), larr(a=[1 2;3 4],b=[1.0 2.0;3.0 4.0]))
    @fact (a=enumeration([:a,:b,:a]);push!(a, Nullable(:a));DataCubes.wrap_array(a)) --> DataCubes.wrap_array(enumeration([:a,:b,:a,:a]))
    @fact (a=enumeration([:a,:b,:a]);push!(a, Nullable{Symbol}());DataCubes.wrap_array(a)) --> DataCubes.wrap_array(@enumeration([:a,:b,:a,NA]))
    @fact (a=nalift([1.0,2.0,3.0]);push!(a, Nullable(5.0));a) --> nalift([1.0,2.0,3.0,5.0])
    @fact (a=nalift([1.0,2.0,3.0]);push!(a, Nullable{Float64}());a) --> @nalift([1.0,2.0,3.0,NA])
    @fact pick(darr(a=[1,2,3],b=[:x,:y,:z])[1], [:a]) --> LDict(:a=>Nullable(1))
    @fact pick(darr(a=[1,2,3],b=[:x,:y,:z])[1], :a).value --> 1
    @fact pick(darr(a=[1,2,3],b=[:x,:y,:z])[1], :a, :b) --> nalift([1,:x])
    @fact pick(darr(a=[1,2,3],b=[:x,:y,:z])[1], (:a,))[1].value --> 1
    @fact typeof(pick(darr(a=[1,2,3],b=[:x,:y,:z])[1], (:a,))) --> Array{Nullable{Int},1}
  end
end

end


text:
# Use baremodule to shave off a few KB from the serialized `.ji` file
baremodule fts_jll
using Base
using Base: UUID
import JLLWrappers

JLLWrappers.@generate_main_file_header("fts")
JLLWrappers.@generate_main_file("fts", UUID("d65627f6-89bd-53e8-8ab5-8b75ff535eee"))
end  # module fts_jll

ext: jl


text:
y_true = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
y_pred = [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

# absolute_error
@test absolute_error(y_true, y_pred) == [9, 7, 5, 3, 1, 1, 3, 5, 7, 9]

# percent_error
@test 1 == 1

# log_error
@test 1 == 1

# squared_error
@test squared_error(y_true, y_pred) == [81, 49, 25, 9, 1, 1, 9, 25, 49, 81]

# squared_log_error
@test 1 == 1

# absolute_percent_error
@test 1 == 1

# mean_error
@test mean_error(y_true, y_pred) == 0.0

# mean_absolute_error
@test mean_absolute_error(y_true, y_pred) == 5.0

# median_absolute_error
@test median_absolute_error(y_true, y_pred) == 5.0

# mean_percent_error
@test 1 == 1

# median_percent_error
@test 1 == 1

# mean_squared_error
@test mean_squared_error(y_true, y_pred) == 33.0

# median_squared_error
@test median_squared_error(y_true, y_pred) == 25.0

# sum_squared_error
@test sum_squared_error(y_true, y_pred) == 330

# mean_squared_log_error
@test 1 == 1

# mean_absolute_percent_error
@test 1 == 1

# median_absolute_percent_error
@test 1 == 1

# symmetric_mean_absolute_percent_error
@test 1 == 1

# symmetric_median_absolute_percent_error
@test 1 == 1

# mean_absolute_scaled_error
@test mean_absolute_scaled_error(y_true, y_pred) == 1.125

# total_variance_score
@test total_variance_score(y_true, y_pred) == 82.5

# explained_variance_score
@test explained_variance_score(y_true, y_pred) == 82.5

# unexplained_variance_score
@test unexplained_variance_score(y_true, y_pred) == 330

# r2_score
@test r2_score(y_true, y_pred) == 1.0


text:
export GenomicFeatureTrack, GeneTrack

import DataStructures: heappush!, heappop!

#-----------------------------------------------------------------------------------------------
#
# GenomicFeatureTrack
#

# GenomicFeatures (passed in :features) are organized into lanes of height :lane_height
#
# The GenomicFeatureTrack assigns a lane to each feature,
# and calls drawFeature(cc, tc, feature, lane).
# drawFeature is supposed to draw the feature at y-positions lane-0.5 .. lane + 0.5

"""
    GenomicFeatureTrack(;params...)

GenomicFeatures (passed in :features) assigns each feature to a lane.
Currently only plotting of genes is implemented.

# Parameters

- `:features` Vector of genomic features to plot
- `:feature_label_spacing`
- `:feature_label_font`
- `:feature_label_color`
- `:feature_height`
- `:feature_fill`

- `:title` track title
- `:title_color`
- `:title_font`

- `:width` width of track
- `:height` height of track

- `:margin_top`
- `:margin_bottom`
- `:margin_left`
- `:margin_right`

# TODO

Change this, such that we can pass in all genomic features with
methods (instead of AbstractGenomicFeatures)

- `bpStart`
- `bpEnd`
- `strand`
- `name`

and also `drawFeature(cc, tc, feature, lane)`

"""
struct GenomicFeatureTrack <: AbstractRefFrame
    # AbstractRefFrame
    transform::AbstractTransform
    children::Vector{AbstractRefFrame}
    params::Dict

    function GenomicFeatureTrack(;params...)
        new(Translation(0., 0.), AbstractRefFrame[], Dict(params))
    end
end

function draw(cc::Cairo.CairoContext, tc::Vector{T}, gt::GenomicFeatureTrack, xmin, xmax) where T <: AbstractRefFrame

    # this is supposed to compute the height of bbox from the number of lanes required

    gfs = getparam(tc, :features)
    sort!(gfs)

    # determine tracks

    # user coords are not yet set here
    # compute scale for user coords / device coords
    # here we assume that only translations are used going to root window
    scale = (xmax - xmin) / (getparam(tc, :width) - getparam(tc, :margin_left) - getparam(tc, :margin_right))

    heap = []
    maxtrack = 0
    track = 0
    tracks = Int64[]

    # specify font to be used such that we can determine extent of labels
    Cairo.set_font_face(cc, getparam(tc, :feature_label_font, "SansSerif 6"))

    # could use duck typing instead of requiring AbstractGenomicFeature
    # use function barrier for speed if needed
    for gf::AbstractGenomicFeature in gfs
        label = name(gf)
        if strand(gf) == '+' label = label * ">" end
        if strand(gf) == '-' label = "<" * label end
        labelwidth = textwidth(cc, label) * scale # label width in user coords
        if length(heap) == 0
            track = 1
        elseif heap[1][1] < (bpStart(gf) - labelwidth - getparam(tc, :feature_label_spacing, 0mm)*scale)
            track = heap[1][2]
            heappop!(heap)
        else
            track = length(heap) + 1
        end
        heappush!(heap, (bpEnd(gf), track))

        if track > maxtrack
            maxtrack = track
        end

        push!(tracks, track)
    end

    # now tracks[i] contains track for feature gfs[i]

    # set up scaling
    lane_height = getparam(tc, :feature_height, 6mm)
    width = getparam(tc, :width)
    height = lane_height * maxtrack

    bbox = Rect(getparam(tc, :margin_left),
                getparam(tc, :margin_top),
                width - getparam(tc, :margin_left) - getparam(tc, :margin_right),
                height - getparam(tc, :margin_top) - getparam(tc, :margin_bottom))
    units = Rect(xmin, 0.5, xmax - xmin, maxtrack)
    datactx = RefFrame(transform = Scaling(bbox, units))

    # Cairo stuff
    # title
    title = getparam(tc, :title, "")
    if title != ""
        Cairo.set_source_rgba(cc, rgba(getparam(tc, :title_color))...)
        Cairo.set_font_face(cc, getparam(tc, :title_font))
        xp, yp = user2dev(tc, 0.0, bbox.h/2)
        Cairo.text(cc, xp, yp, title, halign="center", valign="top", angle = 90.0)
        Cairo.stroke(cc)
    end

    # features
    # do this on CairoRecordingsurface, then copy
    s = Cairo.CairoRecordingSurface()
    fcc = Cairo.CairoContext(s)

    feature_rgba = rgba(getparam(tc, :feature_fill, "light blue"), 1.0)
    feature_label_font = getparam(tc, :feature_label_font)
    feature_label_rgba = rgba(getparam(tc, :feature_label_color, "light grey"))
    feature_label_spacing = getparam(tc, :feature_label_spacing, 0mm)

    for (gf::AbstractGenomicFeature, lane) in zip(gfs, tracks)

        xp, yp = user2dev(vcat(tc, datactx), [bpStart(gf), bpEnd(gf)], [lane, lane])

        Cairo.set_font_face(fcc, feature_label_font)
        Cairo.set_source_rgba(fcc, feature_label_rgba...)
        label = name(gf)
        if strand(gf) == '+' label = label * ">" end
        if strand(gf) == '-' label = "<" * label end
        Cairo.text(fcc, xp[1] - feature_label_spacing, (yp[1] + yp[2])/2, label, halign="right", valign="center")
        Cairo.stroke(fcc)

        # test textextent
        #labelwidth = textwidth(cc, label) * scale
        #Cairo.save(cc)
        #Cairo.set_source_rgba(cc, 0.5, 0.5, 0.5, 0.3)
        #myx, myy = user2dev(vcat(tc, datactx), [bpStart(gf) - labelwidth, bpStart(gf)], [lane - 0.5, lane + 0.5])
        #Cairo.rectangle(cc, myx[1] - feature_label_spacing, myy[1], myx[2] - myx[1], myy[2] - myy[1])
        #Cairo.fill(cc)
        #Cairo.restore(cc)

        Cairo.set_source_rgba(fcc, feature_rgba...)
        Cairo.set_line_width(fcc, 1)
        Cairo.move_to(fcc, xp[1], yp[1])
        Cairo.line_to(fcc, xp[2], yp[2])
        Cairo.stroke(fcc)

        coding, noncoding = cdss(gf), exons(gf)
        for (start, stop) in coding
            xp, yp = user2dev(vcat(tc, datactx), [start, stop], [lane - 0.45, lane + 0.45])
            Cairo.rectangle(fcc, xp[1], yp[1], xp[2] - xp[1], yp[2] - yp[1])
        end
        for (start, stop) in noncoding
            xp, yp = user2dev(vcat(tc, datactx), [start, stop], [lane - 0.25, lane + 0.25])
            Cairo.rectangle(fcc, xp[1], yp[1], xp[2] - xp[1], yp[2] - yp[1])
        end
        Cairo.fill(fcc)

    end

    # copy relevant bbox from CairoRecordingSurface
    dbbox = user2dev(tc, bbox)
    Cairo.set_source_surface(cc, s, 0, 0)
    Cairo.rectangle(cc, dbbox.x, dbbox.y, dbbox.w, dbbox.h)
    Cairo.fill(cc)
    Cairo.destroy(fcc)
    Cairo.finish(s)

    user2dev(gt, Rect(0.0, 0.0, width, height))
end

"""
    GeneTrack(chrom, bpStart, bpEnd;
              gtf = GViz.pkgpath("data", "gencode.v35.annotation.gtf.bgz"),
              params...)

Create a track with genes based on GTF file `gtf`.

# Parameters

- `:gtf` path to tabix-indexed GTF file with genes

- `:feature_label_spacing`
- `:feature_label_font`
- `:feature_label_color`
- `:feature_height`
- `:feature_fill`

- `:title` track title
- `:title_color`
- `:title_font`

- `:width` width of track
- `:height` height of track

- `:margin_top`
- `:margin_bottom`
- `:margin_left`
- `:margin_right`

"""
GeneTrack(chrom, bpStart, bpEnd;
          gtf = GViz.pkgpath("data", "gencode.v35.annotation.gtf.bgz"),
          title = "Genes",
          params...) =
              GenomicFeatureTrack(;features = genes(GTFFile(tabix(gtf, chrom, bpStart, bpEnd))),
                                  title = title,
                                  params...)


text:
stencil(T, grd::OceanGrid) = unique(directions(T, grd))
stencil(grd::OceanGrid, T) = stencil(T, grd)
export stencil

# Aux functions to center the stencil
function modcent(x, r)
    x = mod(x, r)
    return x < r/2 ? x : x - r
end
function modcent(x::T, r::T) where {T<:CartesianIndex{3}}
    return CartesianIndex(modcent(x.I[1], r.I[1]), modcent(x.I[2], r.I[2]), modcent(x.I[3], r.I[3]))
end
function modcent!(x::Vector{T}, r::T) where {T<:CartesianIndex{3}}
    for (i, xᵢ) in enumerate(x)
        x[i] = modcent(xᵢ, r)
    end
    return x
end

function directions(T, grd)
    i, j, v = findnz(T) # linear indices of T
    return directions(i, j, grd)
end
function directions(i, j, grd)
    iwet = findall(vec(iswet(grd)))
    tmp = CartesianIndices(size(grd))
    @views JI = tmp[iwet[j]] - tmp[iwet[i]]
    modcent!(JI, CartesianIndex(size(grd)))
    return JI
end

# Labelling stencil directions
label(dir::CartesianIndex) = label(dir.I)
function label(dir::Tuple{T,T,T} where {T<:Int64})
    dy, dx, dz = dir
    return string(labelz(dz), labely(dy), labelx(dx))
end
labelz(dz::Int64) = dz == 0 ? "" : dz > 0 ? string("Below", labelz(dz - 1)) : string("Above", labelz(dz + 1))
labelx(dx::Int64) = dx == 0 ? "" : dx > 0 ? string("East", labelx(dx - 1)) : string("West", labelx(dx + 1))
labely(dy::Int64) = dy == 0 ? "" : dy > 0 ? string("North", labely(dy - 1)) : string("South", labely(dy + 1))

# Symmetric indices with values of T[i,j] and T[j,i]
function symmetric_IJVVᵀ(T)
    i, j, val = findnz(T)
    # symmetric indices
    ijsym = unique(zip([i;j], [j;i]))
    n = length(ijsym)
    I, J = Vector{Int64}(undef, n), Vector{Int64}(undef, n)
    val, valᵀ = Vector{Float64}(undef, n), Vector{Float64}(undef, n)
    for (k, ij) in enumerate(ijsym)
        i, j = ij[1], ij[2]
        I[k], J[k] = i, j
        val[k], valᵀ[k] = T[i,j], T[j,i]
    end
    return I, J, val, valᵀ
end

function directional_transport(T, grd, dir)
    Isym, Jsym, Vsym, Vᵀsym = symmetric_IJVVᵀ(T)
    dirs = directions(Isym, Jsym, grd)
    n = size(T,1)
    v = volumevec(grd)
    return _directional_transport(dir, dirs, n, Isym, Jsym, Vsym, Vᵀsym, v)
end

function _directional_transport(dir, dirs, n, Isym, Jsym, Vsym, Vᵀsym, v)
    all(Tuple(dir) .== 0) && error("No self directional transport")
    idir = findall(isequal(dir), dirs)
    @views i, j, val, valᵀ = Isym[idir], Jsym[idir], Vsym[idir], Vᵀsym[idir]
    return sparse([i; i], [j; i], [val; -v[j] ./ v[i] .* valᵀ], n, n)
end
function directional_transports(T, grd)
    st = stencil(grd, T)
    Isym, Jsym, Vsym, Vᵀsym = symmetric_IJVVᵀ(T)
    dirs = directions(Isym, Jsym, grd)
    n = size(T,1)
    v = volumevec(grd)
    return Dict((label(dir), _directional_transport(dir, dirs, n, Isym, Jsym, Vsym, Vᵀsym, v)) for dir in st if !isempty(label(dir)))
end
export directional_transport, directional_transports


text:
# This file is a part of ValueShapes.jl, licensed under the MIT License (MIT).

using ValueShapes
using Test

using Random
using ArraysOfArrays
import TypedTables

@testset "const_value_shape" begin
    @inferred(size(ConstValueShape(42))) == ()
    @inferred(eltype(ConstValueShape(42))) == Int
    @inferred(totalndof(ConstValueShape(42))) == 0

    @inferred(size(ConstValueShape(rand(2,3)))) == (2,3)
    @inferred(eltype(ConstValueShape(rand(Float32,2,3)))) == Float32
    @inferred(totalndof(ConstValueShape(rand(2,3)))) == 0

    @test @inferred(ConstValueShape([1 4; 3 2])(undef)) == [1 4; 3 2]
    @test @inferred(ConstValueShape([1 4; 3 2])(Int[])) == [1 4; 3 2]

    shape = ConstValueShape([1 4; 3 2])
    @test typeof(@inferred(Vector(undef, shape))) == Vector{Int}
    @test size(@inferred(Vector(undef, shape))) == (0,)
end


text:
"""
    get_table_names(source)::Tuple{Symbol}

Get the names of the tables in `source`.

```jldoctest
julia> using QuerySQLite

julia> database = Database(joinpath(pathof(QuerySQLite) |> dirname |> dirname, "test", "Chinook_Sqlite.sqlite"));

julia> get_table_names(getfield(database, :source))
(:Album, :Artist, :Customer, :Employee, :Genre, :Invoice, :InvoiceLine, :MediaType, :Playlist, :PlaylistTrack, :Track)
```
"""
function get_table_names(source::DB)
    as_symbols(tables(source).name)
end
export get_table_names

"""
    get_column_names(source, table_name)::Tuple{Symbol}

Get the names of the columns in `table_name` in `source`.

```jldoctest example
julia> using QuerySQLite

julia> database = Database(joinpath(pathof(QuerySQLite) |> dirname |> dirname, "test", "Chinook_Sqlite.sqlite"));

julia> get_column_names(getfield(database, :source), :Album)
(:AlbumId, :Title, :ArtistId)
```
"""
function get_column_names(source::DB, table_name)
    as_symbols(columns(source, String(table_name)).name)
end
export get_column_names

"""
    struct Database{Source}

A wrapper for an external database. `source` need only support
[`get_table_names`](@ref) and [`get_column_names`](@ref).
"""
struct Database{Source}
    source::Source
end

"""
    Database(filename::AbstractString)

Guess the database software from the filename.

```jldoctest
julia> using QuerySQLite

julia> database = Database(joinpath(pathof(QuerySQLite) |> dirname |> dirname, "test", "Chinook_Sqlite.sqlite"));

julia> database.Track
?x9 SQLite query result
TrackId │ Name                                      │ AlbumId │ MediaTypeId
────────┼───────────────────────────────────────────┼─────────┼────────────
1       │ "For Those About To Rock (We Salute You)" │ 1       │ 1
2       │ "Balls to the Wall"                       │ 2       │ 2
3       │ "Fast As a Shark"                         │ 3       │ 2
4       │ "Restless and Wild"                       │ 3       │ 2
5       │ "Princess of the Dawn"                    │ 3       │ 2
6       │ "Put The Finger On You"                   │ 1       │ 1
7       │ "Let's Get It Up"                         │ 1       │ 1
8       │ "Inject The Venom"                        │ 1       │ 1
9       │ "Snowballed"                              │ 1       │ 1
10      │ "Evil Walks"                              │ 1       │ 1
... with more rows, and 5 more columns: GenreId, Composer, Milliseconds, Bytes, UnitPrice
```
"""
function Database(filename::AbstractString)
    if endswith(filename, ".sqlite")
        Database(SQLite.DB(filename))
    else
        throw(ArgumentError("Unsupported database type for $filename"))
    end
end
export Database

get_source(source_tables::Database) = getfield(source_tables, :source)

# getproperty overloading allows direct access to each table in the database
function getproperty(source_tables::Database, table_name::Symbol)
    SourceCode(get_source(source_tables),
        Expr(:call, getproperty, source_tables, table_name)
    )
end


text:
module Reckoner

    include("interface.jl")

    export AbstractMatch, AbstractMatches

    export DefaultMatch, DefaultMatches

    export ReckonerInstance

    export reckoner_defaults

    export  aup,
            weight,
            challenge_window,
            skill,
            rating,
            eff_challenge,
            win_chances,
            display_rank,
            player_win_chances,
            weights,
            ratings,
            challenge_windows,
            skills

    export  challenge,
            timestamp,
            win,
            team_id

end # module


text:
# BCF
# ===

include("record.jl")
include("reader.jl")
include("writer.jl")


text:
# [src/vectors_to_states.jl]

"""
Convert a state vector history to a series of states given those states
as inputs. If the total number of numeric states is ns and the number of
time steps represented is nt, then yv is nt-by-ns. The remaining
arguments are example states. The outputs will have the same form as the
example states, but will be arrays of these states, each nt long, with
appropriate values from yv.

When a state is a column or row vector of numeric types, the output will
be an nt-by-x array where x is length(state)

When a state is an n-dimensional matrix (neither column nor vector), the
output will have the dimensions of the matrix concatenated on the first
unused dimension. That is, if the state is a-by-b, the output will be
a-by-b-by-nt.

When a state is a struct, the output will be a struct array of length nt.

Otherwise, the state will be an array of length nt, each element containing
the example type filled in with values from yv.

y1, y2, ... yn = vectors_to_states(yv, y1, y2, ... ,yn);

Inputs:

    yv              State vector history (nt-by-ns)
    y1, y2, ... yn  Example states

Outputs:

    y1, y2, ... yn  State histories as described above

Examples:

    # A history of 3 samples of a row vector:

    yv = [state_to_vector([1  2])';    # Sample 1
          state_to_vector([7  8])';    # Sample 2
          state_to_vector([13 14])'];  # Sample 3

    y  = zeros(1, 2);                  # Example state
    ys = vectors_to_states(yv, y)      # 3-by-2 output

    # A history of 3 samples of 3 different states: a column vector, a cell
    #    array, and a struct:

    # Make the example states.
    mutable struct TEST_STRUC
        a
        b
    end

    y1  = zeros(3, 1);
    y2  = [1 2; 3 4];
    y3  = TEST_STRUC(1, 2);

    # Make the history.
    y1v = [state_to_vector([1  2  3]')';
           state_to_vector([7  8  9]')';
           state_to_vector([13 14 15]')'];

    y2v = [state_to_vector([1 2; 7 8])';
           state_to_vector([3 4; 9 0])';
           state_to_vector([5 6; 1 2])'];

    y3v = [1 2; 3 4; 5 6];
    yv  = [y1v y2v y3v];

    # Get the state histories.
    y1s, y2s, y3s = vectors_to_states(yv, (y1, y2, y3) )  # NOTE that there are multiple reference states, input as a tuple

See also: vector_to_state, state_to_vector.

Online doc: http://www.anuncommonlab.com/doc/odehybrid/vectors_to_states.html

Copyright 2014 An Uncommon Lab
"""
# Called with there is only one reference state
function vectors_to_states(yv, varargin)

    # Number of samples
    ns = size(yv, 1);

    # Number of states
    nx = size(yv, 2) # There is only ONE reference state

    # For each type of state...
    count = 0;
    varargout = Array{Any}(undef, nx) # zeros(1, nx) #cell(1, nx);

    # If there are no states, return empties.
    if isempty(yv)
        return;
    end

    for x = 1:length(varargin)

        # See if it's just a bunch of numbers (easy!).
        if isa(varargin[x], Number) || isa(varargin[x], Char)
            nv  = length(varargin[x]);
            yvx = yv[:, count .+ (1:nv)];
            szs = size(varargin[x]);

            if length(szs) == 0   # Empty, meaning varargin[x] is a scalar (Which should ALWAYS be the case)
                varargout[x] = reshape(yvx, (1, ns)) # NOT casting to type of input...
            end

            count = count + nv;

        # Check for an array of numbers or chars
        elseif (isa(varargin[x], Array{<:Number}) && (any(size(varargin[x]) .== 1)) ) ||
                 (isa(varargin[x], Array{<:Char}) && (any(size(varargin[x]) .== 1)) )

            nv = length(varargin[x]);
            yvx = yv[:, count .+ (1:nv)]
            szs = size(varargin[x]);

            if length(szs) == 2 && any(szs[1:2] .== 1)
                varargout[x] = yvx
            else
                @warn "Potential problem in vecs to states (matrix not array)!"
            end
            count += nv

        # See if it is a struct
        elseif length(varargin[x]) == 1

            vargout[x] = vargin[x];
            dummy = state_to_vector(varargin[x]);
            yvx = yv[:, count + (1:maximum(size(dummy)))]
            for k = 1:ns
                varargout[x][k, 1] = vector_to_state(yvx[k, :], varargin[x]);
            end
            count += maximum(size(dummy));

        else

            varargout[x] = Array{Any}(undef, ns) #zeros(ns, 1);
            dummy = state_to_vector(varargin[x]);
            yvx = yv[:, count .+ (1:maximum(size(dummy)))];
            for k = 1:ns
                t, _ = vector_to_state(yvx[k, :], varargin[x]);
                varargout[x][k] = deepcopy(t)
            end
        end

    end

    return vec_to_mat(varargout)'
end

function vectors_to_states(yv, varargin::Tuple)
    """
        Alternate version run when length(varargin) > 1 (i.e., multiple reference states)
    """

    # Number of samples
    ns = size(yv, 1);

    # Number of states
    nx = length(varargin); # Elements in tuple

    # For each type of state...
    count = 0;
    varargout = Array{Any}(undef, nx)

    # If there are no states, return empties.
    if isempty(yv)
        return;
    end

    for x = 1:length(varargin)

        # See if it's just a bunch of numbers (easy!).
        if isa(varargin[x], Number) || isa(varargin[x], Char)
            nv  = length(varargin[x]);
            yvx = yv[:, count .+ (1:nv)];
            szs = size(varargin[x]);

            if length(szs) == 0   # Empty, meaning varargin[x] is a scalar (Which should ALWAYS be the case)
                varargout[x] = reshape(yvx, (1, ns)) # NOT casting to type of input...
            end

            count = count + nv;

        # Check for an array of numbers or chars
        elseif (isa(varargin[x], Array{<:Number}) && (any(size(varargin[x]) .== 1)) ) ||
                 (isa(varargin[x], Array{<:Char}) && (any(size(varargin[x]) .== 1)) )

            nv = length(varargin[x]);
            yvx = yv[:, count .+ (1:nv)]
            szs = size(varargin[x]);

            if length(szs) == 2 && any(szs[1:2] .== 1)
                varargout[x] = yvx
            else
                @warn "Potential problem in vecs to states (matrix not array?)!"
            end
            count += nv

        else
            varargout[x] = Array{Any}(undef, ns) #zeros(ns, 1);
            dummy = state_to_vector(varargin[x]);
            yvx = yv[:, count .+ (1:maximum(size(dummy)))];
            for k = 1:ns
                t, _ = vector_to_state(yvx[k, :], varargin[x]);
                varargout[x][k] = deepcopy(t)
            end

            count += maximum(size(dummy))
        end

    end

    return varargout
end


text:
using GLPKMathProgInterface, GLPK
using MathProgBase
using Compat.Test

include("params.jl")
include("setbounds.jl")
include("mathprog.jl")


text:
"""
    tex2svg_string(formula::String)
    tex2svg_string(formula::LaTeXString; modifylatex = true, modifysvgcolor = true)
    --> string

Relies on NodeJS to call the package matjax, tex2svg. Based off DocumenterEpub 'prerender_mathjax'.
You may need elevated privileges to spawn NodeJS.

modifylatex = true: Calls 'modify_latex', which subscripts bracketed indexes and other things.
modifysvgcolor = true: Calls Replace currentColor with the last color and opacity from 'setcolor', 'setopacity' or 'sethue'.
"""
function tex2svg_string(formula::String)
    mathjaxfile = escape_string(realpath(joinpath(@__DIR__, "..", "mathjax", "node-main.js")))
    @assert isfile(mathjaxfile)
    adapted_formula = escape_string(replace(formula, '\'' => "\\prime"))
    svgs = cd(dirname(mathjaxfile)) do
        js_command = """
        require('$(mathjaxfile)').init({
            loader: {load: ['input/tex', 'output/svg', '[tex]/boldsymbol']}, svg: {
                fontCache: 'none',
                'localID':$(rand(1:100000))}
        }).then((mathjaxfile) => {
            const svg = MathJax.tex2svg('$adapted_formula');
            console.log(MathJax.startup.adaptor.innerHTML(svg));
        }).catch((err) => console.log("NodeJS was ran and output: " + err.message));
        """
        fullcmd = `$(NodeJS.nodejs_cmd()) -e $js_command`
        try String(read(fullcmd))
        catch err
            @warn "If on windows, you may need to run your editor or julia with some administrator privileges."
            @warn "Program: $(NodeJS.nodejs_cmd())"
            @warn "Option: -e"
            @warn "js_command: $js_command"
            @error err
        end
    end
    @assert !startswith(svgs, "NodeJS was ran and output") svgs
    svgs
end
function tex2svg_string(formula::LaTeXString; modifylatex = true, modifysvgcolor = true)
    bw = modifylatex ? modify_latex(formula) : String(formula)
    sv = tex2svg_string(bw)
    modifysvgcolor ? modify_svg_color(sv) : sv
end

"""
    prepare_latexify!(eq::Expr)

Modify an expression in-place before passing to latexify
"""
function prepare_latexify!(ex::Expr; depth = 0)
    if ex.head == :call
        if ex.args[1] == :∙
            ex.args[1] = :*
        elseif ex.args[1] == :√
            # TODO
            #println("Found it")
        end
    end
    for a in ex.args
        if isa(a, Expr)
            prepare_latexify!(a; depth = depth + 1)
        else
        end
    end
    ex
end

"""
    show_expression(ex::Expr; depth = 0)

For debugging, show an expression tree recursively with indents.
"""
function show_expression(ex::Expr; depth = 0)
    inde = repeat(" ", depth * 4)
    println(inde, ex.head)
    for a in ex.args
        if isa(a, Expr)
            printstyled(inde, "->", "\n"; color= :black)
            show_expression(a; depth = depth + 1)
        else
            if isa(a, Symbol)
                if isdefined(Main, a)
                    printstyled(inde, a, "\n"; color=:green)
                else
                    printstyled(inde, a, "\n"; color=:red)
                end
            else
                println(inde, a)
            end
        end
    end
    printstyled(inde, "<-", "\n"; color= :black)
    ex
end

"""
    modify_latex(formula)
    --> string
Iteratively
    - get rid of latex outer enclosing dollar and paranthesises.
    - get rid of latex outer enclosing paranthesises.
    - fix display of unit superscripts
    - remove underscore escaping
    - reorder √ function to square root (see @warn)
    - replace bold font unicode with latex boldfont
    - replace square bracket with contents in subscript
"""
function modify_latex(formula::String)::String
    st = String(formula)
    if occursin("g\\left( t \\cdot", st)
        @error "---"
    else
        #@show "OK"
    end
    if startswith(st, "\$") && endswith(st, "\$")
        # get rid of latex outer enclosing dollar and paranthesises.
        modify_latex(st[nextind(st, begin, 1):prevind(st, end, 1)])
    elseif startswith(st, "\\left(") && endswith(st, "\\right)")
        # get rid of latex outer enclosing paranthesises.
        output = st[nextind(st, begin, 6):prevind(st, end, 7)]
        #println("1")
        #@show st
        #@show output
        modify_latex(output)
    elseif occursin("{\\vysmblkcircle}", st)
        # drop {\\vysmblkcircle} used within units (no space)
        output = replace(st, "{\\vysmblkcircle}" => "")
        #println("2")
        #@show st
        #@show output
        modify_latex(output)
    elseif occursin("\\^-{^", st)
        # fix display of unit superscripts
        output = replace(st, "\\^-{^" => "{^-}{^")
        #println("3")
        #@show st
        #@show output
        modify_latex(output)
    elseif occursin("\\_", st)
        # remove underscore escaping
        regex = r"\\_(\w+)"
        ma = match(regex, st)
        if !isnothing(ma)
            captured = ma.captures[1]
            matched = ma.match
            replacement = "_{" * captured * "}"
            output = replace(st, matched => replacement; count = 1)
            #println("4")
            #@show st
            #@show output
            @assert st != output
            modify_latex(output)
        else
            @error "Bad regex"
        end
    elseif occursin("\\sqrt\\left( ", st)
        # reorder √ function to square root
        #"\\sqrt\\left( capture \\right)"
        # => \\sqrt{capture}
        regex = r"\\sqrt\\left\( (.*?) \\right\)"
        ma = match(regex, st)
        if !isnothing(ma)
            captured = ma.captures[1]
            matched = ma.match
            replacement = "\\sqrt{" * captured * "} "
            output = replace(st, matched => replacement; count = 1)
            @warn """This replacement may mess up nested expression. Consider pipelining through prepare_latexify (e.g. @@ev_draw "text" expr)"""
            show_colorful_regex(st, ma)

            modify_latex(output)
        else
            @error "Bad regex"
        end
    elseif occursin(r"\\bf([a-zA-Z])", st)
        # Replace bold font unicode
        # \\bfX
        # =>  \\\boldsymbol{X}
        regex = r"\\bf([a-zA-Z])"
        ma = match(regex, st)
        if !isnothing(ma)
            captured = ma.captures[1]
            matched = ma.match
            replacement = "\\boldsymbol{" * captured * "}"
            output = replace(st, matched => replacement; count = 1)
            #println("7")
            #@show st
            #@show output
            modify_latex(output)
        else
            @error "Bad regex"
        end
    else
        # replace square bracket with contents in subscript
        # Also: The only exit point from this iterative function.
        regex = r"\\left\[(.*?)\\right\]"
        ma = match(regex, st)
        if !isnothing(ma)
            captured = ma.captures[1]
            matched = ma.match
            replacement = "_{" * captured * "}"
            output = replace(st, matched => replacement; count = 1)
            #println("8")
            #@show st
            #@show output
            @assert st != output
            modify_latex(output)
        else
            st
        end
    end
end
modify_latex(formula::LaTeXString) = modify_latex(String(formula))

"""
    show_colorful_regex(st, ma::RegexMatch)

For debugging
"""
function show_colorful_regex(st, ma::RegexMatch)
    printstyled(st * "\n", color=:green)

    str = repeat('-', ma.offset - 1)
    str *= repeat('|', length(ma.match))
    str *= repeat('-', length(st) - length(ma.match) - ma.offset + 1)
    printstyled(str * "\n", color=:green)

    if length(ma.captures) > 0
        str = repeat('-', ma.offsets[1] - 1)
        str *= repeat('|', length(ma.captures[1]))
        printstyled(str * "\n", color=:green)
    end

    if length(ma.captures) > 1
        str = repeat('-', ma.offsets[2] - 1)
        str *= repeat('|', length(ma.captures[2]))
        printstyled(str * "\n", color=:green)
    end

    printstyled(st[1:(ma.offset - 1)], color=:red)
    printstyled(ma.match, color=:yellow)
    printstyled(st[(ma.offset + length(ma.match)):end] * "\n", color=:red)

    if length(ma.captures) > 0
        printstyled(st[1:(ma.offsets[1] - 1)], color=:red)
        printstyled(ma.captures[1], color=:yellow)
        printstyled(st[(ma.offsets[1] + length(ma.captures[1])):end] * "\n", color=:red)
    end

    if length(ma.captures) > 1
        printstyled(st[1:(ma.offsets[2] - 1)], color=:red)
        printstyled(ma.captures[2], color=:yellow)
        printstyled(st[(ma.offsets[2] + length(ma.captures[2])):end] * "\n", color=:red)
    end
end
"""
    modify_svg_color(svgstring::String)::String

Remove local color definitions (normally defined by style sheet) with
an extended style definition.
Both 'stroke' and 'fill' styles equal the last color and opacity from 'setcolor', 'setopacity' or 'sethue'
Primarily for svgs output by MathJax via NodeJS.
"""
function modify_svg_color(svgstring::String)::String
    c = get_current_RGBA()
    r = Int(round(c.r * 255))
    g = Int(round(c.g * 255))
    b = Int(round(c.b * 255))
    a = c.alpha
    removes = "stroke=\"currentColor\" fill=\"currentColor\" "
    step1 = replace(svgstring, removes => "")
    olds = "style=\""
    cols = "rgb($r, $g, $b)"
    opacs = "-opacity: $a"
    sfills = "fill: " * cols * "; " * "fill" * opacs * "; "
    sstrokes = "stroke: " * cols * "; " * "stroke" * opacs * "; "
    styles = sfills * sstrokes
    news = olds * styles
    replace(step1, olds  => news)
end

"""
    draw_expr(pt, scalefactor, eq::Expr;
        abovedisplayskip = 6scalefactor, belowdisplayskip = 6scalefactor, indent = EM)
    -> (upper left point, lower right point, scalefactor)

Iterative, place each line in eq on the image below each other.

abovedisplayskip is applied before every equation.
belowdisplayskip is applied after every equation.
indent is offset to right
"""
function draw_expr(pt, scalefactor, eq::Expr;
        abovedisplayskip = 6scalefactor, belowdisplayskip = 6scalefactor, indent = EM)
    ptorg = pt
    ptbr = pt
    ptul = pt
    # Replace dot multiplication by ordinary multiplication,
    # and √ with sqrt.
    prepare_latexify!(eq)
    # Consider replacing the expression walk below with MacroTools postwalk.
    # ...if that is clearer.
    if eq.head != :block
        draw_single_expr(pt, scalefactor, eq; abovedisplayskip, belowdisplayskip, indent)
    else
        # Multi-line - iterate and display below.
        geniter = (a for a in eq.args if typeof(a) != LineNumberNode)
        for a in geniter
            ptul, ptbr = draw_expr(pt, scalefactor, a)
            pt += (0, ptbr.y - ptul.y)
        end
        ptorg, ptbr
    end
end
draw_expr(pt, eq::Expr) = draw_expr(pt, 3.143, eq)

"""
    draw_single_expr(pt, scalefactor, eq::Expr;
        abovedisplayskip = 6scalefactor, belowdisplayskip = 6scalefactor, indent = EM)
    -> (upper left point, lower right point)

Iterative, place each line in eq on the image below each other.

abovedisplayskip is applied before every equation.
belowdisplayskip is applied after every equation.
indent is offset to right
"""
function draw_single_expr(pt, scalefactor, eq::Expr;
                          abovedisplayskip = 6scalefactor, belowdisplayskip = 6scalefactor, indent = EM)
    @assert eq.head != :block
    ptorg = pt
    pt+= (0, abovedisplayskip)
    l = guard_latexify(eq)
    ptul, ptbr = place_image(pt + (indent, 0), l; scalefactor, centered = false)
    ptorg, ptbr + (0, belowdisplayskip), scalefactor
end

"""
    guard_latexify(eq::Expr)

If a line contains just a defined function call with literal arguments,
show the. For example, string("Just show this comment") can be useful.
"""
function guard_latexify(eq::Expr)
    if eq.head == :call
        evalit = true
        for a in eq.args
            if typeof(a) <: Symbol
                if !isdefined(@__MODULE__, a)
                    evalit = false
                    break
                end
            end
        end
        if evalit
            latexify(LaTeXString(eval(eq)))
        else
            latexify(LaTeXString(string(eq)))
        end
    else
        # This would be an assignment. Show it!
        latexify(prepare_latexify!(eq))
    end
end


text:
struct EnumerableMapMany{T,SO,CS<:Function,RS<:Function} <: Enumerable
    source::SO
    collectionSelector::CS
    resultSelector::RS
end

Base.eltype(::Type{EnumerableMapMany{T,SO,CS,RS}}) where {T,SO,CS,RS} = T

# TODO Make sure this is actually correct. We might have to be more selective,
# i.e. only scan arguments for certain types of expression etc.
function expr_contains_ref_to(expr::Expr, var_name::Symbol)
    for sub_expr in expr.args
        if isa(sub_expr, Symbol)
            if sub_expr==var_name
                return true
            end
        else
            test_sub = expr_contains_ref_to(sub_expr, var_name)
            if test_sub
                return true
            end
        end
    end
    return false
end

function expr_contains_ref_to(expr::Symbol, var_name::Symbol)
    return expr==var_name
end

function expr_contains_ref_to(expr::QuoteNode, var_name::Symbol)
    return expr==var_name
end

function expr_contains_ref_to(expr::Function, var_name::Symbol)
    return false
end

expr_contains_ref_to(::Number, ::Symbol) = false

function mapmany(source::Enumerable, f_collectionSelector::Function, collectionSelector::Expr, f_resultSelector::Function, resultSelector::Expr)
    TS = eltype(source)
    # First detect whether the collectionSelector return value depends at all
    # on the value of the anonymous function argument
    anon_var = collectionSelector.head==:escape ? collectionSelector.args[1].args[1] : collectionSelector.args[1]
    body = collectionSelector.head==:escape ? collectionSelector.args[1].args[2].args[2] : collectionSelector.args[2].args[2]
    crossJoin = !expr_contains_ref_to(body, anon_var)

    if crossJoin
        inner_collection = f_collectionSelector(nothing)
        input_type_collection_selector = typeof(inner_collection)
        TCE = input_type_collection_selector.parameters[1]
    else
        input_type_collection_selector = Base._return_type(f_collectionSelector, Tuple{TS,})
        TCE = typeof(input_type_collection_selector)==Union || input_type_collection_selector==Any ? Any : eltype(input_type_collection_selector)
    end

    T = Base._return_type(f_resultSelector, Tuple{TS,TCE})
    SO = typeof(source)

    CS = typeof(f_collectionSelector)
    RS = typeof(f_resultSelector)

    return EnumerableMapMany{T,SO,CS,RS}(source,f_collectionSelector,f_resultSelector)
end

# TODO This should be changed to a lazy implementation
function Base.iterate(iter::EnumerableMapMany{T,SO,CS,RS}) where {T,SO,CS,RS}
    results = Array{T}(undef, 0)
    for i in iter.source
        for j in iter.collectionSelector(i)
            push!(results,iter.resultSelector(i,j))
        end
    end

    if length(results)==0
        return nothing
    end

    return results[1], (results,2)
end

function Base.iterate(iter::EnumerableMapMany{T,SO,CS,RS}, state) where {T,SO,CS,RS}
    if state[2]>length(state[1])
        return nothing
    else
        return state[1][state[2]], (state[1], state[2]+1)
    end
end


text:
module Day6

using Advent: save_input, eachlinegroup

# use codeunits. and BitSet for a slight performance gain over Vector{Char} and Set

function solve_a(filename)
    sum(eachlinegroup(filename)) do group
        codeunits.(group) |> Iterators.flatten |> BitSet |> length
    end
end

function solve_b(filename)
    sum(eachlinegroup(filename)) do group
        reduce(intersect, BitSet.(codeunits.(group))) |> length
    end
end

solve(filename = save_input(2020, 6)) = solve_a(filename), solve_b(filename)

end

max_stars_repo_name: AnAverageHuman/competitive

max_issues_repo_name: AnAverageHuman/competitive

max_forks_repo_name: AnAverageHuman/competitive


text:
__precompile__()

module ConicBenchmarkUtilities

using GZip

export readcbfdata, cbftompb, mpbtocbf, writecbfdata
export remove_zero_varcones, socrotated_to_soc, remove_ints_in_nonlinear_cones, dualize

include("cbf_input.jl")
include("cbf_output.jl")
include("mpb.jl")
include("preprocess_mpb.jl")
include("convex_to_cbf.jl")
include("jump_to_cbf.jl")

end # module


text:
@testset "Language" begin
    @testset "Case-insensitivity" begin
        @testset "Puzzle is pussgruka; User guesses PUSSGURKA; Guess is correct" begin
            # Arrange
            dictionary = SwedishDictionary(["pussgurka"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("PUSSGRUKA"))

            # Act
            response = gamecommand(game, user, Guess("PUSSGURKA"))

            # Assert
            @test response == Correct("PUSSGURKA")
        end

        @testset "Puzzle is PUSSGRUKA; User guesses pussgurka; Guess is correct" begin
            # Arrange
            dictionary = SwedishDictionary(["PUSSGURKA"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("PUSSGRUKA"))

            # Act
            response = gamecommand(game, user, Guess("pussgurka"))

            # Assert
            @test response == Correct("pussgurka")
        end
    end

    @testset "Diacritics" begin
        @testset "Swedish Å" begin
            # Arrange
            dictionary = SwedishDictionary(["Å", "ÄR", "Ö"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("Å"))

            # Act
            response = gamecommand(game, user, Guess("Å"))

            # Assert
            @test response == Correct("Å")
        end

        @testset "Swedish Å doesn't match A" begin
            # Arrange
            dictionary = SwedishDictionary(["Å", "ÄR", "Ö"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("Å"))

            # Act
            response = gamecommand(game, user, Guess("A"))

            # Assert
            @test response isa Incorrect
            @test response.word == Word("A")
        end

        @testset "Swedish ÄR" begin
            # Arrange
            dictionary = SwedishDictionary(["Å", "ÄR", "Ö"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("ÄR"))

            # Act
            response = gamecommand(game, user, Guess("ÄR"))

            # Assert
            @test response == Correct("ÄR")
        end

        @testset "Swedish ÄR doesn't match AR" begin
            # Arrange
            dictionary = SwedishDictionary(["Å", "ÄR", "Ö"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("ÄR"))

            # Act
            response = gamecommand(game, user, Guess("AR"))

            # Assert
            @test response isa Incorrect
            @test response.word == Word("AR")
        end

        @testset "Swedish Ö" begin
            # Arrange
            dictionary = SwedishDictionary(["Å", "ÄR", "Ö"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("Ö"))

            # Act
            response = gamecommand(game, user, Guess("Ö"))

            # Assert
            @test response == Correct("Ö")
        end

        @testset "Swedish Ö doesn't match O" begin
            # Arrange
            dictionary = SwedishDictionary(["Å", "ÄR", "Ö"])
            game = NiancatGame(dictionary, TestingGameService())
            team = Team(1, "defaultteam", "")
            user = User(1, "name", "displayname", team)
            gamecommand(game, user, SetPuzzle("Ö"))

            # Act
            response = gamecommand(game, user, Guess("O"))

            # Assert
            @test response isa Incorrect
            @test response.word == Word("O")
        end

    end

    @testset "Non-alphabetic characters" begin
        withnonalpha = ["DATOR-SPEL", "DATOR SPEL", "DATOR_SPEL", "DATORSPEL:"]
        for word in withnonalpha
            @testset "Word $word matches puzzle DATORSPLE" begin
                # Arrange
                dictionary = SwedishDictionary(["DATORSPEL"])
                game = NiancatGame(dictionary, TestingGameService())
                team = Team(1, "defaultteam", "")
                user = User(1, "name", "displayname", team)
                gamecommand(game, user, SetPuzzle("DATORSPLE"))

                # Act
                response = gamecommand(game, user, Guess(word))

                # Assert
                @test response == Correct(word)
            end
        end
    end
end


text:
using Test
using ThreeBodyDecay
using Statistics

ms = ThreeBodyMasses(1.1,1.2,1.3; m0=4.2)

let
    σs = randomPoint(ms)
    #
    x,y = squaredalitz1(σs,ms)
    @test -1 ≤ x ≤ 1
    @test -1 ≤ y ≤ 1
    x,y = squaredalitz2(σs,ms)
    @test -1 ≤ x ≤ 1
    @test -1 ≤ y ≤ 1
    x,y = squaredalitz3(σs,ms)
    @test -1 ≤ x ≤ 1
    @test -1 ≤ y ≤ 1
end

let
    x, y = rand(2)
    #
    σs = invsquaredalitz1(x,y,ms)
    @test prod((x,y) .≈ squaredalitz1(σs,ms))
    #
    σs = invsquaredalitz2(x,y,ms)
    @test prod((x,y) .≈ squaredalitz2(σs,ms))
    #
    σs = invsquaredalitz3(x,y,ms)
    @test prod((x,y) .≈ squaredalitz3(σs,ms))
end

let Nd = 100_000, Nb = 10
    ms = ThreeBodyMasses(1.1,1.2,1.3; m0=4.2)

    xv, yv = rand(Nd), rand(Nd)

    σsv = [invsquaredalitz1(x,y,ms) for (x,y) in zip(xv, yv)]
    ws = [jacobean_squaredalitz1(σs, ms) for σs in σsv]

    # this should be consistent with flat
    # histogram2d(getproperty.(σsv,:σ1), getproperty.(σsv,:σ3), weights=ws, bins=30)

    # test in the grid: count cells that are entirely inside of Dalitz
    σ1v = range(lims1(ms)..., length=Nb)
    σ3v = range(lims1(ms)..., length=Nb)
    samples = [sum(x->(r1[1]<x[1].σ1<r1[2]&&r3[1]<x[1].σ3<r3[2])*x[2], zip(σsv,ws))
        for (r1,r3) in Iterators.product(zip(σ1v[1:end-1], σ1v[2:end]), zip(σ3v[1:end-1], σ3v[2:end]))
            if (Kibble(Invariants(ms; σ1=r1[1], σ3=r3[1]), ms^2)<0)*
               (Kibble(Invariants(ms; σ1=r1[2], σ3=r3[1]), ms^2)<0)*
               (Kibble(Invariants(ms; σ1=r1[2], σ3=r3[2]), ms^2)<0)*
               (Kibble(Invariants(ms; σ1=r1[1], σ3=r3[2]), ms^2)<0)]
    nev = vcat(samples...)
    @test mean(nev) > 10*sqrt(cov(nev))
end

max_stars_repo_name: mmikhasenko/ThreeBodyDecay

max_issues_repo_name: mmikhasenko/ThreeBodyDecay

max_forks_repo_name: mmikhasenko/ThreeBodyDecay


text:
using LBFGSB

# code adapted from https://github.com/Gnimuc/LBFGSB.jl/blob/master/src/wrapper.jl

struct L_BFGS_B
    nmax::Int
    mmax::Int
    task::Vector{UInt8}
    csave::Vector{UInt8}
    lsave::Vector{Cint}
    isave::Vector{Cint}
    dsave::Vector{Cdouble}
    wa::Vector{Cdouble}
    iwa::Vector{Cint}
    g::Vector{Cdouble}
    nbd::Vector{Cint}
    l::Vector{Cdouble}
    u::Vector{Cdouble}
    function L_BFGS_B(nmax, mmax)
        task = fill(Cuchar(' '), 60)
        csave = fill(Cuchar(' '), 60)
        lsave = zeros(Cint, 4)
        isave = zeros(Cint, 44)
        dsave = zeros(Cdouble, 29)
        wa = zeros(Cdouble, 2mmax*nmax + 5nmax + 11mmax*mmax + 8mmax)
        iwa = zeros(Cint, 3*nmax)
        g = zeros(Cdouble, nmax)
        nbd = zeros(Cint, nmax)
        l = zeros(Cdouble, nmax)
        u = zeros(Cdouble, nmax)
        new(nmax, mmax, task, csave, lsave, isave, dsave, wa, iwa, g, nbd, l, u)
    end
end

function (obj::L_BFGS_B)(func, grad!, x0::AbstractVector, bounds::AbstractMatrix;
    m=10, ftol=1e-5, pgtol=1e-5, iprint=-1, maxfun=15000, maxiter=15000, store_trace = true)
    x = copy(x0)
    n = length(x)
    #f = func(x0)
    f = 0.0
    factr = ftol/(2.0^(-52))
    # clean up
    fill!(obj.task, Cuchar(' '))
    fill!(obj.csave, Cuchar(' '))
    fill!(obj.lsave, zero(Cint))
    fill!(obj.isave, zero(Cint))
    fill!(obj.dsave, zero(Cdouble))
    fill!(obj.wa, zero(Cdouble))
    fill!(obj.iwa, zero(Cint))
    fill!(obj.g, zero(Cdouble))
    fill!(obj.nbd, zero(Cint))
    fill!(obj.l, zero(Cdouble))
    fill!(obj.u, zero(Cdouble))
    # set bounds
    for i = 1:n
        obj.nbd[i] = bounds[1,i]
        obj.l[i] = bounds[2,i]
        obj.u[i] = bounds[3,i]
    end
    # start
    obj.task[1:5] = b"START"
    if store_trace
        objectives = Array{Float64,1}([f])
    end
    while true
        setulb(Ref{Cint}(n), Ref{Cint}(m), x, obj.l, obj.u, obj.nbd, Ref{Cdouble}(f), obj.g, Ref{Cdouble}(factr), Ref{Cdouble}(pgtol), obj.wa, obj.iwa, obj.task, Ref{Cint}(iprint), obj.csave, obj.lsave, obj.isave, obj.dsave)
        if obj.task[1:2] == b"FG"
            f = func(x)
            grad!(obj.g, x)
        elseif obj.task[1:5] == b"NEW_X"
            if store_trace
                append!(objectives, f)
            end
            if obj.isave[30] ≥ maxiter
                obj.task[1:43] = b"STOP: TOTAL NO. of ITERATIONS REACHED LIMIT"
            elseif obj.isave[34] ≥ maxfun
                obj.task[1:52] = b"STOP: TOTAL NO. of f AND g EVALUATIONS EXCEEDS LIMIT"
            end
        else
            if store_trace
                return f, x, objectives
            else
                return f,x
            end
        end
    end
end

function (obj::L_BFGS_B)(func, x0::AbstractVector, bounds::AbstractMatrix;
    m=10, ftol=1e-5, pgtol=1e-5, iprint=-1, maxfun=15000, maxiter=15000, profiling = true)
    x = copy(x0)
    n = length(x)
    #f = func(x0)
    f = 0.0
    factr = ftol/(2.0^(-52))
    # clean up
    fill!(obj.task, Cuchar(' '))
    fill!(obj.csave, Cuchar(' '))
    fill!(obj.lsave, zero(Cint))
    fill!(obj.isave, zero(Cint))
    fill!(obj.dsave, zero(Cdouble))
    fill!(obj.wa, zero(Cdouble))
    fill!(obj.iwa, zero(Cint))
    fill!(obj.g, zero(Cdouble))
    fill!(obj.nbd, zero(Cint))
    fill!(obj.l, zero(Cdouble))
    fill!(obj.u, zero(Cdouble))
    # set bounds
    for i = 1:n
        obj.nbd[i] = bounds[1,i]
        obj.l[i] = bounds[2,i]
        obj.u[i] = bounds[3,i]
    end
    # start
    obj.task[1:5] = b"START"
    if profiling
        iter::Int64 = 0;
        f_evals = zeros(Int64, maxiter)
        objectives = zeros(Float64, maxiter+1)
        start_flag = true
    end
    while true
        setulb(Ref{Cint}(n), Ref{Cint}(m), x, obj.l, obj.u, obj.nbd, Ref{Cdouble}(f), obj.g, Ref{Cdouble}(factr), Ref{Cdouble}(pgtol), obj.wa, obj.iwa, obj.task, Ref{Cint}(iprint), obj.csave, obj.lsave, obj.isave, obj.dsave)
        if obj.task[1:2] == b"FG"
            f = func(obj.g,x)
            if profiling
                if start_flag
                    iter = 1;
                    objectives[1] = f;
                    f_evals[iter] += 1;
                    start_flag = false;
                else
                    f_evals[iter] += 1;
                end
            end
        elseif obj.task[1:5] == b"NEW_X"
            if profiling
                iter += 1
                objectives[iter] = f;
            end
            if obj.isave[30] ≥ maxiter
                obj.task[1:43] = b"STOP: TOTAL NO. of ITERATIONS REACHED LIMIT"
            elseif obj.isave[34] ≥ maxfun
                obj.task[1:52] = b"STOP: TOTAL NO. of f AND g EVALUATIONS EXCEEDS LIMIT"
            end
        else
            if profiling
                return f,x,objectives[1:iter],f_evals[1:iter-1],iter-1
            else
                return f,x
            end
        end
    end
end

function typ_bnd(lb,ub)
        lb > ub && error("Inconsistent bounds")

        lbinf = lb==-Inf
        ubinf = ub==+Inf

        lbinf && ubinf && return 0
        !lbinf && ubinf && return 1
        !lbinf && !ubinf && return 2
        lbinf && !ubinf && return 3
end

function _opt_bounds(n,m,lb,ub)
    optimizer=L_BFGS_B(n,m)
    bounds=zeros(3,n)
    bounds[2,:].=lb
    bounds[3,:].=ub
    bounds[1,:].= typ_bnd.(lb,ub)
    return optimizer,bounds
end

function lbfgsb(f,g!,x0;m=10,lb=[-Inf for i in x0],ub=[Inf for i in x0],kwargs...)
    n=length(x0)
    optimizer,bounds=_opt_bounds(n,m,lb,ub)

    optimizer(f,g!,x0,bounds;m=m,kwargs...)
end

function lbfgsb(f,x0;m=10,lb=[-Inf for i in x0],ub=[Inf for i in x0],kwargs...)
    n=length(x0)
    optimizer,bounds=_opt_bounds(n,m,lb,ub)

    optimizer(f,x0,bounds;m=m,kwargs...)
end

# f(x) = (1.0 - x[1])^2 + 100.0 * (x[2] - x[1]^2)^2
# function g!(G, x)
# G[1] = -2.0 * (1.0 - x[1]) - 400.0 * (x[2] - x[1]^2) * x[1]
# G[2] = 200.0 * (x[2] - x[1]^2)
# end

# println(lbfgsb(f,g!,[0.,0.];lb=[-Inf,-Inf],ub=[0.5,1],store_trace=true,ftol=0.2))


text:
using OrdinaryDiffEq, DiffEqSensitivity, DiffEqFlux, Flux
using ComponentArrays, LinearAlgebra, GalacticOptim, Test

const nknots = 10
const h = 1.0/(nknots+1)
x = range(0, step=h, length=nknots)
u0 = sin.(π*x)

@inline function f(du,u,p,t)
  du .= zero(eltype(u))
  u₃ = @view u[3:end]
  u₂ = @view u[2:end-1]
  u₁ = @view u[1:end-2]
  @. du[2:end-1] = p.k*((u₃ - 2*u₂ + u₁)/(h^2.0))
  nothing
end

p_true = ComponentArray(k=0.42)
jac_proto = Tridiagonal(similar(u0,nknots-1), similar(u0), similar(u0, nknots-1))
prob = ODEProblem(ODEFunction(f,jac_prototype=jac_proto), u0, (0.0,1.0), p_true)
@time sol_true = solve(prob, Rodas4P(), saveat=0.1)

function loss(prob0, p)
  prob = remake(prob0, p=p)
  sol = solve(prob, Rodas4P(autodiff=false), saveat=0.1, sensealg=ForwardDiffSensitivity())
  sum((sol .- sol_true).^2)
end

p0 = ComponentArray(k=1.0)
res = DiffEqFlux.sciml_train(p -> loss(prob,p), p0, ADAM(0.01), GalacticOptim.AutoZygote(), maxiters=100)
@test res.u.k ≈ 0.42461977305259074 rtol=1e-1


text:
"""
    supported_constraints(con_types::Vector{Tuple{Type, Type}})

Returns `true` if `Function-in-Set` is supported for Dualization and throws an error if it is not.
"""
function supported_constraints(con_types::Vector{Tuple{Type,Type}})
    for (F, S) in con_types
        if !supported_constraint(F, S)
            error(
                "Constraints of the Function ",
                F,
                " in the Set ",
                S,
                " are not implemented",
            )
        end
    end
    return
end

supported_constraint(::Type, ::Type) = false
function supported_constraint(
    ::Type{<:Union{MOI.VariableIndex,MOI.ScalarAffineFunction}},
    S::Type{<:MOI.AbstractScalarSet},
)
    return _dual_set_type(S) !== nothing
end
function supported_constraint(
    ::Type{<:Union{MOI.VectorOfVariables,MOI.VectorAffineFunction}},
    S::Type{<:MOI.AbstractVectorSet},
)
    return _dual_set_type(S) !== nothing
end

"""
    supported_objective(primal_model::MOI.ModelLike)

Returns `true` if `MOI.ObjectiveFunctionType()` is supported for Dualization and throws an error if it is not.
"""
function supported_objective(primal_model::MOI.ModelLike)
    obj_func_type = MOI.get(primal_model, MOI.ObjectiveFunctionType())
    if !supported_obj(obj_func_type)
        error("Objective functions of type $obj_func_type are not implemented")
    end
    return
end

# General case
supported_obj(::Type) = false
# List of supported objective functions
supported_obj(::Type{VI}) = true
supported_obj(::Type{<:SAF}) = true
supported_obj(::Type{<:SQF}) = true