@testset "Testing RedBlack Tree" begin
    @test reverse(ReuseDistance.Left) == ReuseDistance.Right
    @test reverse(ReuseDistance.Right) == ReuseDistance.Left

    @testset "Testing Accessors" begin
        # Initialize the tree with length zero.
        # This will test the corner case handling of setting the minimum node vector
        # length to 1 AND test the length extension logic.
        tree = ReuseDistance.RedBlackTree{Int}(0)

        # Get a new index from the tree
        n = tree[]
        @test n == 1
        tree[n] = 500
        @test tree[n] == 500

        # Null all other pointer fields
        ReuseDistance.setcolor!(tree, n, ReuseDistance.Black)
        ReuseDistance.setparent!(tree, n, 0)
        ReuseDistance.setleft!(tree, n, 0)
        ReuseDistance.setright!(tree, n, 0)

        @test ReuseDistance.getcolor(tree, n) == ReuseDistance.Black
        @test ReuseDistance.getparent(tree, n) == 0
        @test ReuseDistance.leftchild(tree, n) == 0
        @test ReuseDistance.rightchild(tree, n) == 0

        # Setting the color to `Red` should not affect the parent
        ReuseDistance.setcolor!(tree, n, ReuseDistance.Red)
        @test ReuseDistance.getcolor(tree, n) == ReuseDistance.Red
        @test ReuseDistance.getparent(tree, n) == 0
        @test ReuseDistance.leftchild(tree, n) == 0
        @test ReuseDistance.rightchild(tree, n) == 0

        # Similarly, setting the parent should not affect the color.
        ReuseDistance.setparent!(tree, n, typemax(Int64))
        @test ReuseDistance.getcolor(tree, n) == ReuseDistance.Red
        @test ReuseDistance.getparent(tree, n) == typemax(Int64)
        @test ReuseDistance.leftchild(tree, n) == 0
        @test ReuseDistance.rightchild(tree, n) == 0

        ReuseDistance.setleft!(tree, n, 10)
        ReuseDistance.setright!(tree, n, 33)
        @test ReuseDistance.getcolor(tree, n) == ReuseDistance.Red
        @test ReuseDistance.getparent(tree, n) == typemax(Int64)
        @test ReuseDistance.leftchild(tree, n) == 10
        @test ReuseDistance.rightchild(tree, n) == 33

        # Test misc accessors
        @test ReuseDistance.child_direction(tree, n, 10) == ReuseDistance.Left
        @test ReuseDistance.child_direction(tree, n, 33) == ReuseDistance.Right
        @test ReuseDistance.getparent(tree, n) == typemax(Int64)
        m = tree[]
        @test m == 2
        ReuseDistance.setparent!(tree, n, 0)
        ReuseDistance.setparent!(tree, m, n)
        ReuseDistance.setleft!(tree, n, m)
        @test ReuseDistance.child_direction(tree, m) == ReuseDistance.Left

        ReuseDistance.setleft!(tree, n, 0)
        ReuseDistance.setright!(tree, n, m)
        @test ReuseDistance.child_direction(tree, m) == ReuseDistance.Right
        @test ReuseDistance.getparent(tree, m) == n
    end

    @testset "Testing Tree Operations" begin
        function clear!(tree, n)
            ReuseDistance.setcolor!(tree, n, ReuseDistance.Black)
            ReuseDistance.setparent!(tree, n, 0)
            ReuseDistance.setleft!(tree, n, 0)
            ReuseDistance.setright!(tree, n, 0)
            tree[n] = 0
            return n
        end

        # Now, we get a bit more elaborate with our tree setup, testing out tree rotations
        # and the accessors for uncles, nephews etc.
        #
        # Out tree will look like this.
        #
        #         A
        #        / \
        #       B   C
        #      / \   \
        #     D   E   F
        function setup_tree()
            tree = ReuseDistance.RedBlackTree{Int}(10)
            a = clear!(tree, tree[])
            b = clear!(tree, tree[])
            c = clear!(tree, tree[])
            d = clear!(tree, tree[])
            e = clear!(tree, tree[])
            f = clear!(tree, tree[])

            tree.root = 1
            ReuseDistance.setleft!(tree, a, b)
            ReuseDistance.setparent!(tree, b, a)

            ReuseDistance.setright!(tree, a, c)
            ReuseDistance.setparent!(tree, c, a)

            ReuseDistance.setright!(tree, c, f)
            ReuseDistance.setparent!(tree, f, c)

            ReuseDistance.setleft!(tree, b, d)
            ReuseDistance.setparent!(tree, d, b)
            ReuseDistance.setright!(tree, b, e)
            ReuseDistance.setparent!(tree, e, b)
            return tree, (; a, b, c, d, e, f)
        end

        #####
        ##### Tree Rotations
        #####

        # After a right rotation on A
        #
        #         B
        #        / \
        #       D   A
        #          / \
        #         E   C
        #              \
        #               F

        tree, nodes = setup_tree()
        ReuseDistance.rotateright!(tree, nodes.a)
        @test tree.root == nodes.b
        @test ReuseDistance.getparent(tree, nodes.b) == 0

        @test ReuseDistance.leftchild(tree, nodes.b) == nodes.d
        @test ReuseDistance.getparent(tree, nodes.d) == nodes.b
        @test ReuseDistance.rightchild(tree, nodes.b) == nodes.a
        @test ReuseDistance.getparent(tree, nodes.a) == nodes.b

        @test ReuseDistance.leftchild(tree, nodes.a) == nodes.e
        @test ReuseDistance.getparent(tree, nodes.e) == nodes.a

        @test ReuseDistance.rightchild(tree, nodes.a) == nodes.c
        @test ReuseDistance.getparent(tree, nodes.c) == nodes.a

        # After a left rotation on A
        #           C
        #          / \
        #         A   F
        #        /
        #       B
        #      / \
        #     D   E
        tree, nodes = setup_tree()
        ReuseDistance.rotateleft!(tree, nodes.a)
        @test tree.root == nodes.c
        @test ReuseDistance.getparent(tree, nodes.c) == 0
        @test ReuseDistance.rightchild(tree, nodes.c) == nodes.f
        @test ReuseDistance.getparent(tree, nodes.f) == nodes.c

        @test ReuseDistance.leftchild(tree, nodes.c) == nodes.a
        @test ReuseDistance.getparent(tree, nodes.a) == nodes.c

        @test ReuseDistance.rightchild(tree, nodes.a) == 0
        @test ReuseDistance.leftchild(tree, nodes.a) == nodes.b
        @test ReuseDistance.getparent(tree, nodes.b) == nodes.a

        # Finally, perform a rotation that doesn't change the root
        # Right rotation on `B`.
        #
        #
        #         A
        #        / \
        #       D   C
        #        \   \
        #         B   F
        #          \
        #           E
        tree, nodes = setup_tree()
        ReuseDistance.rotateright!(tree, nodes.b)
        @test tree.root == nodes.a
        @test ReuseDistance.leftchild(tree, nodes.a) == nodes.d
        @test ReuseDistance.getparent(tree, nodes.d) == nodes.a
        @test ReuseDistance.leftchild(tree, nodes.d) == 0
        @test ReuseDistance.rightchild(tree, nodes.d) == nodes.b
        @test ReuseDistance.getparent(tree, nodes.b) == nodes.d

        #####
        ##### Search
        #####

        tree, nodes = setup_tree()
        tree[nodes.a] = 20
        tree[nodes.b] = 10
        tree[nodes.c] = 30
        tree[nodes.d] = 5
        tree[nodes.e] = 15
        tree[nodes.f] = 35

        @test ReuseDistance.search(tree, 20) == (nodes.a, ReuseDistance.Found)
        @test ReuseDistance.search(tree, 10) == (nodes.b, ReuseDistance.Found)
        @test ReuseDistance.search(tree, 30) == (nodes.c, ReuseDistance.Found)
        @test ReuseDistance.search(tree, 5) == (nodes.d, ReuseDistance.Found)
        @test ReuseDistance.search(tree, 15) == (nodes.e, ReuseDistance.Found)
        @test ReuseDistance.search(tree, 35) == (nodes.f, ReuseDistance.Found)

        # Look for findings
        @test ReuseDistance.search(tree, 25) == (nodes.c, ReuseDistance.NotFoundLeft)
        @test ReuseDistance.search(tree, 16) == (nodes.e, ReuseDistance.NotFoundRight)
    end

    #####
    ##### Insertion!
    #####

    @testset "Testing Insertion" begin
        tree = ReuseDistance.RedBlackTree{Int}()
        for i in 1:100
            push!(tree, i)
        end

        @test ReuseDistance.validate(tree)
        @test length(tree) == 100
        @test all(in(tree), 1:100)
        @test !any(in(tree), 101:200)
    end

    #####
    ##### Stress Testing
    #####

    @testset "Performing Stress Test" begin
        groundtruth = Set{Int}()
        tree = ReuseDistance.RedBlackTree{Int}()
        domain = 1:10000
        numswaps = 1000
        increment = 0.02

        swapcount = 0
        add = true
        meter = ProgressMeter.Progress(numswaps, 1)
        while swapcount <= numswaps
            swapchance = 0.0
            while rand() > swapchance
                numops = rand(1:10)
                for _ in Base.OneTo(numops)
                    if add
                        i = rand(domain)
                        while length(groundtruth) < length(domain) && in(i, groundtruth)
                            i = rand(domain)
                        end
                        push!(tree, i)
                        push!(groundtruth, i)
                    elseif !isempty(groundtruth)
                        i = rand(groundtruth)
                        delete!(tree, i)
                        delete!(groundtruth, i)
                    end
                end
                swapchance += increment
            end

            passed = true
            for i in domain
                if in(i, groundtruth)
                    passed &= in(i, tree)
                else
                    passed &= !in(i, tree)
                end
            end

            @test passed
            @test ReuseDistance.validate(tree)
            swapcount += 1
            add = !add
            ProgressMeter.next!(meter)
        end
    end
end


#
# Copyright (c) 2021 Tobias Thummerer, Lars Mikelsons, Josef Kircher
# Licensed under the MIT license. See LICENSE file in the project root for details.
#

using FMI: fmi2VariableNamingConventionStructured, fmi2DependencyKindDependent, fmi2DependencyKindFixed

myFMU = fmiLoad("SpringDFrictionPendulum1D", ENV["EXPORTINGTOOL"], ENV["EXPORTINGVERSION"])

@test fmiGetVersion(myFMU) == "2.0"
@test fmiGetTypesPlatform(myFMU) == "default"

@test fmiGetModelName(myFMU) == "SpringFrictionPendulum1D"
@test fmiGetVariableNamingConvention(myFMU) == fmi2VariableNamingConventionStructured
@test fmiIsCoSimulation(myFMU) == true
@test fmiIsModelExchange(myFMU) == true

@test fmiGetGUID(myFMU) == "{df491d8d-0598-4495-913e-5b025e54d7f2}"
@test fmiGetGenerationTool(myFMU) == "Dymola Version 2022x (64-bit), 2021-10-08"
@test fmiGetGenerationDateAndTime(myFMU) == "2022-03-03T15:09:18Z"
@test fmiGetNumberOfEventIndicators(myFMU) == 24
@test fmiCanGetSetState(myFMU) == true
@test fmiCanSerializeFMUstate(myFMU) == true
@test fmiProvidesDirectionalDerivative(myFMU) == true

depMtx = fmi2GetDependencies(myFMU)
@test fmi2DependencyKindFixed in depMtx
@test fmi2DependencyKindDependent in depMtx

@test fmi2GetDefaultStartTime(myFMU.modelDescription) ≈ 0.0
@test fmi2GetDefaultStopTime(myFMU.modelDescription) ≈ 1.0
@test fmi2GetDefaultTolerance(myFMU.modelDescription) ≈ 1e-4
@test fmi2GetDefaultStepSize(myFMU.modelDescription) === nothing

fmiUnload(myFMU)


using Test
using CUDA
using ClimaCore.DataLayouts

function knl_copy!(dst, src)
    i = threadIdx().x
    j = threadIdx().y

    h = blockIdx().x

    p_dst = slab(dst, h)
    p_src = slab(src, h)

    @inbounds p_dst[i, j] = p_src[i, j]
    return nothing
end

function test_copy!(dst, src)
    @cuda threads = (4, 4) blocks = (10,) knl_copy!(dst, src)
end

@testset "data in GPU kernels" begin

    S = Tuple{Complex{Float64}, Float64}
    src = IJFH{S, 4}(CuArray(rand(4, 4, 3, 10)))
    dst = IJFH{S, 4}(CuArray(zeros(4, 4, 3, 10)))

    test_copy!(dst, src)

    @test getfield(dst, :array) == getfield(src, :array)
end

@testset "broadcasting" begin
    FT = Float64
    S1 = NamedTuple{(:a, :b), Tuple{Complex{Float64}, Float64}}
    data1 = CuArray(ones(FT, 2, 2, 3, 2))
    S2 = Float64
    data2 = CuArray(ones(FT, 2, 2, 1, 2))
    data1 = IJFH{S1, 2}(data1)
    data2 = IJFH{S2, 2}(data2)

    f(a1, a2) = a1.a.re * a2 + a1.b
    res = f.(data1, data2)
    @test res isa IJFH{Float64}
    @test Array(parent(res)) == FT[2 for i in 1:2, j in 1:2, f in 1:1, h in 1:2]
end

@testset "broadcasting assignment from scalar" begin
    FT = Float64
    S = Complex{FT}
    data = IJFH{S, 2}(CuArray{FT}, 3)
    data .= Complex(1.0, 2.0)
    @test Array(parent(data)) ==
          FT[f == 1 ? 1 : 2 for i in 1:2, j in 1:2, f in 1:2, h in 1:3]
end


using ReinforcementLearning

include("MDPtest.jl")
include("Gymtest.jl")


using AOC2021
using Test

@testset "AOC2021.jl" begin
    # Write your tests here.
    @testset "Test Solutions" begin
        @test solve(TestInput, 1, 1) == 7
        @test solve(TestInput, 1, 2) == 5

        @test solve(TestInput, 2, 1) == 150
        @test solve(TestInput, 2, 2) == 900

        @test solve(TestInput, 3, 1) == 198
        @test solve(TestInput, 3, 2) == 230

        @test solve(TestInput, 4, 1) == 4512
        @test solve(TestInput, 4, 2) == 1924

        @test solve(TestInput, 5, 1) == 5
        @test solve(TestInput, 5, 2) == 12

        @test solve(TestInput, 6, 1; days=18) == 26
        @test solve(TestInput, 6, 1) == 5934

        @test solve(TestInput, 6, 2; days=256) == 26984457539

        @test solve(TestInput, 7, 1) == 37
        @test solve(TestInput, 7, 2) == 168

        @test solve(TestInput, 8, 1) == 26
        @test solve(TestInput, 8, 2) == 61229

        @test solve(TestInput, 9, 1) == 15
        @test solve(TestInput, 9, 2) == 1134

        @test solve(TestInput, 10, 1) == 26397
        @test solve(TestInput, 10, 2) == 288957

        @test solve(TestInput, 11, 1; flashes=10) == 204
        @test solve(TestInput, 11, 1; flashes=100) == 1656

        @test solve(TestInput, 11, 2) == 195

        @test solve(TestInput, 12, 1, 1) == 10
        @test solve(TestInput, 12, 1, 2) == 19
        @test solve(TestInput, 12, 1, 3) == 226

        @test solve(TestInput, 12, 2, 1) == 36
        @test solve(TestInput, 12, 2, 2) == 103
        @test solve(TestInput, 12, 2, 3) == 3509

        @test solve(TestInput, 13,1) == 17

        @test solve(TestInput, 14,1) == 1588

        @test solve(TestInput, 15,1) == 40
        @test solve(TestInput, 15,2) == 315
    end

    @testset "Real Solutions" begin
        @test solve(FullInput, 1, 1) == 1581
        @test solve(FullInput, 1, 2) == 1618
        @test solve(FullInput, 2, 1) == 1746616
        @test solve(FullInput, 2, 2) == 1741971043
        @test solve(FullInput, 3, 1) == 1997414
        @test solve(FullInput, 3, 2) == 1032597
        @test solve(FullInput, 4, 1) == 28082
        @test solve(FullInput, 4, 2) == 8224
        @test solve(FullInput, 5, 1) == 7674
        @test solve(FullInput, 6, 1; days=80) == 380758
        @test solve(FullInput, 6, 2; days=256) == 1710623015163

        @test solve(FullInput, 7, 1) == 339321
        @test solve(FullInput, 7, 2) == 95476244

        @test solve(FullInput, 8, 1) == 476
        @test solve(FullInput, 8, 2) == 1011823

        @test solve(FullInput, 9, 1) == 452
        @test solve(FullInput, 9, 2) == 1263735

        @test solve(FullInput, 10, 1) == 193275
        @test solve(FullInput, 10, 2) == 2429644557

        @test solve(FullInput, 11, 1) == 1546
        @test solve(FullInput, 11, 2) == 471

        @test solve(FullInput, 12, 1) == 4691
        @test solve(FullInput, 12, 2) == 140718

        @test solve(FullInput, 13, 1) == 850

        @test solve(FullInput, 14, 1) == 4517
        @test solve(FullInput, 14, 2) == 4704817645083

        @test solve(FullInput, 15,1) == 685
    end
end


abstract Light <: Compose3DNode

using Colors

export pointlight, spotlight, ambientlight
#Ambient Light is applied globally to all objects.
immutable AmbientLight <: Light
    color::Color
end

ambientlight(color::AbstractString) = ambientlight(parse(Colorant, color))
ambientlight(color::Color=colorant"white") = AmbientLight(color)

resolve(box::Absolute3DBox, light::AmbientLight) = light

#Like a light bulb. Light shines in all directions.
immutable PointLight <: Light
    position::Vec3
    color::Color
    intensity::Float64
    distance::Length
end

PointLight(x::Length, y::Length, z::Length, color::Color, intensity::Float64,
           distance::Length) =
    PointLight((x, y, z), color, intensity, distance)

pointlight(x::Length, y::Length, z::Length, color::Color=colorant"white";
    intensity::Float64=1.0, distance=0mm) =
    PointLight(x, y, z, color, intensity, distance)

pointlight(x::Length, y::Length, z::Length, color::AbstractString;
    intensity::Float64=1.0, distance=0mm) =
    PointLight(x, y, z, parse(Colorant, color),intensity,distance)

function resolve(box::Absolute3DBox, light::PointLight)
    absposition = resolve(box, light.position)
    absdistance = resolve(box, light.distance)
    PointLight(absposition, light.color, light.intensity, absdistance)
end

#PointLight casting shadow in one direction.
immutable SpotLight <: Light
    position::Vec3
    color::Color
    intensity::Float64
    distance::Length
    angle::Float64 #Degrees
    exponent::Float64
    shadow::Bool
end

SpotLight(x::Length, y::Length, z::Length, color::Color, intensity::Float64,
          distance::Length, angle::Float64, exponent::Float64, shadow::Bool) =
    SpotLight((x, y, z), color, intensity, distance, angle, exponent, shadow)

spotlight(x::Length,y::Length,z::Length,color::Color=colorant"white";
          intensity::Float64=1.0, distance=0mm, angle::Float64=60.0,
          exponent::Float64=8.0, shadow::Bool=false) =
    SpotLight(x, y, z, color, intensity, distance, angle, exponent, shadow)

spotlight(x::Length, y::Length, z::Length, color::AbstractString;
    intensity::Float64=1.0, distance=0mm, angle::Float64=60.0,
    exponent::Float64=8.0, shadow::Bool=false) =
    SpotLight(x,y,z,parse(Colorant, color),intensity,distance,angle,exponent,shadow)

function resolve(box::Absolute3DBox, light::SpotLight)
    absposition = resolve(box, light.position)
    absdistance = resolve(box, light.distance)
    SpotLight(
        absposition,
        light.color,
        light.intensity,
        absdistance,
        light.angle,
        light.exponent,
        light.shadow
    )
end


module TestCopy

using Test
using Transducers

@testset "copy [base types]" begin
    @test copy(Map(x -> x^2), Vector, 1:3) == (1:3) .^ 2
    @test copy(Map(x -> x^2), [1:3;]) == (1:3) .^ 2
    @test copy(Map(x -> x^2), Set, 1:3) == Set((1:3) .^ 2)
    @test copy(Map(x -> x => x^2), Dict, 2:2) == Dict(2 => 4)
end

@testset "Set" begin
    @test Set(eduction(y for x in 1:1000 for y in 1:x)) == Set(1:1000)
end

@testset "Dict" begin
    @test Dict(eduction(x => y for x in 1:1000 for y in 1:x)) == Dict(1:1000 .=> 1:1000)
end

end  # module


mutable struct State
  ptype :: Int
  class :: Int
end

# outer constructors

State() = State(1, 1)
State(ptype) = State(ptype, 1)

get_ptype(x :: State) = x.ptype
get_neighbor_class(x :: State) = x.class

transform!(x :: State, l) = (x.ptype = l)
change_neighbor_class!(x :: State, k) = (x.class = k)

function Base.show(io :: IO, x :: State)
  println(io, "  type: ", x.ptype)
  print(io, "  nbclass: ", x.class)
end

struct Site{D,T}
  label :: Int
  state :: State
  coord :: MVector{D,T}

  function Site{D,T}(id :: Integer, state :: State, coord :: NTuple{D,T}) where {D,T}
      new{D,T}(id, state, MVector(coord...))
  end
end

Site(id, state, coord::NTuple{N,T}) where {N,T} = Site{N,T}(id, state, coord)

# accessors #

label(x :: Site) = x.label
state(x :: Site) = x.state
coordinates(x :: Site) = x.coord.data

# for search / sort compatibility
label(x::Int) = x
coordinates(x::NTuple) = x
coordinates(x::AbstractVector) = x

get_ptype(x :: Site) = get_ptype(state(x))
get_neighbor_class(x :: Site) = get_neighbor_class(state(x))

# state #

transform!(x :: Site, l) = transform!(state(x), l)
change_neighbor_class!(x :: Site, k) = change_neighbor_class!(state(x), k)

# iteration #
Base.iterate(x::Site) = iterate(coordinates(x))
Base.iterate(x::Site, state) = iterate(coordinates(x), state)

# indexing #

Base.getindex(x :: Site, i) = getindex(coordinates(x), i)
Base.setindex!(x :: Site, val, i) = setindex!(coordinates(x), val, i)
Base.eachindex(x :: Site) = eachindex(coordinates(x))

# IO #

Base.summary(io::IO, x::Site) = print(io, "Site")

function Base.show(io::IO, x::Site)
  # summary(io, x)
  print(io, coordinates(x))
end

function Base.show(io::IO, m::MIME"text/plain", x::Site)
  lk = string(get_ptype(x), "|", get_neighbor_class(x))
  print(io, lk)
  print(io, " @ ")
  print(io, coordinates(x))
end

##### recipes #####

@recipe function f(::Type{Site{1,T}}, x::Site{1,T}) where T
  [(0, x[1])]
end

@recipe function f(::Type{Site{2,T}}, x::Site{2,T}) where T
  [(x[1], x[2])]
end

@recipe function f(::Type{Site{3,T}}, x::Site{3,T}) where T
  [(x[1], x[2], x[3])]
end

function build_composition!(comp, lattice, x::Site{D}) where D
  fill!(comp, zero(eltype(comp)))
  comp[1] = capacity(topology(lattice), D)

  for id in neighborhood(lattice, x)
    y = get_site(lattice, id)
    l = get_ptype(y)

    comp[1] -= 1
    comp[l] += 1
  end

  return comp
end

##### sorting #####

const XD1 = (1,)

const XD2 = (1, 2)
const YD2 = (2, 1)

const XD3 = (1, 2, 3)
const YD3 = (1, 3, 2)
const ZD3 = (2, 3, 1)

Base.isless(x::Site, y::Site) = isless(label(x), label(y))

Base.:(<)(::Site{D}, ::Tuple{}) where D = false
Base.:(<)(x::Site{D}, y::NTuple{D}) where D = x.coord.data < y

# does not work for missing but should not be an issue
function lex_order(x, y, idx)
  for i in idx
    a, b = x[i], y[i]
    if !isequal(a, b)
      return a < b
    end
  end
  return false
end

lex_order_x1(x, y) = lex_order(x, y, XD1)

lex_order_x2(x, y) = lex_order(x, y, XD2)
lex_order_y2(x, y) = lex_order(x, y, YD2)

lex_order_x3(x, y) = lex_order(x, y, XD3)
lex_order_y3(x, y) = lex_order(x, y, YD3)
lex_order_z3(x, y) = lex_order(x, y, ZD3)

sort_by_x_1D!(sites) = sort!(sites,
  alg = Base.Sort.DEFAULT_STABLE,
  lt = lex_order_x1,
  by = coordinates)

sort_by_x_2D!(sites) = sort!(sites,
  alg = Base.Sort.DEFAULT_STABLE,
  lt = lex_order_x2,
  by = coordinates)

sort_by_y_2D!(sites) = sort!(sites,
  alg = Base.Sort.DEFAULT_STABLE,
  lt = lex_order_y2,
  by = coordinates)

sort_by_x_3D!(sites) = sort!(sites,
  alg = Base.Sort.DEFAULT_STABLE,
  lt = lex_order_x3,
  by = coordinates)

sort_by_y_3D!(sites) = sort!(sites,
  alg = Base.Sort.DEFAULT_STABLE,
  lt = lex_order_y3,
  by = coordinates)

sort_by_z_3D!(sites) = sort!(sites,
  alg = Base.Sort.DEFAULT_STABLE,
  lt = lex_order_z3,
  by = coordinates)


"""
convert to AutomotiveDrivingModels.Scene
"""
function state2scene(mdp::CarMDP, s::CarMDPState, car_type::VehicleDef = mdp.car_type)
    scene = Scene()
    push!(scene, Vehicle(s.ego, mdp.ego_type, EGO_ID))
    push!(scene, Vehicle(s.car, mdp.car_type, EGO_ID+1))
    return scene
end

function animate_states(mdp::CarMDP, states::Vector{CarMDPState}, actions::Vector{CarMDPAction};
                        overlays=SceneOverlay[IDOverlay()],
                        cam=StaticCamera(VecE2(0, -5.), 17.))
    duration = length(states)*mdp.ΔT
    fps = Int(1/mdp.ΔT)
    function render_states(t, dt)
        frame_index = Int(floor(t/dt)) + 1
        scene = state2scene(mdp, states[frame_index])
        return AutoViz.render(scene,
                mdp.env,
              cat(1, overlays,   TextOverlay(text = ["v: $(get_ego(scene).state.v)"],
                                            font_size=20,
                                            pos=VecE2(mdp.env.params.x_min + 3.,6.),
                                            incameraframe=true),
                                 TextOverlay(text = ["Acc: $(actions[frame_index].acc)"],
                                            font_size=20,
                                            pos=VecE2(mdp.env.params.x_min + 3.,8.),
                                            incameraframe=true),
                                TextOverlay(text = ["step: $frame_index"],
                                            font_size=20,
                                            pos=VecE2(mdp.env.params.x_min + 3.,4.),
                                            incameraframe=true)),
                cam=cam,
                car_colors=get_colors(scene))
    end
    return duration, fps, render_states
end


include("AbstractCausalAnalysis.jl")
include("CausalAnalysis.jl")
include("summarise.jl")
include("recipes.jl")

export AbstractCausalAnalysis, CausalAnalysis, causality, summarise


using TrustPilotCalc
using Dates, JuliaDB

# load sample dataset and generate yearmon list
example_data = loadtable("../../TrustPlotCalc.jl-old/data/trustpilot_working.csv"; type_detect_rows = 500)
yearmon_list = unique(map(x-> round(x.review_created_at_utc, Dates.Month), example_data))
sort!(yearmon_list; rev = true)
pop!(yearmon_list) # removing last entry to avoid empty arrays

# task channels
c_output = Channel(length(yearmon_list))

function run_through(base_dataset, max_yearmon)
    while true
        result = playBack(base_dataset; max_yearmon = max_yearmon)
        put!(c_output, result)
    end
end

for i in 1:length(yearmon_list)
    @async run_through(example_data, yearmon_list[i])
end

[x for x in c_output]

# [print(playBack(example_data; max_yearmon = x).score, " | ", x, "\n") for x in yearmon_list]


using DiffEqBase
mutable struct DummySolution
  retcode
end

mutable struct DummyIntegrator{Alg, IIP, U, T} <: DiffEqBase.DEIntegrator{Alg, IIP, U, T}
  uprev::U
  tprev::T
  u::U
  t::T
  dt::T
  tdir
  tstops
  sol::DummySolution

  DummyIntegrator() = new{Bool,Bool,Vector{Float64},Float64}([0.0],0,[0.0],0,1,1,[],DummySolution(:Default))
end

function DiffEqBase.add_tstop!(integrator::DummyIntegrator,t)
  integrator.tdir * (t - integrator.t) < 0 && error("Tried to add a tstop that is behind the current time. This is strictly forbidden")
  push!(integrator.tstops,t)
end

function DiffEqBase.step!(integrator::DummyIntegrator)
  t_next = integrator.t + integrator.dt
  if ! isempty(integrator.tstops) && integrator.tstops[1] < t_next
    t_next = integrator.tstops[1]
  end
  integrator.uprev .= integrator.u
  integrator.u[1] += 2 * (t_next - integrator.t)
  integrator.tprev = integrator.t
  integrator.t = t_next
end

function step_dt!(integrator, args...)
  t = integrator.t
  step!(integrator, args...)
  integrator.t - t
end

function DiffEqBase.done(integrator::DummyIntegrator)
  integrator.t > 10
end

integrator = DummyIntegrator()
@test step_dt!(integrator, 1.5) == 2
@test step_dt!(integrator, 1.5, true) == 1.5
@test_throws ErrorException step!(integrator, -1)

for (u, t) in tuples(DummyIntegrator())
  @test u[1] == 2*t
end
@test eltype(collect(tuples(DummyIntegrator()))) == Tuple{Vector{Float64},Float64}

for (uprev, tprev, u, t) in intervals(DummyIntegrator())
  @test u[1] - uprev[1] == 2
  @test t - tprev == 1
end
@test eltype(collect(intervals(DummyIntegrator()))) == Tuple{Vector{Float64},Float64,Vector{Float64},Float64}


using Test, ExtendableGrids, GridVisualize
import PyPlot

@test true
if !Sys.isapple()
    plotting=joinpath(@__DIR__,"..","examples","plotting.jl")
    include(plotting)
    include("../docs/makeplots.jl")
    @testset "makeplots - PyPlot" begin
        makeplots(mktempdir(),Plotter=PyPlot)
    end
end


include("planet.jl")
include("subgrid_scale.jl")
include("edmf.jl")
include("microphysics.jl")
include("atmos_subgrid_scale.jl")
include("surface_fluxes.jl")
include("override_defaults.jl")


# Convenience function to allow for things like Array(tp) or CuArray(tp)
# Not sure if this counts as type piracy...
(::Type{T})(p::TensorProto) where T = array(p) |> T
(::Type{Ref{T}})(p::TensorProto) where T = array(p) |> T |> Ref

"""
    array(p::TensorProto)

Return `p` as an reshaped and reinterpreted array.
"""
function array(p::TensorProto)
    # Copy pasted from jl
    # Can probably be cleaned up a bit
    # TODO: Add missing datatypes...
    if p.data_type === TensorProto_DataType.INT64
        if isdefined(p, :int64_data) && !isempty(p.int64_data)
            return reshape(reinterpret(Int64, p.int64_data), reverse(p.dims)...)
        end
        return reshape(reinterpret(Int64, p.raw_data), reverse(p.dims)...)
    end

    if p.data_type === TensorProto_DataType.INT32
        if isdefined(p, :int32_data) && !isempty(p.int32_data)
            return reshape(p.int32_data , reverse(p.dims)...)
        end
        return reshape(reinterpret(Int32, p.raw_data), reverse(p.dims)...)
    end

    if p.data_type === TensorProto_DataType.INT8
        return reshape(reinterpret(Int8, p.raw_data), reverse(p.dims)...)
    end

    if p.data_type === TensorProto_DataType.DOUBLE
        if isdefined(p, :double_data) && !isempty(p.double_data)
            return reshape(p.double_data , reverse(p.dims)...)
        end
        return reshape(reinterpret(Float64, p.raw_data), reverse(p.dims)...)
    end

    if p.data_type === TensorProto_DataType.FLOAT
        if isdefined(p,:float_data) && !isempty(p.float_data)
            return reshape(reinterpret(Float32, p.float_data), reverse(p.dims)...)
        end
        return reshape(reinterpret(Float32, p.raw_data), reverse(p.dims)...)
    end

    if p.data_type === TensorProto_DataType.FLOAT16
        return reshape(reinterpret(Float16, p.raw_data), reverse(p.dims)...)
    end
end

Base.size(vip::ValueInfoProto) = size(vip._type)
Base.size(tp::TypeProto) = size(tp.tensor_type)
Base.size(tp_t::TypeProto_Tensor) = size(tp_t.shape)
Base.size(tsp::TensorShapeProto) = size.(Tuple(reverse(tsp.dim)))
Base.size(tsp_d::TensorShapeProto_Dimension) = isdefined(tsp_d, :dim_value) ? tsp_d.dim_value : missing

"""
    attribute(p::AttributeProto)

Return attribute in `p` as a name => value pair.
"""
function attribute(p::AttributeProto)
    # Copy paste from ONNX.jl
    if (p._type != 0)
        field = [:f, :i, :s, :t, :g, :floats, :ints, :strings, :tensors, :graphs][p._type]
        if field === :s
            return Symbol(p.name) => String(getfield(p, field))
        elseif  field === :strings
            return Symbol(p.name) => String.(getfield(p, field))
        end
        return Symbol(p.name) => getfield(p, field)
    end
end

Base.Dict(pa::AbstractVector{AttributeProto}) = Dict(attribute(p) for p in pa)


include(joinpath("..", "src_PolynomialOptim", "PolynomialOptim.jl"))
using LightXML, MAT

import Base.copy

"""
    Link(orig, dest)

Represents a link from a bus origin `orig` to a bus destination `dest`
Util for stock or treat data about transmission lines or transformers
### Examples
```
julia > Link("BUS_1","BUS_4") #represents a link between BUS_1 and BUS_4
```
"""
struct Link
  orig::String
  dest::String
end

"""
    Abstract type AbstractNodeLabel

Abstract type for any element about a node
### Examples
```julia
struct MatpowerLoad <: AbstractNodeLabel
struct GOCVolt <: AbstractNodeLabel
struct IIDMGenerator <: AbstractNodeLabel
struct GOCShunt <: AbstractNodeLabel

```
"""
abstract type AbstractNodeLabel end

"""
    abstract type AbstractLinkLabel

Abstract type for any element about a link
### Examples
```
struct MatpowerLine_π <: AbstractLinkLabel
struct GOCLineπ_withtransformer <: AbstractLinkLabel
struct GOCNullImpedance_notransformer <: AbstractLinkLabel
```
"""
abstract type AbstractLinkLabel end

"""
    abstract type AbstractInput
Abstract type for any entry format
### Examples
```
type MatpowerInput <: AbstractInput end
type GOCInput <:AbstractInput end
type IIDMInput <: AbstractInput end
```
"""
abstract type AbstractInput end

"""
    DataSource(bus::Dict{String, Dict{String, Any}}, link::Dict{Link, Dict{String, Any}})

Store network data either in an attribute `bus` or in an attribute `link` depending on data type.

### Attributes
- `bus` : Dict{String, Dict{String, Any}}
- `link`: Dict{Link, Dict{String, Any}}

### Example
```
Instance Matpower WB2 with two nodes
julia > databus1 = Dict("Volt" => MatpowerVolt("BUS_1",1,0.95, 1.05), "Gen_1"=> MatpowerGenerator(-400 im, 600+400im, 3, [0 2 0], true))
julia > databus2 = Dict("Volt" => MatpowerVolt("BUS_2",2,0.95, 1.05), "Load" => MatpowerLoad(350-350im))
julia > busdata = Dict("BUS_1"=> databus1, "BUS_2" => databus2)
julia > link1to2 = Dict("LinkMP_1" => MatpowerLine_π(100,0.04,0.2,0.0,0.0,0.0))
julia > linkdata = Dict( Link("BUS_1","BUS_2") => link1to2)
julia > ds = DataSource(busdata,linkdata)
```
"""
struct DataSource
  # baseMVA::Number
  bus::Dict{String, Dict{String, Any}}
  link::Dict{Link, Dict{String, Any}}
end

"""
    struct GridStructure(scenario::String, node_linksin::Dict{String, Set{Link}}, node_linksout::Dict{String, Set{Link}})

Store information about network to construct power balances.

### Attributes
- `scenario::String` : scenario to study
- `generator_types::Set{Type}` : specify which types are generators
- `node_linksin::Dict{String, Set{Link}}` : for each node, set of links having this node for destination
- `node_linksout::Dict{String, Set{Link}}` : for each node, set of links having this node for origin

### Example
```
Instance Matpower WB2 with two nodes
julia > scenario = "BaseCase"
julia > node_linksin = Dict( "BUS_2" => Set{Link}([Link("BUS_1","BUS_2")]) )
julia > node_linksout = Dict( "BUS_1" => Set{Link}([Link("BUS_1, "BUS_2")]) )
julia > gs = GridStructure(scenario,generator_types, node_linksin, node_linksout)
```
"""
mutable struct GridStructure
  scenario::String
  node_linksin::Dict{String, Set{Link}}
  node_linksout::Dict{String, Set{Link}}
end

"""
    struct MathematicalProgramming(node_formulations::Dict{String, Dict{String, Symbol}}, link_formulations::Dict{String, Dict{String, Symbol}}, node_vars::Dict{String, Dict{String, Variable}}, link_vars::Dict{Link, Dict{String, Variable}})

Store information about mathematical formulation.
A node element or a link element can be formulated with a minimal number of variables or with variables representing all the quantities linked to the element.
This formulation is represented by symbols in the dictonaries `node_formulations` or `link_formulations` depending on the type of element.
Store created variables in `node_vars` or `link_vars`.

### Example
```
Instance Matpower WB2 with two nodes
julia > node_formulations =
julia > link_formulations = Dict( Link("BUS_1","BUS_2") => Dict("LinkMP_1" => :NbMinVar))
julia > node_vars =
julia > link_vars =

```
"""
mutable struct MathematicalProgramming
  node_formulations::Dict{String, Dict{String, Symbol}} # :NbMinVar, :NewVar, :None
  link_formulations::Dict{Link, Dict{String, Symbol}}
  node_vars::Dict{String, Dict{String, Variable}}
  link_vars::Dict{Link, Dict{String, Variable}}
end

"""
    struct Scenario(ds::DataSource, gs::GridStructure, mp::MathematicalProgramming)

### Example
```
Instance Matpower WB2 with two nodes
julia > basecase_scenario = Scenario(ds, gs, mp)
```
"""
mutable struct Scenario
  ds::DataSource
  gs::GridStructure
  mp::MathematicalProgramming
end

"""
    OPFproblems

### Examples
```

```
"""
const OPFProblems = Dict{String, Scenario}

# files to include for each entry type
include(joinpath("..","src_GOC","GOC_files.jl"))
include(joinpath("..","src_Matpower","Matpower_files.jl"))
include(joinpath("..","src_IIDM","IIDM_files.jl"))

# common files
include("add_generator_variables.jl")
include("build_globalpb.jl")
include("build_Problem.jl")
include("check_feasibility.jl")
include("default_behaviour.jl")
include("load_OPFproblems.jl")
include("naming_conventions.jl")
include("PowSysMod_accessors.jl")
include("PowSysMod_operators.jl")
include("utils_plots.jl")


module CtxMacro

using Mixtape
using MacroTools

foo(x) = x^5
bar(x) = x^10
apply(f, x1, x2::Val{T}) where {T} = f(x1, T)

function f(x)
    g = x < 5 ? foo : bar
    return g(2)
end

@ctx (false, true, false) struct MyMix end
allow(ctx::MyMix, m::Module) = m == CtxMacro

swap(e) = e
function swap(e::Expr)
    new = MacroTools.postwalk(e) do s
        isexpr(s, :call) || return s
        s.args[1] == Base.literal_pow || return s
        return Expr(:call, apply, Base.:(*), s.args[3:end]...)
    end
    return new
end

function transform(::MyMix, b)
    for (v, st) in b
        replace!(b, v, swap(st))
    end
    return b
end

Mixtape.@load_call_interface()
display(call(MyMix(), f, 3))

end # module


function init_setup()
    param = :complexConjug
    θ₀ = [10.0, -5.0, 5.0, 0.0, 3.0]
    P˟ = BSI.FitzhughDiffusion(param, θ₀...)
    obs = [[0.0, 0.0], [-0.3, -0.2], [0.4, 0.2]]
    tt = [0.0, 1.0, 1.5]
    P̃ = [BSI.FitzhughDiffusionAux(param, θ₀..., tt[1], obs[1], tt[2], obs[2]),
         BSI.FitzhughDiffusionAux(param, θ₀..., tt[2], obs[2], tt[3], obs[3])]
    setup = BSI.DiffusionSetup(P˟, P̃, BSI.PartObs())
    (setup = setup, θ = θ₀, trgt = P˟, obs = obs, tt = tt, aux = P̃)
end

@testset "mcmc schedule" begin
    num_mcmc_steps = 20
    updt_idx = [[1,2,3],[4,5],[6]]
    actions = (save=10, verbose=3, warm_up=5,
               readjust=(x->x%7==0), fuse=(x->false))
    schedule = BSI.MCMCSchedule(num_mcmc_steps, updt_idx, actions)

    @testset "initialisation" begin
        @test schedule.num_mcmc_steps == num_mcmc_steps
        @test schedule.updt_idx == updt_idx
        @test schedule.actions == actions
    end

    @testset "correct iterations" begin
        i = 0
        for step in schedule
            i += 1
            @test step.iter == i
            @test step.idx == updt_idx[mod1(i, length(updt_idx))]
            @test step.save == (i in [10, 20])
            @test step.verbose == (i % actions.verbose == 0)
            @test step.param_updt == (i > actions.warm_up)
            @test step.readjust == (i % 7 == 0)
            @test step.fuse == false
        end
        @test i == 20
    end
end

@testset "workspace" begin
    out = init_setup()
    setup, θ, trgt, obs, tt = out.setup, out.θ, out.trgt, out.obs, out.tt
    aux = out.aux

    L = [1. 0.; 0. 1.]
    Σ = [0.5 0.0; 0.0 1.0]
    BSI.set_observations!(setup, [L, L], [Σ, 2*Σ], obs, tt)

    dt = 0.01
    τ(t₀,T) = (x) ->  t₀ + (x-t₀) * (2-(x-t₀)/(T-t₀))
    BSI.set_imputation_grid!(setup, dt, τ)

    x0_prior = BSI.KnownStartingPt(obs[1])
    BSI.set_x0_prior!(setup, x0_prior)

    BSI.initialise!(Float64, setup, Vern7(), false, NoChangePt())

    ws = BSI.Workspace(setup).workspace
    @testset "initialisation" begin
        @test typeof(ws.Wnr) == Wiener{Float64}
        @test length(ws.XX) == length(ws.XXᵒ) == 2
        @test length(ws.WWᵒ) == length(ws.WW) == 2
        @test all([ws.XX[i].tt == ws.XXᵒ[i].tt for i in 1:2])
        @test all([ws.XX[i].yy == ws.XXᵒ[i].yy for i in 1:2])
        @test all([ws.WW[i].tt == ws.WWᵒ[i].tt for i in 1:2])
        @test all([ws.WW[i].yy == ws.WWᵒ[i].yy for i in 1:2])
        @test eltype(eltype(ws.WW)) == Float64
        @test eltype(eltype(ws.XX)) == SArray{Tuple{2},Float64,1,2}
        @test ws.WW[1].tt[1] == ws.XX[1].tt[1] == tt[1]
        @test ws.WW[2].tt[1] == ws.XX[2].tt[1] == tt[2]
        @test typeof(ws.P[1].Target) <: BSI.FitzhughDiffusion
        @test typeof(ws.P[1].Pt) <: BSI.FitzhughDiffusionAux
        @test all([ws.P[i].tt == ws.WW[i].tt == ws.XX[i].tt for i in 1:2])
    end
end


"""
    DiskTensors

Disk based rank 1,2, and 4 tensors. Defines functions such as +, -, dot,...
with buffered I/O. Uses HDF5 format for storing tensors. One tensor per file.

## structs
    DiskVector -> see ?Fermi.DiskTensors.DiskVector
    DiskMatrix -> see ?Fermi.DiskTensors.DiskMatrix
    DiskFourTensor -> see ?Fermi.DiskTensors.DiskFourTensor

## methods
    squeeze -> see ?Fermi.DiskTensors.squeeze
    eltype -> returns element type of a DiskFourTensor
    ranger -> converts input to a UnitRange, if possible
    blockfill! -> fills a DiskTensor with the value entered
"""
module DiskTensors

#include("DiskVectors.jl")
#include("DiskMatrices.jl")
include("DiskFourTensors.jl")

"""
	ranger
converts inp::Int64 -> UnitRange(inp:inp) for use in indexing DiskTensors
"""
function ranger(inp::Union{UnitRange{Int64},Int64,Colon})
    if typeof(inp) == Int64
        return UnitRange(inp:inp)
    else
        return inp
    end
end
export squeeze
export eltype
export ranger
#export DiskVector
export blockfill!
#export printdv
#export dvwrite!
#export dvread
#export dvdot
#export Base.getindex
#include("DiskVectorOps.jl")

#export DiskMatrix
#export dmread
#export dmwrite
#export dmdot

export DiskFourTensor
    #export dmelmult
end


using Revise, Parameters, Logging, Test
using CUDA
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\structs\\BasicStructs.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\utils\\CUDAGpuUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\utils\\IterationUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\utils\\ReductionUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\utils\\MemoryUtils.jl")
includet("C:\\GitHub\\GitHub\\NuclearMedEval\\src\\distanceMetrics\\MeansMahalinobis.jl")

using  ..CUDAGpuUtils , ..MeansMahalinobis, ..IterationUtils,..ReductionUtils , ..MemoryUtils
nx=512 ; ny=512 ; nz=317
#first we initialize the metrics on CPU so we will modify them easier
goldBoolCPU= zeros(Float32,nx,ny,nz); #mimicks gold standard mask
segmBoolCPU= zeros(Float32,nx,ny,nz); #mimicks other  mask

#cartTrueGold =  collect(vec(CartesianIndices(zeros(11,4,16) ).+CartesianIndex(70,71,78)))
cartTrueGold =  unique(vcat(collect(vec(CartesianIndices(zeros(8,15,5) ).+CartesianIndex(5,5,5)))
         ,collect(vec(CartesianIndices(zeros(14,19,25) ).+CartesianIndex(100,99,88)))
         ,collect(vec(CartesianIndices(zeros(81,87,84) ).+CartesianIndex(100,99,210)))
         )) ;
goldBoolCPU[cartTrueGold].=Float32(1.0);

cartTrueSegm =  CartesianIndices(zeros(34,235,76) ).+CartesianIndex(100,100,100);

segmBoolCPU[cartTrueSegm].=Float32(1.0);

numberToLooFor= Float32(1.0);
goldBoolGPU = CuArray(goldBoolCPU);
segmBoolGPU = CuArray(segmBoolCPU);

#we will fill it after we work with launch configuration
loopXdim = UInt32(1);loopYdim = UInt32(1) ;loopZdim = UInt32(1) ;
sizz = size(goldBoolCPU);maxX = UInt32(sizz[1]);maxY = UInt32(sizz[2]);maxZ = UInt32(sizz[3])
#gold
totalXGold= CuArray([0.0]);
totalYGold= CuArray([0.0]);
totalZGold= CuArray([0.0]);
totalCountGold= CuArray([0]);
#segm
totalXSegm= CuArray([0.0]);
totalYSegm= CuArray([0.0]);
totalZSegm= CuArray([0.0]);

varianceXGlobalGold,covarianceXYGlobalGold,covarianceXZGlobalGold,varianceYGlobalGold,covarianceYZGlobalGold,varianceZGlobalGold= CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]);
varianceXGlobalSegm,covarianceXYGlobalSegm,covarianceXZGlobalSegm,varianceYGlobalSegm,covarianceYZGlobalSegm,varianceZGlobalSegm= CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]);

totalCountSegm= CuArray([0]);
totalCountGold

countPerZGold= CUDA.zeros(Float32,sizz[3]+1);
countPerZSegm= CUDA.zeros(Float32,sizz[3]+1);

# covariancesSliceWiseGold= CUDA.zeros(Float32,6,sizz[3]+1);
# covariancesSliceWiseSegm= CUDA.zeros(Float32,6,sizz[3]+1);

covariancesSliceWiseGold= CUDA.zeros(Float32,6,500);
covariancesSliceWiseSegm= CUDA.zeros(Float32,6,500);

covarianceGlobal= CUDA.zeros(Float32,12,1);

mahalanobisResGlobal= CUDA.zeros(1);
mahalanobisResSliceWise= CUDA.zeros(sizz[3]+1);
#mahalanobisResSliceWise= CUDA.zeros(sizz[3]);

args = (goldBoolGPU,segmBoolGPU,numberToLooFor
,loopYdim,loopXdim,loopZdim
,(maxX, maxY,maxZ)
,totalXGold,totalYGold,totalZGold,totalCountGold
,totalXSegm,totalYSegm,totalZSegm,totalCountSegm,countPerZGold
, countPerZSegm,
# ,covariancesSliceWiseGold, covariancesSliceWiseSegm,
varianceXGlobalGold,covarianceXYGlobalGold,covarianceXZGlobalGold,varianceYGlobalGold,covarianceYZGlobalGold,varianceZGlobalGold
    ,varianceXGlobalSegm,covarianceXYGlobalSegm,covarianceXZGlobalSegm,varianceYGlobalSegm,covarianceYZGlobalSegm,varianceZGlobalSegm
    ,mahalanobisResGlobal
    # , mahalanobisResSliceWise
    )

    # calculate the amount of dynamic shared memory for a 2D block size
    get_shmem(threads) = (sizeof(UInt32)*3*4)

    function get_threads(threads)
        threads_x = 32
        threads_y = cld(threads,threads_x )
        return (threads_x, threads_y)
    end

    kernel = @cuda launch=false MeansMahalinobis.meansMahalinobisKernel(args...)

    config = launch_configuration(kernel.fun, shmem=threads->get_shmem(get_threads(threads)))

   # convert to 2D block size and figure out appropriate grid size
    threads = get_threads(config.threads)
    blocks = UInt32(config.blocks)
    loopXdim = UInt32(fld(maxX, threads[1]))
    loopYdim = UInt32(fld(maxY, threads[2]))
    loopZdim = UInt32(fld(maxZ,blocks ))

#covariancesSliceWise= CUDA.zeros(Float32,12,sizz[3]+1);
covariancesSliceWiseGold= CUDA.zeros(Float32,6,sizz[3]+1);
covariancesSliceWiseSegm= CUDA.zeros(Float32,6,sizz[3]+1);

#gold
totalXGold= CuArray([0.0]);
totalYGold= CuArray([0.0]);
totalZGold= CuArray([0.0]);
totalCountGold= CuArray([0]);
#segm
totalXSegm= CuArray([0.0]);
totalYSegm= CuArray([0.0]);
totalZSegm= CuArray([0.0]);
totalCountSegm= CuArray([0]);

varianceXGlobalGold,covarianceXYGlobalGold,covarianceXZGlobalGold,varianceYGlobalGold,covarianceYZGlobalGold,varianceZGlobalGold= CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]);
varianceXGlobalSegm,covarianceXYGlobalSegm,covarianceXZGlobalSegm,varianceYGlobalSegm,covarianceYZGlobalSegm,varianceZGlobalSegm= CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]),CuArray([0.0]);

args = (goldBoolGPU,segmBoolGPU,numberToLooFor
,loopYdim,loopXdim,loopZdim
,(maxX, maxY,maxZ)
,totalXGold,totalYGold,totalZGold,totalCountGold
,totalXSegm,totalYSegm,totalZSegm,totalCountSegm,countPerZGold
, countPerZSegm,
# ,covariancesSliceWiseGold, covariancesSliceWiseSegm,
varianceXGlobalGold,covarianceXYGlobalGold,covarianceXZGlobalGold,varianceYGlobalGold,covarianceYZGlobalGold,varianceZGlobalGold
    ,varianceXGlobalSegm,covarianceXYGlobalSegm,covarianceXZGlobalSegm,varianceYGlobalSegm,covarianceYZGlobalSegm,varianceZGlobalSegm
    ,mahalanobisResGlobal
    # , mahalanobisResSliceWise
    ,1
    )

    @cuda cooperative=true threads=threads blocks=blocks MeansMahalinobis.meansMahalinobisKernel(args...)

    mahalanobisResGlobal[1]

    # totalCountGold[1]

    # totalCountSegm[1]
    Int64(round(totalXGold[1]))
    Int64(round(totalYGold[1]))
   aa= Int64(round(varianceXGlobalSegm[1]))
   bb=  Int64(round(varianceXGlobalGold[1]))
    aa+bb
    @test totalCountGold[1]==length(cartTrueGold)

    varianceX= (varianceXGlobalGold[1]+ varianceXGlobalSegm[1])/ (totalCountGold[1]+ totalCountSegm[1])
    covarianceXY= (covarianceXYGlobalGold[1]+ covarianceXYGlobalSegm[1])/ (totalCountGold[1]+ totalCountSegm[1])
    covarianceXZ= (covarianceXZGlobalGold[1]+ covarianceXZGlobalSegm[1])/ (totalCountGold[1]+ totalCountSegm[1])
    varianceY= (varianceYGlobalGold[1]+ varianceYGlobalSegm[1])/ (totalCountGold[1]+ totalCountSegm[1])
    covarianceYZ= (covarianceYZGlobalGold[1]+ covarianceYZGlobalSegm[1])/ (totalCountGold[1]+ totalCountSegm[1])
    varianceZ= (varianceZGlobalGold[1]+ varianceZGlobalSegm[1])/ (totalCountGold[1]+ totalCountSegm[1])
    meanX = (totalXGold[1]/totalCountGold[1]) - (totalXSegm[1]/totalCountSegm[1] )
    meanY = (totalYGold[1]/totalCountGold[1]) - (totalYSegm[1]/totalCountSegm[1] )
    meanZ = (totalZGold[1]/totalCountGold[1]) - (totalZSegm[1]/totalCountSegm[1] )

a = sqrt(varianceX) #18.205059
b = (covarianceXY)/a
c = (covarianceXZ)/a
e = sqrt(varianceY - b*b)
d = (covarianceYZ -(c * b))/e
#unrolled forward substitiution
ya= meanX/a
yb = (meanY-b*ya)/e
yc= (meanZ-yb*d-ya* c)/sqrt(varianceZ - c*c -d*d )
#taking square euclidean distance
sqrt(ya*ya+yb*yb+yc*yc)

 Int64(maximum(covariancesSliceWiseGold[:,1,1,1,1,1]))
 Int64(maximum(covariancesSliceWiseSegm[:,1,1,1,1,1]))

@testset " mahalinobis tests " begin
      @test totalCountGold[1]==length(cartTrueGold)
    @test totalXGold[1]== sum(map(ind->ind[1],cartTrueGold))
    @test totalYGold[1]== sum(map(ind->ind[2],cartTrueGold))
    @test totalZGold[1]== sum(map(ind->ind[3],cartTrueGold))

    @test totalXSegm[1]== sum(map(ind->ind[1],cartTrueSegm))
    @test totalYSegm[1]== sum(map(ind->ind[2],cartTrueSegm))
    @test totalZSegm[1]== sum(map(ind->ind[3],cartTrueSegm))
    @test totalCountSegm[1]== length(cartTrueSegm)
    @test sum(countPerZGold)==length(cartTrueGold)
    @test sum(countPerZSegm)==length(cartTrueSegm)
    #@test countPerZSegm[100+1]==150*150
    @test countPerZGold[5+1]==8*15

    meanX = totalXGold[1]/totalCountGold[1]
    meanY = totalYGold[1]/totalCountGold[1]
    meanZ = totalZGold[1]/totalCountGold[1]
    @test isapprox(varianceXGlobalGold[1],sum(map(ind->( ind[1] -meanX )^2 ,cartTrueGold)); atol = 50)

    @test isapprox(covarianceXYGlobalGold[1],sum(map(ind->(( ind[1] -meanX )*( ind[2] -meanY )) ,cartTrueGold)); atol = 50)
    @test isapprox(covarianceXZGlobalGold[1],sum(map(ind->(( ind[1] -meanX )*( ind[3] -meanZ )) ,cartTrueGold)); atol = 50)

   Int64(round( covarianceXZGlobalGold[1]- sum(map(ind->(( ind[1] -meanX )*( ind[3] -meanZ )) ,cartTrueGold))))

    @test isapprox(varianceYGlobalGold[1],sum(map(ind->(( ind[2] -meanY )^2) ,cartTrueGold)); atol = 50)

    @test isapprox(varianceZGlobalGold[1], sum(map(ind->(( ind[3] -meanZ )^2) ,cartTrueGold)); atol = 50)

    @test isapprox(covarianceYZGlobalGold[1],sum(map(ind->(( ind[2] -meanY )*(ind[3] -meanZ  )) ,cartTrueGold)); atol = 50)

    meanX = totalXSegm[1]/totalCountSegm[1]
    @test isapprox(varianceXGlobalSegm[1],sum(map(ind->( ind[1] -meanX )^2 ,cartTrueSegm)); atol = 50)

    meanY = totalYSegm[1]/totalCountSegm[1]
    @test isapprox(covarianceXYGlobalSegm[1],sum(map(ind->(( ind[1] -meanX )*( ind[2] -meanY )) ,cartTrueSegm)); atol = 50)
    meanZ = totalZSegm[1]/totalCountSegm[1]
    @test isapprox(covarianceXZGlobalSegm[1],sum(map(ind->(( ind[1] -meanX )*( ind[3] -meanZ )) ,cartTrueSegm)); atol =50)

    @test isapprox(varianceYGlobalSegm[1],sum(map(ind->(( ind[2] -meanY )^2) ,cartTrueSegm)); atol =50)

    @test isapprox(varianceZGlobalSegm[1],sum(map(ind->(( ind[3] -meanZ )^2) ,cartTrueSegm)); atol = 50)

    Int64(round(varianceZGlobalSegm[1]-sum(map(ind->(( ind[3] -meanZ )^2) ,cartTrueSegm))))

    @test isapprox(covarianceYZGlobalSegm[1],sum(map(ind->(( ind[2] -meanY )*(ind[3] -meanZ  )) ,cartTrueSegm)); atol = 50)
    @test isapprox(mahalanobisResGlobal[1],4.660; atol = 0.1)

end

### can be put in getFinalResults macro to check is it working
            # @ifXY 1 numb CUDA.@cuprint "th 1   res $(sumX/(sumY+sumZ))  correct 331.42417036764425\n "
        # @ifXY 3 numb CUDA.@cuprint "th 3  res $(sumX/(sumY+sumZ))  correct 14.653258176782604  \n "
        # @ifXY 5 numb CUDA.@cuprint "th 5  res $(sumX/(sumY+sumZ))  correct  43.483690662640186 \n "
        # @ifXY 7 numb  CUDA.@cuprint "th 7   res $(sumX/(sumY+sumZ))  correct  2640.6032539591756 \n "
        # @ifXY 10 numb  CUDA.@cuprint "th 10  res $(sumX/(sumY+sumZ))   correct 43.71496394540232 \n "
        # @ifXY 12 numb  CUDA.@cuprint "th 12 res $(sumX/(sumY+sumZ)) correct 685.3852392889322  \n "
        # @ifXY 14 numb  CUDA.@cuprint "th 14   res $(sumX/(sumY))  correct 140.4965 \n "
        # @ifXY 16 numb  CUDA.@cuprint "th 16   res $(sumX/(sumY))  correct 142.492 \n "
        # @ifXY 18 numb  CUDA.@cuprint "th 18  res $(sumX/(sumY))   correct  250.57380 \n "
        # @ifXY 20 numb CUDA.@cuprint "th 20   res $(sumX/(sumZ))  correct 117.5\n "
        # @ifXY 22 numb  CUDA.@cuprint "th 22  res $(sumX/(sumZ))  correct 218.0 \n "
        # @ifXY 24 numb  CUDA.@cuprint "th 24   res $(sumX/(sumZ))  correct 138.5 \n "

    # @device_code_warntype interactive=true @cuda MeansMahalinobis.meansMahalinobisKernel(args...)


#===============================================================================

    verify_request.jl

    Author: Tom Price
    Date: Dec 2018

    This Julia script tests the JSON data structures
    that are supplied in the body of GET requests

    *     based on documentation of REST API from juliaapi_new.txt

    *     Julia listens on http://localhost:8081 via HttpServer.jl and dispatch.jl
          and sends responses to the Rails app on http://localhost:3000

    *     the relevant controller in the Rails app is
          chaipcr/web/app/controllers/experiments_controller.rb

    *     currently calls are GET-ed to
          http://127.0.0.1:8081/experiments/#{experiment.id}/standard_curve
          http://127.0.0.1:8081/experiments/#{experiment.id}/amplification
          http://127.0.0.1:8081/experiments/#{experiment.id}/meltcurve
          http://127.0.0.1:8081/experiments/#{experiment.id}/analyze

===============================================================================#

import JSON, DataStructures.OrderedDict
import FactCheck: facts, context, @fact, clear_results, exitstatus, less_than_or_equal

FactCheck.setstyle(:default)

#===============================================================================
    Here are the REST APIs using HTTP GET
===============================================================================#

#===============================================================================

    call: experiments/:experiment_id/standard_curve

===============================================================================#

function verify_request(
    ::Type{Val{QpcrAnalysis.standard_curve}},
    request ::AbstractArray
)
    facts("Standard curve requested") do
        context("Verifying request body") do
            @fact (isa(request,Vector)) --> true
            n_targets=-99
            for i in range(1,length(request))
                well=request[i]
                @fact (isa(well,OrderedDict)) --> true
                if (length(well)>0)
                    @fact (length(well)) --> 1
                    @fact (haskey(well,"well")) --> true
                    array=well["well"]
                    for j in range(1,length(array))
                        dict=array[j]
                        @fact (isa(dict,OrderedDict)) --> true
                        if (length(dict)>0)
                            # fact (length(dict)) --> 3
                            @fact (haskey(dict,"target")) --> true
                            @fact (isa(dict["target"],Integer)) --> true
                            @fact (haskey(dict,"cq")) --> true
                            @fact (isa(dict["cq"],Number)) --> true
                            @fact (haskey(dict,"quantity")) --> true
                            subdict=dict["quantity"]
                            @fact (isa(subdict,OrderedDict)) --> true
                            @fact (length(subdict)) --> 2
                            @fact (haskey(subdict,"m")) --> true
                            @fact (haskey(subdict,"b")) --> true
                            @fact (isa(subdict["m"],Number)) --> true
                            @fact (isa(subdict["b"],Number)) --> true
                        end
                    end
                end
            end
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/amplification

===============================================================================#

## according to juliaapi_new.txt:
#
## each set of calibration data (water, channel_1, channel_2) comes from the following SQL query:
#
# SELECT fluorescence_value, well_num, channel
#     FROM fluorescence_data
#     WHERE experiment_id = $calib_id AND step_id = $step_id
#     ORDER BY channel, well_num
# ;
#
# channel_2 will be NULL for single channel:
#     "calibration_info": {
#         "water": {
#             "fluorescence_value": [
#                 [water_1__well_01, water_1__well_02, …, water_1__well_16],
#                 null
#             ]
#         },
#         "channel_1": {
#             "fluorescence_value": [
#                 [signal_1__well_01, signal_1__well_02, …, signal_1__well_16],
#                 null
#             ]
#         }
#     }

function calibration_test(
    calib ::Associative,
    n_channels ::Integer =length(CHANNELS),
    conditions ::AbstractArray =["water","channel_1","channel_2"][range(1, n_channels+1)]
)
    n_conditions=length(conditions)
    @fact (isa(calib,OrderedDict)) --> true
    # @fact (length(calib)) --> n_conditions
    @fact (isa(calib[conditions[1]],OrderedDict))--> true
    @fact (haskey(calib[conditions[1]],"fluorescence_value")) --> true
    @fact (isa(calib[conditions[1]]["fluorescence_value"],Vector)) --> true
    n_wells=length(calib[conditions[1]]["fluorescence_value"][1])
    for condition in conditions
        @fact (haskey(calib,condition)) --> true
        @fact (isa(calib[condition],OrderedDict)) --> true
        @fact (length(calib[condition])) --> 1
        @fact (haskey(calib[condition],"fluorescence_value")) --> true
        @fact (isa(calib[condition]["fluorescence_value"],Vector)) --> true
        @fact (length(calib[condition]["fluorescence_value"])) --> less_than_or_equal(2)
        for channel in range(1,n_channels)
            @fact (isa(calib[condition]["fluorescence_value"][channel],Vector)) --> true
            @fact (length(calib[condition]["fluorescence_value"][channel])) --> n_wells
            for i in range(1,n_wells)
                @fact (isa(calib[condition]["fluorescence_value"][channel][i],Number)) --> true
            end
        end
    end
    FactCheck.exitstatus()
end

## Raw Data comes from the following SQL query:
# SELECT fluorescence_value, well_num, cycle_num, channel
#     FROM fluorescence_data
#     WHERE experiment_id = $exp_id AND step_id = $step_id
#   ORDER BY channel, well_num, cycle_num
# ;

function raw_test(raw ::Associative)
    @fact (isa(raw,OrderedDict)) --> true
    variables=["fluorescence_value", "channel", "well_num"]
    if (haskey(raw,"temperature"))
        push!(variables,"temperature")
    else
        push!(variables,"cycle_num")
    end
    n_raw=length(raw["fluorescence_value"])
    for v in variables
        @fact (haskey(raw,v)) --> true
        @fact (isa(raw[v], Vector)) --> true
        @fact abs(length(raw[v]) - n_raw) --> less_than_or_equal(1)
        for i in range(1,length(raw[v]))
            if (v=="fluorescence_value" || v=="temperature")
                @fact (isa(raw[v][i], Number)) --> true
            else
                @fact (isa(raw["well_num"][i], Integer)) --> true
            end
        end
    end
    FactCheck.exitstatus()
end

function verify_request(
    ::Type{Val{amplification}},
    request ::Associative
)
    facts("Amplification requested") do
        context("Verifying request body") do
            @fact (isa(request,OrderedDict)) --> true
            @fact (haskey(request,"experiment_id")) --> true
            @fact (isa(request["experiment_id"],Integer)) --> true
            if (haskey(request,"step_id"))
                id="step_id"
            else
                id="ramp_id"
            end
            @fact (haskey(request,id)) --> true
            @fact (isa(request[id],Integer)) --> true
            if (haskey(request,"min_reliable_cyc"))
                @fact (isa(request["min_reliable_cyc"],Integer)) --> true
            end
            if (haskey(request,"baseline_cyc_bounds"))
                @fact (isa(request["baseline_cyc_bounds"],Vector)) --> true
                if (length(request["baseline_cyc_bounds"]) > 0)
                    @fact (length(request["baseline_cyc_bounds"])) --> 2
                    @fact (isa(request["baseline_cyc_bounds"][1],Integer)) --> true
                    @fact (isa(request["baseline_cyc_bounds"][2],Integer)) --> true
                end
            end
            if (haskey(request,"baseline_method"))
                @fact (isa(request["baseline_method"],String)) --> true
                @fact (
                    request["baseline_method"] == "sigmoid" ||
                    request["baseline_method"] == "linear"  ||
                    request["baseline_method"] == "median"
                ) --> true
            end
            if (haskey(request,"cq_method"))
                @fact (isa(request["cq_method"],String)) --> true
                @fact (request["cq_method"] in ["cp_dr1","cp_dr2","Cy0","ct"]) --> true
            end
            if (haskey(request,"min_fluomax"))
                @fact (isa(request["min_fluomax"],Number)) --> true
            end
            if (haskey(request,"min_D1max"))
                @fact (isa(request["min_D1max"],Number)) --> true
            end
            if (haskey(request,"min_D2max"))
                @fact (isa(request["min_D2max"],Number)) --> true
            end
            @fact (haskey(request,"calibration_info")) --> true
            calib=request["calibration_info"]
            @fact (isa(calib,OrderedDict)) --> true
            @fact (haskey(calib,"water")) --> true
            @fact (isa(calib["water"],OrderedDict)) --> true
            @fact (haskey(calib["water"],"fluorescence_value")) --> true
            @fact (isa(calib["water"]["fluorescence_value"],Vector)) --> true
            if length(calib["water"]["fluorescence_value"]) < 2 ||
                calib["water"]["fluorescence_value"][2]==nothing
                n_channels=1
            else
                n_channels=2
            end
            @fact (haskey(request,"raw_data")) --> true
            raw=request["raw_data"]
            @fact (isa(raw,OrderedDict)) --> true
            calibration_test(calib,n_channels)
            raw_test(raw)
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/meltcurve

===============================================================================#

#=
    Notes:
    channel_nums = [1] for 1 channel, [1,2] for 2 channels, etc.
    top_N = number of Tm peaks to report
=#

# Calibration (water, channel_1, channel_2) data comes from the following SQL query:
# SELECT fluorescence_value, well_num, channel
#     FROM fluorescence_data
#     WHERE experiment_id = $calib_id AND step_id = $step_id
#     ORDER BY channel, well_num
# ;

# Raw Data comes from the following SQL query:
# SELECT fluorescence_value, temperature, well_num, channel
#     FROM melt_curve_data
#     WHERE
#         experiment_id = $exp_id AND
#         stage_id = $stage_id
#     ORDER BY channel, well_num
# ;

function verify_request(
    ::Type{Val{QpcrAnalysis.meltcurve}},
    request ::Any
)
    facts("Melting curve requested") do
        context("Verifying request body") do
            @fact (isa(request,OrderedDict)) --> true
            @fact (haskey(request,"experiment_id")) --> true
            @fact (isa(request["experiment_id"],Integer)) --> true
            @fact (haskey(request,"stage_id")) --> true
            @fact (isa(request["stage_id"],Integer)) --> true
            @fact (haskey(request,"calibration_info")) --> true
            @fact (haskey(request,"channel_nums")) --> true
            @fact (isa(request["channel_nums"],Vector)) --> true
            calib=request["calibration_info"]
            @fact (isa(calib,OrderedDict)) --> true
            @fact (haskey(calib,"water")) --> true
            @fact (isa(calib["water"],OrderedDict)) --> true
            @fact (haskey(calib["water"],"fluorescence_value")) --> true
            @fact (isa(calib["water"]["fluorescence_value"],Vector)) --> true
            if length(calib["water"]["fluorescence_value"])<2 ||
                calib["water"]["fluorescence_value"][2]==nothing
                @fact (request["channel_nums"]) --> [1]
                n_channels=1
            else
                @fact (request["channel_nums"]) --> [1,2]
                n_channels=2
            end
            if (haskey(request,"qt_prob"))
                @fact (isa(request["qt_prob"],Number)) --> true
            end
            if (haskey(request,"max_normd_qtv"))
                @fact (isa(request["max_normd_qtv"],Number)) --> true
            end
            if (haskey(request,"top_N"))
                @fact (isa(request["top_N"],Integer)) --> true
            end
            @fact (haskey(request,"raw_data")) --> true
            raw=request["raw_data"]
            calibration_test(calib,n_channels)
            raw_test(raw)
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/thermal_performance_diagnostic

===============================================================================#

# SQL query:
# SELECT *
#     FROM temperature_logs
#     WHERE experiment_id = $exp_id
#     ORDER BY id
# ;

function verify_request(
    ::Type{Val{QpcrAnalysis.thermal_performance_diagnostic}},
    request ::Any
)
    facts("Thermal performance diagnostic requested") do
        context("Verifying request body") do
            variables=["lid_temp","heat_block_zone_1_temp","heat_block_zone_2_temp","elapsed_time"] # ,"cycle_num"]
            @fact (isa(request,OrderedDict)) --> true
            @fact (length(request)) --> length(variables)
            n_cycles=length(request["elapsed_time"])
            for v in variables
                @fact (haskey(request,v)) --> true
                @fact (length(request[v])) --> n_cycles
                for i in range(1,n_cycles)
                    if (v=="elapsed_time")
                        @fact (isa(request[v][i],Integer)) --> true
                    else
                        @fact (isa(request[v][i],Number)) --> true
                    end
                end
            end
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/thermal_consistency

===============================================================================#

# SQL query:
# SELECT fluorescence_value, temperature, well_num, channel
#     FROM melt_curve_data
#     WHERE
#         experiment_id = $exp_id AND
#         stage_id = $stage_id
#   ORDER BY channel, well_num
# ;

function verify_request(
    ::Type{Val{QpcrAnalysis.thermal_consistency}},
    request ::Any
)
    facts("Thermal consistency requested") do
        context("Verifying request body") do
            @fact (isa(request,OrderedDict)) --> true
            # @fact (length(request)) --> 2
            @fact (haskey(request,"raw_data")) --> true
            @fact (isa(request["raw_data"],OrderedDict)) --> true
            @fact (haskey(request["raw_data"],"channel")) --> true
            @fact (haskey(request,"calibration_info")) --> true
            calib=request["calibration_info"]
            @fact (isa(calib,OrderedDict)) --> true
            @fact (haskey(calib,"water")) --> true
            @fact (isa(calib["water"],OrderedDict)) --> true
            @fact (haskey(calib["water"],"fluorescence_value")) --> true
            @fact (isa(calib["water"]["fluorescence_value"],Vector)) --> true
            if length(calib["water"]["fluorescence_value"]) < 2 ||
                calib["water"]["fluorescence_value"][2]==nothing
                n_channels=1
            else
                n_channels=2
            end
            calibration_test(calib,n_channels)
            raw_test(request["raw_data"])
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/optical_cal

===============================================================================#

function verify_request(
    ::Type{Val{QpcrAnalysis.optical_cal}},
    request ::Any
)
    facts("Optical calibration requested") do
        context("Verifying request body") do
            @fact (isa(request,OrderedDict)) --> true
            @fact (haskey(request,"calibration_info")) --> true
            calib=request["calibration_info"]
            @fact (isa(calib,OrderedDict)) --> true
            @fact (haskey(calib,"water")) --> true
            @fact (isa(calib["water"],OrderedDict)) --> true
            @fact (haskey(calib["water"],"fluorescence_value")) --> true
            @fact (isa(calib["water"]["fluorescence_value"],Vector)) --> true
            if length(calib["water"]["fluorescence_value"]) < 2 ||
                calib["water"]["fluorescence_value"][2]==nothing
                n_channels=1
            else
                n_channels=2
            end
            calibration_test(calib,n_channels)
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/optical_test_single_channel

===============================================================================#

## SQL query:
# SELECT fluorescence_value, well_num, cycle_num
#     FROM fluorescence_data
#     WHERE experiment_id = $exp_id AND step_id = $step_id
#     ORDER BY well_num, cycle_num
# ;

function verify_request(
    ::Type{Val{QpcrAnalysis.optical_test_single_channel}},
    request ::Any
)
    facts("Single channel optical test requested") do
        context("Verifying request body") do
            conditions=["baseline","excitation"]
            @fact (isa(request,OrderedDict)) --> true
            # @fact (length(request)) --> length(conditions)
            @fact (isa(request["baseline"],OrderedDict))--> true
            @fact (haskey(request["baseline"],"fluorescence_value")) --> true
            @fact (isa(request["baseline"]["fluorescence_value"],Vector)) --> true
            n_wells=length(request["baseline"]["fluorescence_value"])
            for condition in conditions
                @fact (haskey(request,condition)) --> true
                @fact (isa(request[condition],OrderedDict)) --> true
                @fact (length(request[condition])) --> 1
                @fact (haskey(request[condition],"fluorescence_value")) --> true
                @fact (isa(request[condition]["fluorescence_value"],Vector)) --> true
                @fact (isa(request[condition]["fluorescence_value"],Vector)) --> true
                @fact (length(request[condition]["fluorescence_value"])) --> n_wells
                for i in range(1,n_wells)
                    @fact (isa(request[condition]["fluorescence_value"][i],Number)) --> true
                end
            end
        end
    end
    FactCheck.exitstatus()
end

#===============================================================================

    call: experiments/:experiment_id/optical_test_dual_channel

===============================================================================#

## SQL query:
# SELECT fluorescence_value, well_num, cycle_num
#     FROM fluorescence_data
#     WHERE experiment_id = $exp_id AND step_id = $step_id
#     ORDER BY well_num, cycle_num
# ;

function verify_request(
    ::Type{Val{QpcrAnalysis.optical_test_dual_channel}},
    request ::Any
)
    facts("Dual channel optical test requested") do
        context("Verifying request body") do
            @fact (isa(request,OrderedDict)) --> true
            if (haskey(request,"channel_1"))
                calibration_test(request,2,["baseline","water","channel_1","channel_2"])
            else
                calibration_test(request,2,["baseline","water","FAM","HEX"])
            end
        end
    end
    FactCheck.exitstatus()
end


export scan_shots

function scan_shots!(s::IO, mem_chunk::Int, mem_trace::Int,
                    keys::Array{String,1}, file::String, scan::Array{BlockScan,1}, fl_eof::Bool)

    # Load chunk into memory
    chunk_start = position(s)
    buf = IOBuffer(read(s, mem_chunk))
    eof(s) ? (fl_eof=true) : nothing
    buf_size = position(seekend(buf)); seekstart(buf)
    ntraces = Int(floor(buf_size/mem_trace))
    headers = Array{BinaryTraceHeader,1}(undef, ntraces)

    # Get headers from chunk
    for i in 1:ntraces
        headers[i] = read_traceheader(buf, keys, SegyIO.th_b2s)
        skip(buf, mem_trace-240)
    end

    # Get all requested header vectors
    vals = Dict{String, Array{Int32,1}}()
    for k in keys
        tmp = Int32.([getfield((headers[i]), Symbol(k)) for i in 1:ntraces])
        vals["$k"] = tmp
    end # k

    # Deliminate to find shots
    sx = vals["SourceX"]
    sy = vals["SourceY"]
    #combo = [[view(sx,i) view(sy,i)] for i in 1:ntraces]
    combo = [[sx[i] sy[i]] for i in 1:ntraces]
    part = delim_vector(combo,1)
    fl_eof ? push!(part, length(combo) + 1) : push!(part, ntraces + 1)

    # Summarise each shot
    for shot in 1:length(part)-1
        start_trace = part[shot]
        end_trace = part[shot + 1]-1
        start_byte = mem_trace*(start_trace-1) + chunk_start
        end_byte = mem_trace*(end_trace) + chunk_start
        summary = Dict{String, Array{Int32,1}}()

        for k in keys
            tmp = vals[k][start_trace:end_trace]
            summary["$k"] = [minimum(tmp); maximum(tmp)]
        end

        push!(scan, BlockScan(file, start_byte, end_byte, summary))
    end

    seek(s, scan[end].endbyte)
    close(buf)
    nothing
end


type MktEvent
    d::Int32 # date
    t::Int64# time
    dt::DateTime # datetime
    isExec::Int32
    bid::Float32
    ask::Float32
    lastAmt::Int32
    BS::Int32
    OC::Int32
    CP::Int32
    oType::Int32
    AcctID::Int32
    function MktEvent()
        new(zero(Int32),   zero(Int64),   typemin(DateTime), zero(Int32),
            zero(Float32), zero(Float32), zero(Int32),       zero(Int32),
            zero(Int32),   zero(Int32),   zero(Int32),       zero(Int32))
    end
end

function datetimeParse(d::Int32, t::Int64)
    yr = floor(d / 10000)
    mn = floor((d - yr * 10000) / 100)
    dy = floor(d - (yr * 10000 + mn * 100))
    hr = floor(t/ 3600000)
    mi = floor((t - hr * 3600000) / 60000)
    sc = floor((t - (hr * 3600000 + mi * 60000)) /  1000)
    ms = floor(t - (hr * 3600000 + mi * 60000 + sc * 1000))
    DateTime(yr, mn, dy, hr, mi, sc, ms)
end

type IODataCache
    ints::Array{Int32,1}
    intsLrg::Array{Int64,1}
    floats::Array{Float32,1}
    IODataCache() = new(zeros(Int32,1),zeros(Int64,1),zeros(Float32,1))
end

function fillBinaryEvent!(io::IOStream, d::IODataCache, e::MktEvent)
    read!(io, d.ints); e.d = d.ints[1]
    read!(io, d.intsLrg); e.t = d.intsLrg[1]
    e.dt = datetimeParse(e.d,e.t)
    read!(io, d.ints); e.isExec = d.ints[1]
    read!(io, d.floats); e.bid = d.floats[1]
    read!(io, d.floats); e.ask = d.floats[1]
    read!(io, d.ints); e.lastAmt = d.ints[1]
    read!(io, d.ints); e.BS = d.ints[1] * -1
    read!(io, d.ints); e.OC = d.ints[1]
    read!(io, d.ints); e.CP = d.ints[1]
    read!(io, d.ints); e.oType = d.ints[1]
    read!(io, d.ints); e.AcctID = d.ints[1]
end

oTypes = ["UNDEFINED" => -1,"null" => 0, "Bulk Settlement"=> 1,"LIMIT"=> 2,"Loss Cut"=> 3,
          "Market"=> 4,"null"=> 5,"Quoted"=> 6,"STOP"=> 7]


module CURAND

using ..CuArrays
using ..CuArrays: active_context

using CUDAapi

using CUDAdrv
using CUDAdrv: CUstream

import CUDAnative

using CEnum

const libcurand = Ref("libcurand")

# core library
include("libcurand_common.jl")
include("error.jl")
include("libcurand.jl")

# low-level wrappers
include("wrappers.jl")

# high-level integrations
include("random.jl")

const _generators = Dict{CuContext,RNG}()
const _generator = Ref{Union{Nothing,RNG}}(nothing)

function generator()
    if _generator[] == nothing
        CUDAnative.maybe_initialize("CURAND")
        _generator[] = get!(_generators, active_context[]) do
            context = active_context[]
            RNG()
        end
    end

    return _generator[]::RNG
end

end

const seed! = CURAND.seed!
const rand = CURAND.rand
const randn = CURAND.randn
const rand_logn = CURAND.rand_logn
const rand_poisson = CURAND.rand_poisson

@deprecate curand CuArrays.rand
@deprecate curandn CuArrays.randn


# [ ] How to support states, actions, etc.
# [ ] initial state distributions??
#
# Things that make this unnecessarily complicated for MCTS
# [ ] rng
# [ ] isterminal

struct StepFunctionMDP{S,A,F,G,I,T}
    step::F
    actions::G
    initialstate::I
    discount::Float64
    isterminal::T
end

StepFunctionMDP(step, actions, initialstate, discount, isterminal=s->false)

POMDPs.generate_sri(m::StepFunctionMDP, s, a, rng) = m.step(s, a, rng)
POMDPs.generate_sr(m::StepFunctionMDP, s, a, rng) = m.step(s, a, rng)[1:2]
POMDPs.generate_s(m::StepFunctionMDP, s, a, rng) = m.step(s, a, rng)[1]

POMDPs.actions(m::StepFunctionMDP, s) = m.actions(s)

POMDPs.discount(m::StepFunctionMDP) = m.discount
POMDPs.initialstate(m::StepFunctionMDP, rng) = m.initialstate(rng)
POMDPs.isterminal(m::StepFunctionMDP, s) = m.isterminal(s)


@testset "datacontainer" begin
    n_samples = 10
    n_dim = 4
    n_out = 3
    X = [rand(n_dim) for _ in 1:n_samples]
    y = rand(n_samples)
    ys = [rand(n_samples) for _ in 1:n_out]
    ybad = rand(n_samples + 1)
    ysbad = vcat(ys, ybad)
    @testset "DataContainer" begin
        data = AGP.wrap_data(X, y)
        @test AGP.nSamples(data) == n_samples
        @test AGP.nDim(data) == n_dim
        @test AGP.nOutput(data) == 1
        @test AGP.input(data) == X
        @test AGP.output(data) == y
        @test_throws ErrorException AGP.wrap_data(X, ybad)
        # Multiple ys case
        data = AGP.wrap_data(X, ys)
        @test AGP.nSamples(data) == n_samples
        @test AGP.nDim(data) == n_dim
        @test AGP.nOutput(data) == 1
        @test AGP.input(data) == X
        @test AGP.output(data) == ys
        @test_throws ErrorException AGP.wrap_data(X, ysbad)
    end

    @testset "MODataContainer" begin
        data = AGP.wrap_modata(X, ys)
        @test AGP.nSamples(data) == n_samples
        @test AGP.nDim(data) == n_dim
        @test AGP.nOutput(data) == n_out
        @test AGP.input(data) == X
        @test AGP.output(data) == ys
        @test_throws ErrorException AGP.wrap_data(X, ysbad)
    end

    @testset "OnlineDataContainer" begin
        data = AGP.OnlineDataContainer()
        AGP.wrap_data!(data, AGP.wrap_X(X)[1], y)
        @test AGP.nSamples(data) == n_samples
        @test AGP.nDim(data) == n_dim
        @test AGP.nOutput(data) == 1
        @test AGP.input(data) == X
        @test AGP.output(data) == y
    end

    X = rand(5, 2)
    @testset "wrap_X" begin
        @test AGP.wrap_X(X, 1)[1] isa AbstractVector
        @test AGP.wrap_X(X, 2)[1] isa AbstractVector
        @test AGP.wrap_X(vec(X))[1] isa AbstractVector
        @test AGP.wrap_X(vec(X))[2] == Float64
    end

end


using Plots
using ColorTypes
using Images
gr()

function anim_viz(x_spikes::Array{Int64}, y_spikes::Array{Int64},
                  img::Array{RGB{Float64},2}, n_neurons::Int64, step::Int64,
                  window::Int64 = 60)
    if step > window
        p1 = scatter(
            x_spikes[step-window:step],
            y_spikes[step-window:step],
            ylim = [0,n_neurons],
            xlabel = "Step",
            ylabel = "Neuron",
            legend = false
        )
        p2 = plot(img, ticks = nothing)
        plot(p1, p2, layout = 2)
    end
end


# metropolis_intro.jl

include("../src/intro.jl")
include("../src/fast_LP.jl")
include("new_LP_test.jl")


# Partition mesh using a static domain decomposition algorithm based on leaf cell count alone
function partition!(mesh)
  # Determine number of leaf cells per rank
  leaves = leaf_cells(mesh.tree)
  @assert length(leaves) > mpi_nranks()
  n_leaves_per_rank = OffsetArray(fill(div(length(leaves), mpi_nranks()), mpi_nranks()),
                                  0:(mpi_nranks() - 1))
  for d in 0:(rem(length(leaves), mpi_nranks()) - 1)
    n_leaves_per_rank[d] += 1
  end
  @assert sum(n_leaves_per_rank) == length(leaves)

  # Assign MPI ranks to all cells such that all ancestors of each cell - if not yet assigned to a
  # rank - belong to the same rank
  mesh.first_cell_by_rank = similar(n_leaves_per_rank)
  mesh.n_cells_by_rank = similar(n_leaves_per_rank)

  leaf_count = 0
  last_id = leaves[n_leaves_per_rank[0]]
  mesh.first_cell_by_rank[0] = 1
  mesh.n_cells_by_rank[0] = last_id
  mesh.tree.mpi_ranks[1:last_id] .= 0
  for d in 1:(length(n_leaves_per_rank)-1)
    leaf_count += n_leaves_per_rank[d-1]
    last_id = leaves[leaf_count + n_leaves_per_rank[d]]
    mesh.first_cell_by_rank[d] = mesh.first_cell_by_rank[d-1] + mesh.n_cells_by_rank[d-1]
    mesh.n_cells_by_rank[d] = last_id - mesh.first_cell_by_rank[d] + 1
    mesh.tree.mpi_ranks[mesh.first_cell_by_rank[d]:last_id] .= d
  end

  @assert all(x->x >= 0, mesh.tree.mpi_ranks[1:length(mesh.tree)])
  @assert sum(mesh.n_cells_by_rank) == length(mesh.tree)

  return nothing
end

function load_mesh(restart_filename, mpi_parallel::Val{true})
  # Get number of spatial dimensions
  ndims_ = parameter("ndims")

  # Get maximum number of cells that should be supported
  n_cells_max = parameter("n_cells_max")

  # Create mesh
  @timeit timer() "creation" mesh = TreeMesh(ParallelTree{ndims_}, n_cells_max)

  # Determine mesh filename
  filename = get_restart_mesh_filename(restart_filename, Val(true))
  mesh.current_filename = filename
  mesh.unsaved_changes = false

  # Read mesh file
  if mpi_isroot()
    h5open(filename, "r") do file
      # Set domain information
      mesh.tree.center_level_0 = read(attrs(file)["center_level_0"])
      mesh.tree.length_level_0 = read(attrs(file)["length_level_0"])
      mesh.tree.periodicity    = Tuple(read(attrs(file)["periodicity"]))
      MPI.Bcast!(collect(mesh.tree.center_level_0), mpi_root(), mpi_comm())
      MPI.Bcast!(collect(mesh.tree.length_level_0), mpi_root(), mpi_comm())
      MPI.Bcast!(collect(mesh.tree.periodicity),    mpi_root(), mpi_comm())

      # Set length
      n_cells = read(attrs(file)["n_cells"])
      MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm())
      resize!(mesh.tree, n_cells)

      # Read in data
      mesh.tree.parent_ids[1:n_cells] = read(file["parent_ids"])
      mesh.tree.child_ids[:, 1:n_cells] = read(file["child_ids"])
      mesh.tree.neighbor_ids[:, 1:n_cells] = read(file["neighbor_ids"])
      mesh.tree.levels[1:n_cells] = read(file["levels"])
      mesh.tree.coordinates[:, 1:n_cells] = read(file["coordinates"])
      @views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells],      mpi_root(), mpi_comm())
      @views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells],    mpi_root(), mpi_comm())
      @views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), mpi_comm())
      @views MPI.Bcast!(mesh.tree.levels[1:n_cells],          mpi_root(), mpi_comm())
      @views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells],  mpi_root(), mpi_comm())
    end
  else # non-root ranks
    # Set domain information
    mesh.tree.center_level_0 = MPI.Bcast!(collect(mesh.tree.center_level_0), mpi_root(), mpi_comm())
    mesh.tree.length_level_0 = MPI.Bcast!(collect(mesh.tree.length_level_0), mpi_root(), mpi_comm())[1]
    mesh.tree.periodicity    = Tuple(MPI.Bcast!(collect(mesh.tree.periodicity),    mpi_root(), mpi_comm()))

    # Set length
    n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[]
    resize!(mesh.tree, n_cells)

    # Read in data
    @views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells],      mpi_root(), mpi_comm())
    @views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells],    mpi_root(), mpi_comm())
    @views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), mpi_comm())
    @views MPI.Bcast!(mesh.tree.levels[1:n_cells],          mpi_root(), mpi_comm())
    @views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells],  mpi_root(), mpi_comm())
  end

  # Partition mesh
  partition!(mesh)

  return mesh
end

function get_restart_mesh_filename(restart_filename, mpi_parallel::Val{true})
  # Get directory name
  dirname, _ = splitdir(restart_filename)

  if mpi_isroot()
    # Read mesh filename from restart file
    mesh_file = ""
    h5open(restart_filename, "r") do file
      mesh_file = read(attrs(file)["mesh_file"])
    end

    buffer = Vector{UInt8}(mesh_file)
    MPI.Bcast!(Ref(length(buffer)), mpi_root(), mpi_comm())
    MPI.Bcast!(buffer, mpi_root(), mpi_comm())
  else # non-root ranks
    count = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())
    buffer = Vector{UInt8}(undef, count[])
    MPI.Bcast!(buffer, mpi_root(), mpi_comm())
    mesh_file = String(buffer)
  end

  # Construct and return filename
  return joinpath(dirname, mesh_file)
end


module ElectronicStructurePySCF

using ElectronicStructure: Atom, Geometry, MolecularData, MolecularSpec
import PyCall
using PyCall: PyObject

export PyMol, PySCF
export to_pyscf, hartree_fock!

## In order to make this a global const it is defined outside the
## try/catch block. See the `PyCall` documentation for this idiom.
const pyscf = PyCall.PyNULL()

function __init__()
    try
        copy!(pyscf, PyCall.pyimport("pyscf"))
    catch
        error("Unable to load python module pyscf.")
    end
end

"""
    to_pyscf(atom::Atom)

Convert `atom` to pyscf geometry string for a single atom (or component).

# Examples
```jldoctest
julia> to_pyscf(Atom(:Li, (0.0, 0.0, 1.4)))
"Li 0.0 0.0 1.4"
```
"""
to_pyscf(atom::Atom) = string(atom.species, " ", join(string.(atom.coords), " "))

"""
    to_pyscf(geom::Geometry)

Convert `geom` to pyscf geometry string, a semi-colon separated list of specifications of
species and their positions.

# Examples
```jldoctest
julia> to_pyscf(Geometry(Atom(:H, (0., 0., 0.)), Atom(:H, (0., 0., 0.7414))))
"H 0.0 0.0 0.0;H 0.0 0.0 0.7414"
```
"""
to_pyscf(geom::Geometry) = join((to_pyscf(atom) for atom in geom), ";")

"""
    to_pyscf(mol_data::MolecularSpec)

Convert `mol_data` to a `pyscf` python `Mol` object, and call the `Mol.build()` method.
"""
function to_pyscf(md::MolecularSpec)
    pymol = pyscf.gto.Mole(atom=to_pyscf(md.geometry), basis=md.basis)
    pymol.spin = md.spin
    pymol.charge = md.charge
    pymol.symmetry = false
    pymol.build()
    return pymol
end

"""
    struct PyMol

Wraps the Python class `pyscf.gto.Mole`.
"""
struct PyMol
    pymol::PyObject

    function PyMol(pymol::PyObject)
        class = PyCall.pytypeof(pymol).__name__
        if class != "Mole"
            throw(ErrorException("`PyMol` expecting python class `Mole`."))
        end
        return new(pymol)
    end
end

"""
    PyMol(md::MolecularSpec)

Create an instance of Python class `pyscf.gto.Mole` and wrap in `PyMol`. This
first converts the `ElectronicStructure`-native `MolecularSpec` type to the corresponding `pyscf`
type, then wraps the resulting Python object in `PyMol`.
"""
PyMol(md::MolecularSpec) = PyMol(to_pyscf(md))

"""
    struct PySCF

Wraps an `scf` calculation object from `pyscf`. The `scf` is the central object
for nearly all calculations available from `pyscf`. You must call `hartree_fock!`
on this object before running other calculations.
"""
struct PySCF
    scf::PyObject
end

function PySCF(pymol::PyMol)
    if pymol.pymol.spin != 0  # from OpenFermion
        pyscf_scf = pyscf.scf.ROHF(pymol.pymol)
    else
        pyscf_scf = pyscf.scf.RHF(pymol.pymol)
    end
    return PySCF(pyscf_scf)
end

"""
    hartree_fock!(scf::PySCF)

Perform Hartee-Fock calculation using the `scf` calculational object.  This method must be
called on `scf` before any other calculations, such as integrals, may be performed.
"""
function hartree_fock!(scf::PySCF)
    verbose = scf.scf.verbose
    scf.scf.verbose = 0
    scf.scf.run()
    scf.scf.verbose = verbose
    return scf
end

## These integrals, etc. are calculated in two places in OpenFermion,
## perhaps and older and newer way of doing it.
## _pyscf_molecular_data.y seems simpler, more organized, and clear
## _run_pyscf.py is more complicated.
## The latter makes a distinction between 1d compressed and not integrals

## Both OpenFermion and qiskit.nature do mo_coeff' * hcore * mo_coeff.
## OpenFermion reshapes this into one_electron, but it really looks like a no-op to me.
## Wait,... In _pyscf_molecular_data.py, they seem to agree with me.
"""
    one_electron_integrals(scf::PySCF)

Compute one-electron MO integrals. This uses pyscf to get AO integrals
and transforms them to MO integrals.
"""
function one_electron_integrals(scf::PySCF)
    mo_coeff = scf.scf.mo_coeff
    hcore = scf.scf.get_hcore()
    return mo_coeff' * hcore * mo_coeff
end

## This is copied from OpenFermion
"""
    two_electron_integrals(pymol::PyMol, scf::PySCF)

Compute two-electron MO integrals. This uses pyscf to get AO integrals
and transforms them to MO integrals. The integrals are indexed in chemists'
order.
"""
function two_electron_integrals(pymol::PyMol, scf::PySCF)
    two_electron_compressed = pyscf.ao2mo.kernel(pymol.pymol, scf.scf.mo_coeff)
    n_orbitals = size(scf.scf.mo_coeff)[2]
    symmetry_code = 1 # No permutation symmetry
    return pyscf.ao2mo.restore(symmetry_code, two_electron_compressed, n_orbitals)
end

## See OpenFermion molecular_data.py
## get_molecular_hamiltonian for how to convert these integrals to InteractionOperator

"""
    MolecularData(::Type{PySCF}, mol_spec::MolecularSpec)

Compute coefficient integrals and energies for molecular Hamiltonian from specification of
problem `mol_spec` using `PySCF`.
The two-body integrals are indexed in chemists' order.
"""
function MolecularData(::Type{PySCF}, mol_spec::MolecularSpec)
    pymol = PyMol(mol_spec)  # Convert MolecularSpec to pyscf equiv.
    scf = PySCF(pymol)       # Create scf calc object.
    hartree_fock!(scf)       # Run basic, neccesary calcs.
    one_e_ints = one_electron_integrals(scf)  # Compute one and two body integrals
    two_e_ints = two_electron_integrals(pymol, scf)
    nuclear_repulsion = pymol.pymol.energy_nuc() # Compute constant energy
    return MolecularData(mol_spec, nuclear_repulsion, one_e_ints, two_e_ints)
end

end


"""
`module  JAC.MultiPhotonIonization`
    ... a submodel of JAC that contains all methods for computing multi-photon (single-electron) ionization cross sections.
"""
module MultiPhotonIonization

    using  ..AngularMomentum, ..Basics, ..Continuum, ..InteractionStrength, ..ManyElectron, ..Radial

    """
    `struct  MultiPhotonIonization.Settings`
        ... defines a type for the settings in estimating multi-photon (single-electron) ionization cross sections

        + multipoles              ::Array{EmMultipole}           ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge}              ... Specifies the gauges to be included into the computations.
        + photonEnergies          ::Array{Float64,1}             ... List of photon energies.
        + printBefore             ::Bool                         ... True, if all energies and lines are printed before their evaluation.
        + lineSelection           ::LineSelection                ... Specifies the selected levels, if any.

    """
    struct Settings
        multipoles                ::Array{EmMultipole}
        gauges                    ::Array{UseGauge}
        photonEnergies            ::Array{Float64,1}
        printBefore               ::Bool
        lineSelection             ::LineSelection
    end

    """
    `MultiPhotonIonization.Settings()`  ... constructor for the default values of multi-photon (single-electron) ionization estimates.
    """
    function Settings()
        Settings(EmMultipole[], UseGauge[], Float64[], false, LineSelection() )
    end

    # `Base.show(io::IO, settings::MultiPhotonIonization.Settings)`
    # 	... prepares a proper printout of the variable settings::MultiPhotonIonization.Settings.
    function Base.show(io::IO, settings::MultiPhotonIonization.Settings)
        println(io, "multipoles:               $(settings.multipoles)  ")
        println(io, "gauges:                   $(settings.gauges)  ")
        println(io, "photonEnergies:           $(settings.photonEnergies)  ")
        println(io, "printBefore:              $(settings.printBefore)  ")
        println(io, "lineSelection:            $(settings.lineSelection)  ")
    end

    """
    `struct  MultiPhotonIonization.Channel`
        ... defines a type for a multi-photon channel to help characterize multi-photon (single-electron)ionization with
            well-defined multipolarities.

        + NoPhotons      ::EmMultipole            ... Number of photons in the multi-photon ionization
        + multipoles     ::Array{EmMultipole,1}   ... Multipoles of all N incoming photons.
        + gauge          ::EmGauge                ... Gauge for dealing with the (coupled) radiation field.
        + kappa          ::Int64                  ... partial-wave of the free electron
        + symmetry       ::LevelSymmetry          ... total angular momentum and parity of the scattering state
        + phase          ::Float64                ... phase of the partial wave
        + amplitude      ::Complex{Float64}       ... multi-photon ionization amplitude associated with the given channel.
   """
    struct  Channel
        NoPhotons        ::EmMultipole
        multipoles       ::Array{EmMultipole,1}
        gauge            ::EmGauge
        kappa            ::Int64
        symmetry         ::LevelSymmetry
        phase            ::Float64
        amplitude        ::Complex{Float64}
    end

    """
    `struct  MultiPhotonIonization.Line`  ... defines a type for a multi-photon ionization line that may include the definition of channels.

        + initialLevel     ::Level                  ... initial-(state) level
        + finalLevel       ::Level                  ... final-(state) level
        + photonEnergy     ::Float64                ... Energy of the incoming photons; all photons are assumed to have equal energy.
        + crossSection     ::EmProperty             ... Cross section for this multi-photon ionization.
        + hasChannels      ::Bool                   ... Determines whether the individual (sub-) channels are defined in terms of their
                                                        multipolarities, etc., or not.
        + channels         ::Array{MultiPhotonIonization.Channel,1}  ... List of MultiPhotonIonization.Channels of this line.
    """
    struct  Line
        initialLevel       ::Level
        finalLevel         ::Level
        photonEnergy       ::Float64
        crossSection       ::EmProperty
        hasChannels        ::Bool
        channels           ::Array{MultiPhotonIonization.Channel,1}
    end

    # `Base.show(io::IO, line::MultiPhotonIonization.Line)`  ... prepares a proper printout of the variable line::MultiPhotonIonization.Line.
    function Base.show(io::IO, line::MultiPhotonIonization.Line)
        println(io, "initialLevel:      $(line.initialLevel)  ")
        println(io, "finalLevel:        $(line.finalLevel)  ")
        println(io, "photonEnergy:      $(line.photonEnergy)  ")
        println(io, "crossSection:      $(line.crossSection)  ")
        println(io, "hasChannels:       $(line.hasChannels)  ")
        println(io, "channels:          $(line.channels)  ")
    end

    """
    `MultiPhotonIonization.computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                                              settings::MultiPhotonIonization.Settings; output=true)`
        ... to compute the multiphoton transition amplitudes and all properties as requested by the given settings.
            A list of lines::Array{MultiPhotonIonization.Lines} is returned.
    """
    function  computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                           settings::MultiPhotonIonization.Settings; output=true)
        println("")
        printstyled("MultiPhotonIonization.computeLines(): The computation of multi-photon ionization amplitudes starts now ... \n", color=:light_green)
        printstyled("---------------------------------------------------- ----------------------------------------------------- \n", color=:light_green)
        println("")
        #
        #
        pathways = "Not yet implemented !"
        #
        if    output    return( pathways )
        else            return( nothing )
        end
    end

    # Inlcude one-electron code
    include("module-MultiPhotonIonization-inc-one-electron.jl")

end # module