module Approximators

using Random
using StatsBase: mean, pweights

using Ramnet.Models
using Ramnet.Encoders

include("PolicyApproximators/BinaryActionPolicy.jl")
export BinaryActionPolicy

include("PolicyApproximators/RegularizedBinaryPolicy.jl")
export RegularizedBinaryPolicy

include("PolicyApproximators/AdaptiveBinaryPolicy.jl")
export AdaptiveBinaryPolicy

include("PolicyApproximators/DiscreteActionPolicy.jl")
export DiscreteActionPolicy

include("PolicyApproximators/ContinousActionPolicy.jl")
export ContinousActionPolicy

include("PolicyApproximators/RegularizedContinousPolicy.jl")
export RegularizedContinousPolicy

include("PolicyApproximators/AltContinousActionPolicy.jl")
export AltContinousActionPolicy

include("PolicyApproximators/AdaptiveContinousPolicy.jl")
export AdaptiveContinousPolicy

include("ValueApproximators/VDiscriminator.jl")
export VDiscriminator

include("ValueApproximators/QDiscriminator.jl")
export QDiscriminator

struct DiscreteQDiscriminator{O <: AbstractVector,A <: Real,E <: AbstractEncoder}
    action_range::UnitRange{A}
    discount::Float64
    encoder::E
    Q::Dict{A,RegressionDiscriminator}

    # TODO: It's annoying to have to specify the input size. It requires knowledge
    #       of the encoder's resolution and the length of the state of the environment.
    #       Maybe it would be better to have a type that specifies the characteristics
    #       of the environment, such as action and observation spaces.
    function DiscreteQDiscriminator{O,A,E}(action_range::UnitRange{A}, size::Int, n::Int, discount::Float64, encoder::E) where {O <: AbstractVector,A <: Real,E <: AbstractEncoder}
        n < 1 && throw(DomainError(n, "Tuple size must be at least 1"))
        size < n && throw(DomainError(size, "Input size may not be smaller then tuple size"))
        !(0 ≤ discount ≤ 1) && throw(DomainError(discount, "Discount must lie in the [0,1] interval"))

        Q = Dict(a => RegressionDiscriminator(size, n; γ=discount) for a in action_range)

        new(action_range, discount, encoder, Q)
    end
end

# TODO: Properly check if observation and action are valid
function (M::DiscreteQDiscriminator{O,A,E})(observation::O, action::A) where {O <: AbstractVector,A <: Real,E <: AbstractEncoder}
    return predict(M.Q[action], encode(M.encoder, observation))
end

# TODO: Properly check if observation is valid
function q_values!(M::DiscreteQDiscriminator{O,A,E}, observation::O, q_values::Vector{Float64}) where {O <: AbstractVector,A <: Real,E <: AbstractEncoder}
    length(q_values) != length(M.action_range) && throw(
      DimensionMismatch("Expected vector of length $(length(M.action_range)), got $(length(q_values))"))

    q_max = typemin(Float64)
    q = 0.0
    encoded_obs = encode(M.encoder, observation) # TODO: Don't have to create a bunch of temp arrays here
    for (i, action) in Iterators.zip(eachindex(q_values), M.action_range)
        q = predict(M.QQ[action], encoded_obs)
        dest[i] = q
        q_max = q > q_max ? q : q_max
    end

    return q_max
end

function (M::DiscreteQDiscriminator{O,A,E})(observation::O) where {O <: AbstractVector,A <: Real,E <: AbstractEncoder}
    values = similar(M.action_range, Float64)
    q_values!(M, observation, values)

return values
end

function expected_q_value(M::DiscreteQDiscriminator{O,A,E}, observation::O; ϵ::Float64=0.0) where {O <: AbstractVector,A <: Real,E <: AbstractEncoder}
    values = similar(M.action_range, Float64)
    q_max = q_values!(M, observation, values)

    return mean(values, pweights((values .== q_max) .+ ϵ))
end

# function select_greedy_action(M::DiscreteQDiscriminator{O,A,E}, observation, rng::MersenneTwister; ϵ::Float64=0.0 ) where {A <: AbstractAgent}
#     # TODO: I could avoid creating these temporary arrays here
#     values = similar(M.action_range, Float64)
#     q_max = q_values!(M, observation, values)

#     if rand(agent.rng) < ϵ # Random action taken with ϵ probability
#         agent.action = rand(agent.rng, agent.actions)
#     else
#       # Selecting action that maximizes Q̂, with ties broken randomly
#         agent.action = sample(agent.rng, agent.actions, pweights(values .== q_max))
#     end

#     return agent.action
# end

end


module Rivers

using Unitful
using Unitful: m, s, km, yr
import Unitful: uconvert
using Distances: Haversine
using NearestNeighbors: BruteTree, knn
using OceanGrids
import OceanGrids: regrid

# These river discharge volumetric flow rates and locations were taken from the Dai and Trenberth dataset
# located on UCAR's Research Data Archive (RDA)
# (URL: https://rda.ucar.edu/datasets/ds551.0/index.html#!description)
# and were converted to Julia/AIBECS with permission from Aiguo Dai.
#
# This module only contains the annual mean river discharge of the 200 largest rivers, taken specifically from files `runoff-table-A.txt` and `runoff-table2-top50r.txt` of the original dataset.
#
# Then I copied these files here and wrote them up into a Julia format by hand
# because it was the most straightforward approach for me here...

struct River{T}
    name::String
    lon::Float64
    lat::Float64
    VFR::T # volumetric flow rate
end

# Show method for rivers
function Base.show(io::IO, r::River)
    lat, lon, name, v = r.lat, r.lon, r.name, r.VFR
    print(io, name, " ", (lat,lon), " ", round(Int, ustrip(v)) * unit(v))
end
Base.show(io::IO, m::MIME"text/plain", r::River) = show(io, r)

const RIVERS = [
    River("Amazon",                          -55.5,  -2.0, 6642.0km^3/yr),
    River("Congo",                            15.3,  -4.3, 1308.0km^3/yr),
    River("Orinoco",                         -63.6,   8.1, 1129.0km^3/yr),
    River("Changjiang",                      117.6,  30.8,  944.0km^3/yr),
    River("Brahmaputra",                      89.7,  25.2,  628.0km^3/yr),
    River("Mississippi",                     -90.9,  32.3,  610.0km^3/yr),
    River("Yenisey",                          86.5,  67.4,  599.0km^3/yr),
    River("Paraná",                          -60.7, -32.7,  568.0km^3/yr),
    River("Lena",                            127.4,  70.7,  531.0km^3/yr),
    River("Mekong",                          105.8,  15.1,  525.0km^3/yr),
    River("Tocantins",                       -49.7,  -3.8,  511.0km^3/yr),
    River("Tapajos",                         -56.8,  -5.2,  415.0km^3/yr),
    River("Ob",                               66.6,  66.6,  412.0km^3/yr),
    River("Ganges",                           88.1,  24.5,  404.0km^3/yr),
    River("Irrawaddy",                        96.0,  21.9,  393.0km^3/yr),
    River("St Lawrence",                     -74.7,  45.0,  363.0km^3/yr),
    River("Amur",                            137.0,  50.5,  354.0km^3/yr),
    River("Xingu",                           -52.2,  -3.2,  302.0km^3/yr),
    River("Mackenzie",                      -133.7,  67.5,  290.0km^3/yr),
    River("Xijiang",                         111.3,  23.5,  270.0km^3/yr),
    River("Columbia",                       -121.2,  45.6,  252.0km^3/yr),
    River("Magdalena",                       -74.9,  10.2,  231.0km^3/yr),
    River("Uruguay",                         -58.0, -31.4,  228.0km^3/yr),
    River("Yukon",                          -162.9,  61.9,  212.0km^3/yr),
    River("Atrato",                          -76.7,   6.2,  204.0km^3/yr),
    River("Danube",                           28.7,  45.2,  202.0km^3/yr),
    River("Niger",                             3.5,  11.9,  193.0km^3/yr),
    River("Ogooué",                           10.2,  -0.7,  186.0km^3/yr),
    River("Essequibo",                       -58.6,   5.8,  154.0km^3/yr),
    River("Fraser",                         -121.4,  49.4,  144.0km^3/yr),
    River("Pechora",                          52.2,  67.6,  140.0km^3/yr),
    River("Nelson",                          -97.9,  54.8,  126.0km^3/yr),
    River("Khatanga",                        102.5,  72.0,  124.0km^3/yr),
    River("Sepik",                           142.2,  -4.2,  123.0km^3/yr),
    River("Kolyma",                          158.7,  68.7,  118.0km^3/yr),
    River("Zambeze",                          33.6, -16.1,  117.0km^3/yr),
    River("Severnaya Dvina",                  41.9,  64.1,  112.0km^3/yr),
    River("Indus",                            68.3,  25.4,  104.0km^3/yr),
    River("Sanaga",                           10.1,   3.8,   99.0km^3/yr),
    River("Godavari",                         81.8,  16.9,   97.0km^3/yr),
    River("Rajang",                          112.9,   2.0,   93.0km^3/yr),
    River("Sao Francisco",                   -37.0, -10.0,   90.0km^3/yr),
    River("Usumacinta",                      -91.5,  17.4,   89.0km^3/yr),
    River("Maroni",                          -54.5,   5.0,   86.0km^3/yr),
    River("Rhine",                             6.1,  51.8,   75.0km^3/yr),
    River("Purari",                          145.1,  -7.0,   74.0km^3/yr),
    River("Caniapiscau",                     -69.2,  57.4,   73.0km^3/yr),
    River("Mahanadi",                         84.0,  20.8,   73.0km^3/yr),
    River("Sacramento",                     -121.5,  38.6,   69.0km^3/yr),
    River("Jacui",                           -51.5, -30.0,   69.0km^3/yr),
    River("San Juan CO",                     -77.2,   4.2,   65.0km^3/yr),
    River("Bénoué",                           13.4,   9.3,   64.0km^3/yr),
    River("Kuskokwim",                      -158.1,  61.9,   57.0km^3/yr),
    River("Albany",                          -83.9,  51.3,   57.0km^3/yr),
    River("Huai",                            117.4,  32.9,   57.0km^3/yr),
    River("La Grande",                       -78.6,  53.7,   56.0km^3/yr),
    River("Tigris (Dicle)",                   44.4,  33.3,   56.0km^3/yr),
    River("Krishna",                          80.6,  16.5,   55.0km^3/yr),
    River("Ottawa",                          -76.2,  45.5,   55.0km^3/yr),
    River("Chindwin",                         95.7,  26.0,   55.0km^3/yr),
    River("Taz",                              82.3,  66.6,   55.0km^3/yr),
    River("Po",                               11.6,  44.9,   55.0km^3/yr),
    River("Courantyne",                      -57.7,   5.0,   54.0km^3/yr),
    River("Indigirka",                       147.5,  69.6,   54.0km^3/yr),
    River("Rhone",                             4.6,  43.8,   54.0km^3/yr),
    River("Saguenay",                        -71.6,  48.6,   53.0km^3/yr),
    River("Alabama",                         -87.5,  31.5,   51.0km^3/yr),
    River("Stikine",                        -132.1,  56.7,   51.0km^3/yr),
    River("Eastmain",                        -78.1,  52.2,   49.0km^3/yr),
    River("Brahmani",                         85.0,  21.5,   48.0km^3/yr),
    River("Dnepr",                            35.2,  47.9,   47.0km^3/yr),
    River("Huanghe",                         114.9,  35.2,   47.0km^3/yr),
    River("Jamanxim",                        -55.8,  -5.5,   47.0km^3/yr),
    River("Susquehanna",                     -76.9,  40.2,   46.0km^3/yr),
    River("Beijiang",                        112.9,  23.6,   46.0km^3/yr),
    River("Don",                              40.7,  47.5,   45.0km^3/yr),
    River("Susitna",                        -150.5,  61.5,   45.0km^3/yr),
    River("Narmada",                          73.7,  21.9,   44.0km^3/yr),
    River("Santa Cruz",                      -71.9, -50.3,   41.0km^3/yr),
    River("Oyapock (Oia)",                   -51.9,   3.8,   40.0km^3/yr),
    River("George",                          -65.8,  58.2,   40.0km^3/yr),
    River("Rufiji",                           37.9,  -7.8,   40.0km^3/yr),
    River("Nile",                             31.3,  29.7,   40.0km^3/yr),
    River("Nottaway",                        -77.4,  50.1,   39.0km^3/yr),
    River("Volta",                             0.1,   6.2,   37.0km^3/yr),
    River("Willamette",                     -123.0,  44.9,   37.0km^3/yr),
    River("Skeena",                         -128.4,  54.6,   36.0km^3/yr),
    River("Garonne",                           0.2,  44.4,   36.0km^3/yr),
    River("Red",                             -92.4,  31.3,   36.0km^3/yr),
    River("Pahang",                          102.4,   3.4,   35.0km^3/yr),
    River("Moose",                           -81.3,  50.8,   34.0km^3/yr),
    River("Chao Phraya",                     100.1,  15.3,   34.0km^3/yr),
    River("Cuyuni",                          -58.8,   6.4,   34.0km^3/yr),
    River("Vistula (Wisła)",                  18.8,  54.1,   34.0km^3/yr),
    River("Copper",                         -144.5,  61.5,   34.0km^3/yr),
    River("Saint John",                      -66.8,  46.0,   34.0km^3/yr),
    River("Olenek",                          123.7,  71.9,   34.0km^3/yr),
    River("Rupert",                          -76.9,  51.5,   33.0km^3/yr),
    River("Vuoksi",                           28.8,  61.2,   33.0km^3/yr),
    River("Kamchatka",                       161.6,  56.3,   33.0km^3/yr),
    River("Manicouagan",                     -68.3,  49.2,   33.0km^3/yr),
    River("Esmeraldas",                      -79.4,   0.5,   32.0km^3/yr),
    River("Doce",                            -40.1, -19.4,   32.0km^3/yr),
    River("Yana",                            136.1,  70.8,   32.0km^3/yr),
    River("Jari",                            -52.6,  -0.6,   32.0km^3/yr),
    River("Bío Bío",                         -73.1, -36.8,   32.0km^3/yr),
    River("Severn-CA",                       -88.3,  55.4,   32.0km^3/yr),
    River("Nushagak",                       -157.5,  59.3,   31.0km^3/yr),
    River("Tsiribihina",                      45.0, -19.7,   31.0km^3/yr),
    River("Baker",                           -72.9, -47.3,   30.0km^3/yr),
    River("Araguari",                        -51.4,   0.7,   30.0km^3/yr),
    River("Pur",                              78.2,  67.0,   29.0km^3/yr),
    River("Nass",                           -129.1,  55.2,   29.0km^3/yr),
    River("Douro (Duero)",                    -7.8,  41.2,   29.0km^3/yr),
    River("Negro-AR",                        -63.7, -40.5,   29.0km^3/yr),
    River("Kouilou",                          12.1,  -4.1,   29.0km^3/yr),
    River("Loire",                            -0.8,  47.4,   28.0km^3/yr),
    River("Puelo",                           -72.2, -41.5,   28.0km^3/yr),
    River("Dongjiang",                       114.3,  23.2,   27.0km^3/yr),
    River("Tista",                            89.5,  25.8,   27.0km^3/yr),
    River("Elbe (Labe)",                      11.8,  53.0,   27.0km^3/yr),
    River("Alsek",                          -138.1,  59.4,   27.0km^3/yr),
    River("Tombigbee",                       -87.9,  32.5,   27.0km^3/yr),
    River("Paraiba do Sul",                  -41.3, -21.8,   26.0km^3/yr),
    River("Parnaiba",                        -42.4,  -3.5,   26.0km^3/yr),
    River("Karun",                            48.7,  31.3,   25.0km^3/yr),
    River("Curua-2",                         -54.5,  -5.6,   25.0km^3/yr),
    River("Mezen",                            45.6,  65.0,   25.0km^3/yr),
    River("Kinabatangan",                    117.6,   5.3,   25.0km^3/yr),
    River("Saint-Maurice",                   -72.7,  46.6,   24.0km^3/yr),
    River("Nyanga",                           10.7,  -2.7,   23.0km^3/yr),
    River("Capim",                           -47.8,  -2.5,   23.0km^3/yr),
    River("Grijalva",                        -93.2,  18.0,   22.0km^3/yr),
    River("Senegal",                         -15.5,  16.5,   22.0km^3/yr),
    River("Papaloapan",                      -96.1,  18.2,   22.0km^3/yr),
    River("Euphrates (F",                     44.3,  32.7,   21.0km^3/yr),
    River("Glomma (Glåma)",                   11.1,  59.6,   21.0km^3/yr),
    River("Apalachicola",                    -84.9,  30.7,   20.0km^3/yr),
    River("Nadym",                            72.7,  65.6,   20.0km^3/yr),
    River("Odra (Oder)",                      14.3,  52.8,   20.0km^3/yr),
    River("Daugava (Západnaya Dviná)",        25.9,  56.5,   19.0km^3/yr),
    River("Hayes, MB",                       -92.8,  56.4,   19.0km^3/yr),
    River("Baleine (Great Whale River)",     -77.0,  55.2,   19.0km^3/yr),
    River("Mangoky",                          43.9, -21.8,   19.0km^3/yr),
    River("Feuilles (Rivière aux Feuilles)", -70.4,  58.6,   19.0km^3/yr),
    River("Melezes (Rivière aux Mélèzes)",   -69.6,  57.7,   19.0km^3/yr),
    River("Churchill",                       -94.6,  58.1,   18.0km^3/yr),
    River("Kelantan",                        102.2,   5.8,   18.0km^3/yr),
    River("Clutha",                          169.7, -46.2,   17.0km^3/yr),
    River("Cross",                             9.3,   5.8,   17.0km^3/yr),
    River("Kemi",                             24.7,  65.9,   17.0km^3/yr),
    River("Göta",                             12.4,  58.4,   17.0km^3/yr),
    River("Han",                             127.0,  37.5,   17.0km^3/yr),
    River("Back",                            -96.5,  66.1,   17.0km^3/yr),
    River("Onega",                            38.5,  63.8,   16.0km^3/yr),
    River("Tapi (Tapti)",                     72.9,  21.3,   16.0km^3/yr),
    River("Anabar",                          114.1,  72.0,   16.0km^3/yr),
    River("Jequitinhonh",                    -39.5, -15.9,   14.0km^3/yr),
    River("Kuban",                            38.2,  45.2,   14.0km^3/yr),
    River("Pánuco",                          -98.6,  22.0,   14.0km^3/yr),
    River("Limpopo",                          33.0, -24.5,   14.0km^3/yr),
    River("Winisk",                          -87.2,  54.5,   13.0km^3/yr),
    River("Ebro",                              0.5,  40.8,   13.0km^3/yr),
    River("Narva",                            28.2,  59.4,   12.0km^3/yr),
    River("Colorado-AR",                    -114.6,  32.7,   12.0km^3/yr),
    River("Seine",                             1.2,  49.3,   11.0km^3/yr),
    River("Coopermine",                     -115.4,  67.7,   11.0km^3/yr),
    River("Dnestr",                           29.5,  46.8,   11.0km^3/yr),
    River("Tagus (Tejo)",                     -8.4,  39.5,   11.0km^3/yr),
    River("Itapecuru",                       -44.4,  -3.6,    9.9km^3/yr),
    River("Burdekin",                        147.2, -19.8,   10.0km^3/yr),
    River("Murray",                          142.8, -34.6,    9.4km^3/yr),
    River("Bolshoy Anyuy",                   161.2,  68.2,    8.4km^3/yr),
    River("Grande de Santiago",             -105.1,  21.8,    8.4km^3/yr),
    River("Bandama",                          -4.8,   5.9,    8.3km^3/yr),
    River("Cauvery",                          78.8,  10.8,    7.7km^3/yr),
    River("Juba",                             42.5,   3.8,    7.5km^3/yr),
    River("Fitzroy-QX",                      150.1, -23.1,    7.4km^3/yr),
    River("Brazos",                          -95.8,  29.6,    7.1km^3/yr),
    River("Comoé (Komoé)",                    -3.7,   6.6,    6.8km^3/yr),
    River("Jaguaribe",                       -38.2,  -5.2,    6.6km^3/yr),
    River("Daly",                            130.7, -13.8,    6.3km^3/yr),
    River("Ouémé",                             2.5,   6.9,    5.4km^3/yr),
    River("Guadiana",                         -7.6,  37.8,    5.2km^3/yr),
    River("Anderson",                       -128.4,  68.6,    5.0km^3/yr),
    River("Victoria",                        130.9, -15.5,    4.9km^3/yr),
    River("Roper",                           134.4, -14.7,    4.8km^3/yr),
    River("Liao",                            123.5,  42.2,    4.6km^3/yr),
    River("Orange (Senqu)",                   17.6, -28.8,    4.6km^3/yr),
    River("Paraguacu",                       -39.0, -12.6,    4.3km^3/yr),
    River("Contas [de]",                     -39.3, -14.3,    3.4km^3/yr),
    River("Guadalquivir",                     -6.0,  37.5,    3.3km^3/yr),
    River("Yaqui",                          -109.5,  29.2,    3.3km^3/yr),
    River("Colorado-TX",                     -96.1,  29.3,    2.6km^3/yr),
    River("Rio Grande",                      -97.4,  25.9,    1.5km^3/yr),
    River("de Grey",                         119.2, -20.3,    1.4km^3/yr),
    River("Gascoyne",                        113.8, -24.8,    0.7km^3/yr),
    River("Avon",                            116.1, -31.8,    0.7km^3/yr),
    River("Fortescue",                       116.2, -21.3,    0.3km^3/yr),
    River("Murchison",                       114.5, -27.9,    0.2km^3/yr)
]

citation() = """
- For the dataset itself: Dai, A. 2017. Dai and Trenberth Global River Flow and Continental Discharge Dataset. Research Data Archive at the National Center for Atmospheric Research, Computational and Information Systems Laboratory. https://doi.org/10.5065/D6V69H1T.
- For the formal publication that describe the dataset: Dai, A., and K. E. Trenberth, 2002: Estimates of freshwater discharge from continents: Latitudinal and seasonal variations. J. Hydrometeorol., 3, 660–687.
- For descriptions of updates to the dataset:
    - Dai, A., T. Qian, K. E. Trenberth, and J. D Milliman, 2009: Changes in continental freshwater discharge from 1948-2004. J. Climate, 22, 2773–2791.
    - Dai, A., 2016: Historical and future changes in streamflow and continental runoff: A review, in Terrestrial Water Cycle and Climate Change: Natural and Human-Induced Impacts, Geophysical Monograph 221. Ed. Qiuhong Tang and Taikan Oki, AGU, John Wiley & Sons, 17–37 (DOI: 10.1002/9781118971772).
"""

uconvert(u, r::River) = River(r.name, r.lon, r.lat, uconvert(u, r.VFR))

function load(unit=m^3/s)
    @info """You are about to use the Dai and Trenberth river discharge dataset.
          If you use it for research, please cite:

          $(citation())

          You can find the corresponding BibTeX entries in the CITATION.bib file
          at the root of the AIBECS.jl package repository.
          (Look for the "Dai_2017", "Dai_Trenberth_2002", "Dai_etal_2009", and "Dai_2016" keys.)
          """
    return uconvert.(unit, RIVERS)
end

function regrid(R::Vector{River{T}}, grd) where T <: Quantity
    lats = [r.lat for r in R]
    lons = [r.lon for r in R]
    depths = zeros(length(R))
    vs = [r.VFR for r in R]
    return regrid(vs, lats, lons, depths, grd)
end
export regrid

end # module

export Rivers


module VisionModel

using Flux, Images

# Models
include("alexnet.jl")
include("vgg.jl")

end


# Copyright 2017 Stephen L. Smith and Frank Imeson
# Modified copyright 2020 A. Kudryavtsev and M. Khachay
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

module PCGLNS
export solver

using Random

include("utilities.jl")
include("parse_print.jl")
include("tour_optimizations.jl")
include("adaptive_powers.jl")
include("insertion_deletion.jl")
include("parameter_defaults.jl")

const global EPS = 1.0e9

"""
Main GTSP solver, which takes as input a problem instance and
some optional arguments.
"""
function solver(problem_instance; args...)

    # Read problem data and solver settings.
    num_vertices, num_sets, sets, set_orderings, dist, membership, start_set =
        read_file(problem_instance)
    param = parameter_settings(num_vertices, num_sets, sets, problem_instance, args)

    order_constraints = calc_order_constraints(sets, set_orderings)

    init_time = time()
    count = Dict(
        :latest_improvement => 1,
        :first_improvement => false,
        :warm_trial => 0,
        :cold_trial => 1,
        :total_iter => 0,
        :print_time => init_time,
    )
    lowest = Tour(Int64[], typemax(Int64))
    start_time = time_ns()
    best_fount_time = 0.0::Float64

    # Compute set distances which will be helpful.
    setdist = set_vertex_dist(dist, num_sets, membership)
    powers = initialize_powers(param)
    dummy_power = Power("", 0.0, Dict(), Dict(), Dict())

    nthreads = max(1, Threads.nthreads())
    threads_trials = Array{TrialResult, 1}(undef, nthreads)

    RNGs = Array{Random.MersenneTwister, 1}(undef, nthreads)
    resize!(RNGs, nthreads)
    @sync if param[:seed] != -1
        Threads.@threads for i in 1:nthreads
            RNGs[Threads.threadid()] = Random.MersenneTwister(param[:seed] + i)
        end
    else
        Threads.@threads for i in 1:nthreads
            RNGs[Threads.threadid()] = Random.MersenneTwister()
        end
    end

    while count[:cold_trial] <= param[:cold_trials]

        # Build tour from scratch on a cold restart.
        best = initial_tour!(
            RNGs,
            lowest,
            dist,
            sets,
            order_constraints,
            setdist,
            count[:cold_trial],
            membership,
            param,
            start_set,
        )

        # Print_cold_trial(count, param, best).
        phase = :early

        if count[:cold_trial] == 1
            powers = initialize_powers(param)
        else
            power_update!(powers, param)
        end

        while count[:warm_trial] <= param[:warm_trials]
            iter_count = 1
            current = Tour(copy(best.tour), best.cost)
            temperature = 1.442 * param[:accept_percentage] * best.cost

            # Accept a solution with 50% higher cost with 0.05% change after num_iterations.
            cooling_rate =
                (
                    (0.0005 * lowest.cost) / (param[:accept_percentage] * current.cost)
                )^(1 / param[:num_iterations])

            # If warm restart, then use lower temperature.
            if count[:warm_trial] > 0
                temperature *= cooling_rate^(param[:num_iterations] / 2)
                phase = :late
            end

            while count[:latest_improvement] <= (
                count[:first_improvement] ? param[:latest_improvement] :
                param[:first_improvement]
            )
                # Move to mid phase after half iterations.
                if iter_count > param[:num_iterations] / 2 && phase == :early
                    phase = :mid
                end

                trial_result = TrialResult(
                    Tour(Int64[], typemax(Int64)),
                    dummy_power,
                    dummy_power,
                    dummy_power,
                    0.0,
                )
                @sync Threads.@threads for i in 1:nthreads
                    thread_trial, insertion, removal, noise, score = remove_insert(
                        RNGs,
                        current,
                        best,
                        dist,
                        membership,
                        setdist,
                        sets,
                        order_constraints,
                        powers,
                        param,
                        phase,
                        start_set,
                    )
                    threads_trials[Threads.threadid()] =
                        TrialResult(thread_trial, insertion, removal, noise, score)
                end

                for i in 1:nthreads
                    if threads_trials[i].tour.cost < trial_result.tour.cost
                        trial_result = threads_trials[i]
                    end
                end

                # Update power scores.
                trial_result.insertion.scores[phase] += trial_result.score
                trial_result.insertion.count[phase] += 1
                trial_result.removal.scores[phase] += trial_result.score
                trial_result.removal.count[phase] += 1
                trial_result.noise.scores[phase] += trial_result.score
                trial_result.noise.count[phase] += 1

                # Decide whether or not to accept trial.
                if accepttrial_noparam(
                    RNGs,
                    trial_result.tour.cost,
                    current.cost,
                    param[:prob_accept],
                ) || accepttrial(RNGs, trial_result.tour.cost, current.cost, temperature)
                    param[:mode] == "slow" && opt_cycle!(
                        RNGs,
                        current,
                        dist,
                        sets,
                        order_constraints,
                        membership,
                        param,
                        setdist,
                        "full",
                        start_set,
                    )
                    current = trial_result.tour
                end
                if current.cost < best.cost
                    count[:latest_improvement] = 1
                    count[:first_improvement] = true
                    if count[:cold_trial] > 1 && count[:warm_trial] > 1
                        count[:warm_trial] = 1
                    end
                    opt_cycle!(
                        RNGs,
                        current,
                        dist,
                        sets,
                        order_constraints,
                        membership,
                        param,
                        setdist,
                        "full",
                        start_set,
                    )
                    best = current
                    if lowest.cost > best.cost
                        best_fount_time = (time_ns() - start_time) / EPS
                    end
                else
                    count[:latest_improvement] += 1
                end

                # If we've come in under budget, or we're out of time, then exit.
                if best.cost <= param[:budget] || time() - init_time > param[:max_time]
                    param[:timeout] = (time() - init_time > param[:max_time])
                    param[:budget_met] = (best.cost <= param[:budget])
                    timer = (time_ns() - start_time) / EPS
                    lowest.cost > best.cost && (lowest = best)
                    print_best(count, param, best, lowest, init_time)
                    print_summary(lowest, timer, best_fount_time, membership, param)
                    return
                end

                # Cool the temperature.
                temperature *= cooling_rate
                iter_count += 1
                count[:total_iter] += 1
                print_best(count, param, best, lowest, init_time)
            end

            # On the first cold trial, we are just determining.
            print_warm_trial(count, param, best, iter_count)
            count[:warm_trial] += 1
            count[:latest_improvement] = 1
            count[:first_improvement] = false
        end

        lowest.cost > best.cost && (lowest = best)
        count[:warm_trial] = 0
        count[:cold_trial] += 1
    end

    timer = (time_ns() - start_time) / EPS
    print_summary(lowest, timer, best_fount_time, membership, param)
end
end


@testset "Ensemble assembly and nested sampling functions" begin
    ensembledir = randstring()
    spensembledir = randstring()
    distdir = randstring()

    source_pwm = [.7 .1 .1 .1
    .1 .1 .1 .7
    .1 .1 .7 .1]

    source_pwm_2 = [.6 .1 .1 .2
    .2 .1 .1 .6
    .1 .2 .6 .1]

    src_length_limits=2:12
    no_sources=3

    source_priors = assemble_source_priors(no_sources, [source_pwm, source_pwm_2])
    mix_prior=.5

    bg_scores = log.(fill(.1, (30,4)))
    obs=[BioSequences.LongSequence{DNAAlphabet{2}}("CCGTTGACGATGTGATGAATAATGAAAGAA")
    BioSequences.LongSequence{DNAAlphabet{2}}("CCCCGATGATGACCGTTGACCAGATGGATG")
    BioSequences.LongSequence{DNAAlphabet{2}}("CCCCGATGATGACCCCGATTTTGAAAAAAA")
    BioSequences.LongSequence{DNAAlphabet{2}}("TCATCATGCTGATGATGAATCAGATGAAAG")
    ]

    order_seqs = BioBackgroundModels.get_order_n_seqs(obs, 0)
    coded_seqs = BioBackgroundModels.code_seqs(order_seqs)
    obs=Array(transpose(coded_seqs))

    ensemble = IPM_Ensemble(ensembledir, 150, source_priors, (falses(0,0),mix_prior), bg_scores, obs, src_length_limits)
    ensemble = IPM_Ensemble(ensembledir, 200, source_priors, (falses(0,0),mix_prior), bg_scores, obs, src_length_limits) #test resumption

    sp_ensemble = IPM_Ensemble(spensembledir, 200, source_priors, (falses(0,0),mix_prior), bg_scores, obs, src_length_limits, posterior_switch=true)

    @test length(ensemble.models) == 200
    for model in ensemble.models
        @test -350 < model.log_Li < -150
    end

    @test length(sp_ensemble.models) == 200
    for model in sp_ensemble.models
        @test -350 < model.log_Li < -150
    end

    assembler=addprocs(1)

    @everywhere using BioMotifInference

    dist_ensemble=IPM_Ensemble(assembler, distdir, 150, source_priors, (falses(0,0),mix_prior), bg_scores, obs, src_length_limits)
    dist_ensemble=IPM_Ensemble(assembler, distdir, 200, source_priors, (falses(0,0),mix_prior), bg_scores, obs, src_length_limits) #test resumption

    @test length(dist_ensemble.models) == 200
    for model in ensemble.models
        @test -350 < model.log_Li < -150
    end

    rmprocs(assembler)
    rm(distdir, recursive=true)

    models_to_permute = 600
    funclimit=200
    funcvec=full_perm_funcvec

    instruct = Permute_Instruct(funcvec, ones(length(funcvec))./length(funcvec),models_to_permute,200, min_clmps=fill(.02,length(funcvec)))

    @info "Testing convergence displays..."
    sp_logZ = converge_ensemble!(sp_ensemble, instruct, converge_factor=500.,  wk_disp=true, tuning_disp=true, ens_disp=true, conv_plot=true, src_disp=true, lh_disp=true, liwi_disp=true, max_iterates=50)

    sp_ensemble=reset_ensemble!(sp_ensemble)

    @info "Testing threaded convergence..."
    sp_logZ = converge_ensemble!(sp_ensemble, instruct,  converge_factor=500.,wk_disp=false, tuning_disp=false, ens_disp=false, conv_plot=false, src_disp=false, lh_disp=false, liwi_disp=false, backup=(true, 150), max_iterates=300)

    @info "Testing resumption..."
    sp_logZ = converge_ensemble!(sp_ensemble, instruct,  converge_factor=500.,wk_disp=false, tuning_disp=false, ens_disp=false, conv_plot=false, src_disp=false, lh_disp=false, liwi_disp=false, backup=(true, 150))
    @test length(sp_ensemble.models) == 200
    @test length(sp_ensemble.log_Li) == length(sp_ensemble.log_Xi) == length(sp_ensemble.log_wi) == length(sp_ensemble.log_Liwi) == length(sp_ensemble.log_Zi) == length(sp_ensemble.Hi) == sp_ensemble.model_counter-200
    for i in 1:length(sp_ensemble.log_Li)-1
        @test sp_ensemble.log_Li[i] <= sp_ensemble.log_Li[i+1]
    end
    for i in 1:length(sp_ensemble.log_Zi)-1
        @test sp_ensemble.log_Zi[i] <= sp_ensemble.log_Zi[i+1]
    end
    @test sp_logZ > -1500.0

    @info "Testing multiprocess convergence..."
    @info "Spawning worker pool..."
    worker_pool=addprocs(2, topology=:master_worker)
    @everywhere using BioMotifInference

    ####CONVERGE############
    final_logZ = converge_ensemble!(ensemble, instruct, worker_pool,  converge_factor=500., wk_disp=false, tuning_disp=false, ens_disp=false, conv_plot=false, src_disp=false, lh_disp=false, liwi_disp=false, backup=(true,500), clean=(true, 500, 1000))

    convits=length(ensemble.log_Li)

    #test converging already converged, wih different converge criterion
    final_logZ = converge_ensemble!(ensemble, instruct, worker_pool,  converge_factor=150., converge_criterion="compression", wk_disp=false, tuning_disp=false, ens_disp=false, conv_plot=false, src_disp=false, lh_disp=false, liwi_disp=false, backup=(true,500), clean=(true, 500, 1000))

    length(ensemble.log_Li)==convits

    rmprocs(worker_pool)

    @test length(ensemble.models) == 200
    @test length(ensemble.log_Li) == length(ensemble.log_Xi) == length(ensemble.log_wi) == length(ensemble.log_Liwi) == length(ensemble.log_Zi) == length(ensemble.Hi) == ensemble.model_counter-200
    for i in 1:length(ensemble.log_Li)-1
        @test ensemble.log_Li[i] <= ensemble.log_Li[i+1]
    end
    for i in 1:length(ensemble.log_Zi)-1
        @test ensemble.log_Zi[i] <= ensemble.log_Zi[i+1]
    end
    @test typeof(final_logZ) == Float64
    @test final_logZ > -1500.0

    rm(ensembledir, recursive=true)
    rm(spensembledir, recursive=true)
end


module BaytesMCMC

################################################################################
#Import modules
using BaytesCore:
    BaytesCore,
    AbstractAlgorithm,
    AbstractTune,
    AbstractConfiguration,
    AbstractDiagnostics,
    AbstractKernel,
    AbstractConstructor,
    Updater,
    Iterator,
    UpdateBool,
    UpdateTrue,
    UpdateFalse,
    logsumexp,
    logaddexp,
    logmeanexp,
    update,
    AcceptStatistic,
    SampleDefault,
    to_Tuple

import BaytesCore:
    BaytesCore,
    update,
    update!,
    infer,
    results,
    init,
    init!,
    propose,
    propose!,
    propagate,
    propagate!,
    result!,
    get_result,
    generate_showvalues

using ModelWrappers:
    ModelWrappers,
    ModelWrapper,
    Tagged,
    Objective,
    DiffObjective,
    AbstractDifferentiableTune,
    ℓObjectiveResult,
    ℓDensityResult,
    ℓGradientResult,
    checkfinite,
    AutomaticDiffTune,
    AnalyticalDiffTune,
    sample,
    sample!

import ModelWrappers: ModelWrappers, predict, generate, checkfinite

using Random: Random, AbstractRNG, GLOBAL_RNG, randexp
using LinearAlgebra:
    LinearAlgebra, Symmetric, Diagonal, diag, cholesky, inv, UniformScaling, dot, det, I
using Statistics: Statistics, mean, median, std, sqrt, quantile, var, cor, cov, middle
using Distributions: Distributions, logpdf, MvNormal

using DocStringExtensions:
    DocStringExtensions, TYPEDEF, TYPEDFIELDS, FIELDS, SIGNATURES, FUNCTIONNAME
using ArgCheck: ArgCheck, @argcheck
using UnPack: UnPack, @unpack, @pack!

################################################################################
#Abstract types to be dispatched in Examples section
abstract type MCMCKernel <: AbstractKernel end
abstract type MCMCKernelDiagnostics <: AbstractDiagnostics end
function get_acceptrate() end
function move() end

include("Core/Core.jl")
include("Kernels/Kernels.jl")

################################################################################
export
    # BaytesCore
    UpdateBool,
    UpdateTrue,
    UpdateFalse,
    propose,
    propose!,
    propagate,
    propagate!,
    results,
    update!,
    SampleDefault,

    # ModelWrappers
    checkfinite,
    init,
    init!,
    predict,
    generate,

    #MCMC
    MCMCKernel,
    MCMCKernelDiagnostics,
    MCMCDiagnostics,
    get_acceptrate,
    move

end


struct Pose3 <: IncrementalInference.InferenceVariable
  dims::Int
  labels::Vector{String}
  Pose3() = new(6, String["POSE";])
end
struct Point3 <: IncrementalInference.InferenceVariable
  dims::Int
  Point3() = new(3)
end


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

    Module QpcrAnalysis.jl

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

#===============================================================================
    start of module definition >>
===============================================================================#

__precompile__()

# using Base

"""
Bioinformatics module for Chai's open-source Real-Time PCR instrument.
[https://www.chaibio.com](https://www.chaibio.com)
"""
module QpcrAnalysis
    const MODULE_NAME = "QpcrAnalysis"

    # using Clustering, Combinatorics, DataFrames, DataStructures, Dierckx, Ipopt, JLD, JSON,
    # JuMP, MySQL, NamedTuples, DataArrays
    ## `NLopt` needed on BBB but not on PC ("ERROR: LoadError: Declaring __precompile__(false)
    ## is not allowed in files that are being precompiled".
    ## "ERROR: Failed to precompile NLopt to /root/.julia/lib/v0.6/NLopt.ji")
    ## In addition, `HttpServer` for "juliaserver.jl"

    ## Other functions than `include` read files from `pwd()` only instead of also `LOAD_PATH`.
    ## `pwd()` shows the present working directory in module `Main`, instead of the directory
    ## where "QpcrAnalysis.jl" is located. Therefore `LOAD_FROM_DIR` needs to be defined
    ## for those functions to find files in the directory where "QpcrAnalysis.jl" is located.
    const LOAD_FROM_DIR = LOAD_PATH[find(LOAD_PATH) do path_
        isfile("$path_/$MODULE_NAME.jl")
    end][1] ## slice by boolean vector returned a one-element vector. Assumption: LOAD_PATH is global

    ## default data width in production mode:  32 bits (BBB)
    ## default data width in development mode: 64 bits
    const DEVELOPMENT_MODE = "development"
    const PRODUCTION_MODE  = "production"
    const production_env   = (get(ENV, "JULIA_ENV", nothing) == PRODUCTION_MODE)
    ##const production_env = true

    const Float_T = Float64
	const Int_T = (Sys.WORD_SIZE == 32) ? Int32 : Int64
    const NaN_T = NaN64
    const Inf_T = Inf64

#===============================================================================
    include each script, generally in the order of workflow
===============================================================================#

    ## define constants & macros:
    include("defines/keystring_constants.jl")
    include("defines/enums.jl")
    include("defines/macros.jl")

    ## define shared functions:
    include("shared_functions.jl")

    ## define structs and types:
    include("defines/Field.jl")
    include("defines/Input.jl")
    ## calibration
    include("defines/NumberOfChannels.jl")
    include("defines/RawData.jl")
    include("defines/CalibrationData.jl")
    include("defines/CalibrationParameters.jl")
    include("defines/DeconvolutionMatrices.jl")
    ## allelic discrimination
    include("defines/ClusterAnalysisResult.jl")
    include("defines/UniqCombinCenters.jl")
    include("defines/AssignGenosResult.jl")
    ## amplification models
    include("amp_models/defines/AmpModel.jl")
    include("amp_models/defines/AmpModelFit.jl")
    include("amp_models/defines/SFC_model_definitions.jl")
    include("amp_models/defines/SFCModelDef.jl")
    ## generate amplification model definitions
    include("amp_models/generate_SFC_models.jl")
    include("amp_models/MAKx.jl")
    include("amp_models/MAKERGAULx.jl")
    include("amp_models/defines/AmpModelResults.jl")
    ## amplification
    include("amp_models/defines/AmpInput.jl")
    include("amp_models/defines/AmpOutput.jl")
    ## melting curve
    include("defines/McInput.jl")
    include("defines/PeakIndices.jl")
    include("defines/Peak.jl")
    include("defines/McPeakOutput.jl")
    include("defines/McOutput.jl")
    ## standard curve
    include("defines/StandardCurveResult.jl")
    ## thermal consistency
    include("defines/TmCheck1w.jl")
    include("defines/ThermalConsistencyOutput.jl")

    ## this code is hidden from the parser on the BeagleBone
    @static if !production_env
        ## development & testing
        import Base.Test
        using FactCheck
        include("../test/test_functions.jl")
        ## data format verification
        include("../test/verify_request.jl")
        include("../test/verify_response.jl")
    end

    ## define functions:
    ## dispatch
    include("dispatch.jl")
    ## calibration
    include("calibration.jl")
    ## amplification
    include("amplification.jl")
    include("amp_analysis.jl")
    include("amp_fit_model.jl")
    ## melting curve
    include("melting_curve.jl")
    include("mc_analysis.jl")
    include("mc_peak_analysis.jl")
    include("supsmu.jl")
    ## standard curve
    include("standard_curve.jl")
    ## custom analyses
    include("custom_analyses/optical_calibration.jl")
    include("custom_analyses/optical_test_single_channel.jl")
    include("custom_analyses/optical_test_dual_channel.jl")
    include("custom_analyses/thermal_consistency.jl")
    include("custom_analyses/thermal_performance_diagnostic.jl")
    # include("custom_analyses/your_own_analysis.jl")

#===============================================================================
    create logger >>
===============================================================================#

    ## create module level logger
    ## this can be precompiled
    using Memento
    import FactCheck.clear_results
    logger = getlogger(current_module())

#===============================================================================
    runtime initialization >>
===============================================================================#

    ## This function contains stuff that needs to happen
    ## at runtime when the module is loaded
    function __init__()
        ## create log handler
        ## NB changes to the default logger must happen at runtime
        ## otherwise segfaults are liable to occur
        push!(logger,
            DefaultHandler(
                production_env ? "/var/log/julia.log" : "/tmp/julia.log",
                DefaultFormatter("[ {date} | {level} ]: {msg}")))
        ## debug-level logging in production environment (provisional setting)
        production_env  && setlevel!(logger, "debug")
        ## debug-level logging in development environment
        !production_env && setlevel!(logger, "debug")
        ## Register the module level logger at runtime
        ## so it is accessible via `get_logger(QpcrAnalysis)`
        Memento.register(logger)
        #
        ## clear Fact Checks
        !production_env && FactCheck.clear_results()
    end ## __init__()

end ## module QpcrAnalysis

#===============================================================================
    << end of module definition
===============================================================================#

# for precompiling
using HTTP, JSON, Memento
#using Clustering, Combinatorics, DataArrays, DataFrames
#using DataStructures, Dierckx, HDF5, Ipopt, JLD, JSON, JuMP
#using MathProgBase, Nullables, SpecialFunctions, FactCheck, FunctionalData, Match, Syslogs, Memento, HttpCommon, HttpParser, IniFile


using MetaGen
using Gen

num_videos = 2
num_frames = 2

objects_observed = Matrix{Array{Detection2D}}(undef, num_videos, num_frames)
camera_trajectories = Matrix{Camera_Params}(undef, num_videos, num_frames)
for v=1:num_videos
	f = 1

	labels = [2, 5]
	xs = [50.1, 225.5]
	ys = [60.2, 173.1]
	temp = Array{Detection2D}(undef, length(labels))
	for i = 1:length(labels)
		label = labels[i]
		x = xs[i]
		y = ys[i]
		temp[i] = (x, y, label) #so now I have an array of detections
	end
	#turn that array of detections into an array of an array of detections sorted by receptive_field
	#temp_sorted_into_rfs = map(rf -> filter(p -> within(p, rf), temp), receptive_fields)
	objects_observed[v, f] = temp

	c = Camera_Params(camera_location = Coordinate(0.01,0.02,0.001), camera_focus = Coordinate(1.0,1.0,1.0))
	camera_trajectories[v, f] = c

	#################################
	#frame 2
	for f = 2:2

		labels = [2]
		xs = [50.1]
		ys = [60.2]
		temp = Array{Detection2D}(undef, length(labels))
		for i = 1:length(labels)
			label = labels[i]
			x = xs[i]
			y = ys[i]
			temp[i] = (x, y, label) #so now I have an array of detections
		end
		#turn that array of detections into an array of an array of detections sorted by receptive_field
		#temp_sorted_into_rfs = map(rf -> filter(p -> within(p, rf), temp), receptive_fields)
		#objects_observed[v, f] = temp_sorted_into_rfs
		objects_observed[v, f] = temp

		c = Camera_Params(camera_location = Coordinate(0.01,0.02,0.001), camera_focus = Coordinate(1.0,1.0,1.0))
		camera_trajectories[v, f] = c
	end
end

cm = choicemap()
for v = 1:num_videos
	# cm[:videos => v => :v_matrix => :lambda_fa => 1 => :fa] = 0.000001
	# cm[:videos => v => :v_matrix => :lambda_fa => 2 => :fa] = 0.000001
	# cm[:videos => v => :v_matrix => :lambda_fa => 3 => :fa] = 0.000001
	# cm[:videos => v => :v_matrix => :lambda_fa => 4 => :fa] = 0.000001
	# cm[:videos => v => :v_matrix => :lambda_fa => 5 => :fa] = 0.1 #super high false alarm rate for category 5
	# cm[:videos => v => :v_matrix => :miss_rate => 1 => :miss] = 0.000001 #tiny miss rate for category 1
	# cm[:videos => v => :v_matrix => :miss_rate => 2 => :miss] = 0.01
	# cm[:videos => v => :v_matrix => :miss_rate => 3 => :miss] = 0.01
	# cm[:videos => v => :v_matrix => :miss_rate => 4 => :miss] = 0.99 #huge miss rate for category 4
	# cm[:videos => v => :v_matrix => :miss_rate => 5 => :miss] = 0.01
	for f = 1:num_frames
		camera_params = camera_trajectories[v, f]
		cm[:videos => v => :frame_chain => f => :camera => :camera_location_x] = camera_params.camera_location.x
		cm[:videos => v => :frame_chain => f => :camera => :camera_location_y] = camera_params.camera_location.y
		cm[:videos => v => :frame_chain => f => :camera => :camera_location_z] = camera_params.camera_location.z
		cm[:videos => v => :frame_chain => f => :camera => :camera_focus_x] = camera_params.camera_focus.x
		cm[:videos => v => :frame_chain => f => :camera => :camera_focus_y] = camera_params.camera_focus.y
		cm[:videos => v => :frame_chain => f => :camera => :camera_focus_z] = camera_params.camera_focus.z

		#for rf = 1:num_receptive_fields
			#println("objects_observed[v, f][rf] ", objects_observed[v, f][rf])
			#println("type ", typeof(objects_observed[v, f][rf]))
		cm[:videos => v => :frame_chain => f => :observations_2D] = convert(Array{Any, 1}, objects_observed[v, f])
		#end
	end
	#def
end

gt_trace,_ = Gen.generate(main, (num_videos, num_frames), cm);
#println(gt_trace)
gt_choices = get_choices(gt_trace)

#they run fine when main isn't (static)
new_trace,_ = regenerate(gt_trace, select(:videos => 1 => :init_scene));
gt_choices = get_choices(new_trace)
regenerate(gt_trace, select(:videos => 2 => :v_matrix => :lambda_fa => 1 => :fa));


# Fetch wfm
function fetch_wfm(obj::T;ch::String="1") where {T<:NIScope}
	# niScope channels start from 0, so subtract 1
	ch = NIScope_Vertical_Dict[ch]
	y, aux = niScope_Fetch(obj;ch=ch)
	return y, aux.absoluteInitialX, aux.xIncrement
end

# one channel at a time...
function niScope_Fetch(obj::T;ch::String="1") where {T<:NIScope}
#ViStatus niScope_Fetch (ViSession vi, ViConstString channelList, ViReal64 timeout, ViInt32 numSamples, ViReal64* wfm, struct niScope_wfmInfo* wfmInfo);
	chlist  = [UInt8.(collect("$ch")); UInt8(0)] # terminate with NULL char
	timeout = 0.0
	n_samp  = niScope_ActualRecordLength(obj)
	wfmInfo = [niScope_wfmInfo(0.0, 0.0, 0.0, Int32(0), 1.0, 0.0, 0, 0)] # this needs to be an array of structures, as per niScope documentation
	y       = Vector{Cdouble}(undef, n_samp)# arg type is Ref but one needs to pass the actual array
	status  = niScope_Fetch(obj.handle, chlist, timeout, n_samp, y, wfmInfo)
	return y, wfmInfo[1]# return the first wfm in array
end


function hdr_hash(S::SeisData, i::Int64)
  h = hash(zero(UInt64))
  for f in (:id, :name, :loc, :fs, :gain, :resp, :units)
    h = hash(getfield(S, f)[i], h)
  end
  return h
end

function track_hdr!(S::GphysData, hashes::Array{UInt64, 1}, fmt::String, fname::String, opts::String)
  to_track = Array{Int64, 1}(undef, 0)

  # Add new channels
  L = length(hashes)
  if S.n > L
    append!(hashes, zeros(UInt64, S.n-L))
  end

  # Check existing channels
  for i in 1:S.n
    if hdr_hash(S, i) != hashes[i]
      push!(to_track, i)
    end
  end

  # note new meta source in to_track
  note!(S, to_track, string( "+meta ¦ read_meta!(S, \"",
                            fmt, "\", \"",
                            fname, "\", ",
                            opts) )
  return nothing
end


ENV["PKG_CONFIG_PATH"] = "/Users/dc/anaconda/envs/python35/lib/pkgconfig"

using OpenCV
using Images, ImageView
using Cxx

import ImageView

# C++ OpenCV code
cxx"""

#include "opencv2/objdetect.hpp"
#include "opencv2/highgui.hpp"
#include "opencv2/imgproc.hpp"

#include <iostream>
#include <stdio.h>

using namespace std;
using namespace cv;

/// Load cascade file
CascadeClassifier load_face_cascade(String face_cascade_name) {

    CascadeClassifier face_cascade;
    face_cascade.load(face_cascade_name);

    return face_cascade;
}

/// detect and draw a bounding box on the image
void detect_face(cv::Mat frame, CascadeClassifier face_cascade) {

    std::vector<Rect> faces;
    cv::Mat frame_gray;
    std::vector<int>vec(4);

    cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
    equalizeHist( frame_gray, frame_gray );

    face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30) );

    for( size_t i = 0; i < faces.size(); i++ ) {
        Point center( faces[i].x + faces[i].width/2, faces[i].y + faces[i].height/2 );
        ellipse( frame, center, Size( faces[i].width/2, faces[i].height/2), 0, 0, 360, Scalar( 255, 0, 255 ), 4, 8, 0 );
    }
}

/// detect and return bounding box coordinates
std::vector<int> detect_face_coords(cv::Mat frame, CascadeClassifier face_cascade) {

    std::vector<Rect> faces;
    cv::Mat frame_gray;
    std::vector<int>vec(4);

    cvtColor( frame, frame_gray, COLOR_BGR2GRAY );
    equalizeHist( frame_gray, frame_gray );

    face_cascade.detectMultiScale( frame_gray, faces, 1.1, 2, 0|CASCADE_SCALE_IMAGE, Size(30, 30) );

    if (faces.size() > 0) {
        vec[0] = faces[0].x;
        vec[1] = faces[0].width;
        vec[2] = faces[0].y;
        vec[3] = faces[0].height;
    }
    else {
        vec[0] = 0; vec[1] = 0;
        vec[2] = 0; vec[3] = 0;
    }

    return vec;
}

"""

cascade_path = download("https://raw.githubusercontent.com/opencv/opencv/master/data/haarcascades/haarcascade_frontalface_alt.xml")
face_cascade = @cxx load_face_cascade(pointer(cascade_path));

# create a bounding box around face and save results in the root folder
filename = joinpath(pwd(), "sample-images", "beautiful-1274051_640_100_1.jpg");
img_opencv = imread(filename);
@cxx detect_face(img_opencv, face_cascade);
imwrite(joinpath(pwd(), "result.jpg"), img_opencv)

# retrieve coordinates and crop the image
filename = joinpath(pwd(), "sample-images", "beautiful-1274051_640_100_1.jpg");
img_opencv = imread(filename);
img_images = load(filename);

coords_cxx = @cxx detect_face_coords(img_opencv, face_cascade);
coords = map(x -> Int(at(coords_cxx, x)), 0:3);

face = img_images[coords[3]:coords[3] + coords[4], coords[1]:coords[1] + coords[2]]
ImageView.imshow(face)


"""
    setup_dump_tendencies_diagnostics(
        ::ClimateMachineConfigType,
        interval::String,
        out_prefix::String;
        writer = NetCDFWriter(),
        interpol = nothing,
    )

Create the "DumpTendencies" `DiagnosticsGroup` which contains all the
tendencies for the simulation's `BalanceLaw`. These are collected on
an interpolated grid (`interpol` _must_ be specified).

!!! warn
    This diagnostics group can produce a lot of output and is
    very expensive.

!!! warn
    These diagnostics are intended for physics-debugging, and not numerics debugging, because
    `flux` and `source` are called on interpolated data, which may not exactly match the
    non-interpolated evaluation of fluxes and sources that the solver uses.
"""
function setup_dump_tendencies_diagnostics(
    ::ClimateMachineConfigType,
    interval::String,
    out_prefix::String;
    writer = NetCDFWriter(),
    interpol = nothing,
)
    @assert !isnothing(interpol)

    return DiagnosticsGroup(
        "DumpTendencies",
        Diagnostics.dump_tendencies_init,
        Diagnostics.dump_tendencies_fini,
        Diagnostics.dump_tendencies_collect,
        interval,
        out_prefix,
        writer,
        interpol,
    )
end

# helpers for PV and tendency names
params(::T) where {T} = T.parameters
unval(::Val{i}) where {i} = i
param_suffix(p, ::Val{n}) where {n} = string("_", unval(p[1]))
param_suffix(p, ::Val{0}) = ""
param_suffix(p) = param_suffix(p, Val(length(p)))
prog_name(pv::PV) where {PV} = string(nameof(PV), param_suffix(params(pv)))
tend_name(t) = String(nameof(typeof(t)))

function precompute_args(
    bl,
    state,
    aux,
    diffusive,
    hyperdiffusive,
    t,
    direction,
)
    _args_fx1 = (; state, aux, t, direction)
    _args_fx2 = (; state, aux, t, diffusive, hyperdiffusive)
    _args_src = (; state, aux, t, direction, diffusive)

    cache_fx1 = precompute(bl, _args_fx1, Flux{FirstOrder}())
    cache_fx2 = precompute(bl, _args_fx2, Flux{SecondOrder}())
    cache_src = precompute(bl, _args_src, Source())

    args_fx1 = merge(_args_fx1, (; precomputed = cache_fx1))
    args_fx2 = merge(_args_fx2, (; precomputed = cache_fx2))
    args_src = merge(_args_src, (; precomputed = cache_src))

    return (args_fx1, args_fx2, args_src)
end

function dump_tendencies_init(dgngrp, t)
    dg = Settings.dg
    Q = Settings.Q
    FT = eltype(Q)
    bl = Settings.dg.balance_law
    mpicomm = Settings.mpicomm
    mpirank = MPI.Comm_rank(mpicomm)

    if mpirank == 0
        if array_device(Q) isa CPU
            prognostic_data = Q.realdata
            auxiliary_data = dg.state_auxiliary.realdata
            gradient_flux_data = dg.state_gradient_flux.realdata
            hyperdiffusive_data = dg.states_higher_order[2].realdata
        else
            prognostic_data = Array(Q.realdata)
            auxiliary_data = Array(dg.state_auxiliary.realdata)
            gradient_flux_data = Array(dg.state_gradient_flux.realdata)
            hyperdiffusive_data = Array(dg.states_higher_order[2].realdata)
        end

        # get dimensions for the interpolated grid
        dims = dimensions(dgngrp.interpol)
        dim_names = tuple(collect(keys(dims))...)

        state, aux, diffusive, hyperdiffusive = (
            extract_state(dg, prognostic_data, 1, 1, Prognostic()),
            extract_state(dg, auxiliary_data, 1, 1, Auxiliary()),
            extract_state(dg, gradient_flux_data, 1, 1, GradientFlux()),
            extract_state(dg, hyperdiffusive_data, 1, 1, Hyperdiffusive()),
        )
        direction = dg.direction
        args_fx1, args_fx2, args_src = precompute_args(
            bl,
            state,
            aux,
            diffusive,
            hyperdiffusive,
            t,
            direction,
        )

        # set up the variables we're going to be writing
        vars = OrderedDict()
        for (tend_fun, tend_type, tend_args) in (
            (flux, Flux{FirstOrder}(), args_fx1),
            (flux, Flux{SecondOrder}(), args_fx2),
            (source, Source(), args_src),
        )
            for pv in prognostic_vars(bl)
                progname = prog_name(pv)
                for tend in eq_tends(pv, bl, tend_type)
                    flat_vals = flattened_tuple(
                        FlattenArr(),
                        tend_fun(pv, tend, bl, tend_args),
                    )
                    for i in 1:length(flat_vals)
                        varname = "$(progname)_$(tend_name(tend))_$(i)"
                        vars[varname] = (dim_names, FT, Dict())
                    end
                end
            end
        end

        dprefix = @sprintf("%s_%s", dgngrp.out_prefix, dgngrp.name)
        dfilename = joinpath(Settings.output_dir, dprefix)
        noov = Settings.no_overwrite
        init_data(dgngrp.writer, dfilename, noov, dims, vars)
    end

    return nothing
end

function dump_tendencies_collect(dgngrp, t)
    interpol = dgngrp.interpol
    mpicomm = Settings.mpicomm
    dg = Settings.dg
    Q = Settings.Q
    FT = eltype(Q.data)
    bl = dg.balance_law
    mpirank = MPI.Comm_rank(mpicomm)

    istate = similar(Q.data, interpol.Npl, number_states(bl, Prognostic()))
    interpolate_local!(interpol, Q.data, istate)
    iaux = similar(Q.data, interpol.Npl, number_states(bl, Auxiliary()))
    interpolate_local!(interpol, dg.state_auxiliary.data, iaux)
    igf = similar(Q.data, interpol.Npl, number_states(bl, GradientFlux()))
    interpolate_local!(interpol, dg.state_gradient_flux.data, igf)
    ihd = similar(Q.data, interpol.Npl, number_states(bl, Hyperdiffusive()))
    interpolate_local!(interpol, dg.states_higher_order[2].data, ihd)

    if interpol isa InterpolationCubedSphere
        i_ρu = varsindex(vars_state(bl, Prognostic(), FT), :ρu)
        project_cubed_sphere!(interpol, istate, tuple(collect(i_ρu)...))
    end

    all_state_data = accumulate_interpolated_data(mpicomm, interpol, istate)
    all_aux_data = accumulate_interpolated_data(mpicomm, interpol, iaux)
    all_gf_data = accumulate_interpolated_data(mpicomm, interpol, igf)
    all_hd_data = accumulate_interpolated_data(mpicomm, interpol, ihd)

    if mpirank == 0
        varvals = OrderedDict()
        dims = dimensions(dgngrp.interpol)
        direction = dg.direction
        nx, ny, nz = [length(dim[1]) for dim in values(dims)]
        @traverse_interpolated_grid nx ny nz begin
            vars_view(st, data) =
                Vars{vars_state(bl, st, FT)}(view(data, lo, la, le, :))
            statei = vars_view(Prognostic(), all_state_data)
            auxi = vars_view(Auxiliary(), all_aux_data)
            diffusivei = vars_view(GradientFlux(), all_gf_data)
            hyperdiffusivei = vars_view(Hyperdiffusive(), all_hd_data)

            args_fx1, args_fx2, args_src = precompute_args(
                bl,
                statei,
                auxi,
                diffusivei,
                hyperdiffusivei,
                t,
                direction,
            )

            for (tend_fun, tend_type, tend_args) in (
                (flux, Flux{FirstOrder}(), args_fx1),
                (flux, Flux{SecondOrder}(), args_fx2),
                (source, Source(), args_src),
            )
                for pv in prognostic_vars(bl)
                    progname = prog_name(pv)
                    for tend in eq_tends(pv, bl, tend_type)
                        flat_vals = flattened_tuple(
                            FlattenArr(),
                            tend_fun(pv, tend, bl, tend_args),
                        )
                        for i in 1:length(flat_vals)
                            varname = "$(progname)_$(tend_name(tend))_$(i)"
                            vals = get!(
                                varvals,
                                varname,
                                Array{FT}(undef, nx, ny, nz),
                            )
                            vals[lo, la, le] = flat_vals[i]
                        end
                    end
                end
            end

        end

        append_data(dgngrp.writer, varvals, t)
    end

    MPI.Barrier(mpicomm)
    return nothing
end

function dump_tendencies_fini(dgngrp, t) end


using Glob: glob
using Random: shuffle
using Distributed: procs, addprocs
using Distributed: @everywhere

if length(procs()) < Sys.CPU_THREADS-2
    addprocs(Sys.CPU_THREADS-2) ;println("$(length(procs())) cores running.") ;end
@everywhere include("gru_api(seq_to_vec).jl") ; @everywhere include("utils.jl")

const learning_rate = .01
const hm_epochs     = 50

const in_size       = 52
const layers        = [52]
const out_size      = 52

create_model_definitions(layers)

const model = Model(mk_layers(in_size, layers, out_size)...)

const data = [[[randn(1, in_size) for i in 1:rand(1:16)], softmax(randn(1, out_size))] for _ in 1:100] # const data = import_data(glob("class*.txt")[1])

for i in 1:hm_epochs
    print("epoch: $i ")
    train!(model, shuffle(data), learning_rate)
    end
end

save_model(model)

print("Hit enter to continue..")
chomp(readline())


module HostCPUFeatures

using Libdl, Static
using Static: Zero, One, lt, gt
using IfElse: ifelse

using BitTwiddlingConvenienceFunctions: prevpow2, nextpow2, intlog2

export has_feature, fma_fast, pick_vector_width, pick_vector_width_shift, register_count,
  register_size, simd_integer_register_size

include("cpu_info.jl")
if (Sys.ARCH === :x86_64) || (Sys.ARCH === :i686)
    include("cpu_info_x86.jl")
elseif Sys.ARCH === :aarch64
    include("cpu_info_aarch64.jl")
else
    include("cpu_info_generic.jl")
end
include("pick_vector_width.jl")

unwrap(::Val{S}) where {S} = S
unwrap(::StaticInt{S}) where {S} = S
unwrap(::StaticFloat64{S}) where {S} = S
unwrap(::StaticSymbol{S}) where {S} = S

@noinline function redefine()
  @debug "Defining CPU name."
  define_cpu_name()

  reset_features!()
  reset_extra_features!()
end
const BASELINE_CPU_NAME = (Sys.CPU_NAME)::String
function __init__()
  ccall(:jl_generating_output, Cint, ()) == 1 && return
  BASELINE_CPU_NAME == Sys.CPU_NAME::String || redefine()
  return nothing
end

end


# Main SimString search algorithm

"""
    search(measure::AbstractSimilarityMeasure, db_collection::AbstractSimStringDB, query::AbstractString;
        α=0.7, ranked=true)

Search for strings in a string collection using the SimString algorithm and a
similarity measure.

# Arguments:
* `measure`::AbstractSimilarityMeasure - The similarity measure to use.
* `db_collection`::AbstractSimStringDB - The database collection to search.
* `query`::AbstractString - The query string to search for.
* `α`::float - The α parameter for the SimString algorithm.
* `ranked`::Boolean - Whether to return the results in ranked order.

# Example
```julia
db = DictDB(CharacterNGrams(2, " "));
append!(db, ["foo", "bar", "fooo"]);

search(Dice(), db, "foo"; α=0.8, ranked=true)
# 2-element Vector{Tuple{String, Float64}}:
#  ("foo", 1.0)
#  ("fooo", 0.8888888888888888)
```

# Returns
* A Vector of results, where each element is a Tuple of the form (`string`, `similarity measure score`).
"""
function search(measure::AbstractSimilarityMeasure, db_collection::AbstractSimStringDB, query::AbstractString; α=0.7, ranked=true)
    return search!(measure, db_collection, query; α=α, ranked=ranked)
end

"""
Internal function which ranks the results of a search using the specified similarity measure.
"""
function rank_search_results(measure::AbstractSimilarityMeasure, db_collection::DictDB, query, results; ranked=true)
    features = extract_features(db_collection.feature_extractor, query)

    # Compute similarity scores for each result
    ranked_results = map(results) do x
        x, similarity_score(measure, features, extract_features(db_collection.feature_extractor, x) )
    end

    # Sort by similarity score and return
    return ranked ? sort(ranked_results, by = i -> i[2], rev=true) : ranked_results
end

"""
Internal function which performs the overlap join
"""
function overlap_join(db_collection::AbstractSimStringDB, features, τ, candidate_size)
    # length of features
    query_feature_length = length(features)

    # Sort features from the most uncommon and the most common
    features = sort(features, by = i -> length(lookup_feature_set_by_size_feature(db_collection, candidate_size, i) ) )

    # Count the occurrences of each feature
    candidate_match_counts = DefaultDict{String, Int}(0)
    feature_slice_index = query_feature_length - τ + 1
    idx = query_feature_length - τ
    focus_features = feature_slice_index < 0 ? (@view features[0:end + feature_slice_index]) : (@view features[0:idx])

    @inbounds @views for i in focus_features
        for s in lookup_feature_set_by_size_feature(db_collection, candidate_size, i)
            candidate_match_counts[s] += 1
        end
    end

    results = String[]

    for (candidate, match_count) in candidate_match_counts
        for i in (query_feature_length - τ + 1) : query_feature_length
            if candidate in lookup_feature_set_by_size_feature(db_collection, candidate_size, features[i])
                match_count += 1
            end

            if match_count >= τ
                append!(results, [candidate])
                break
            end

            remaining_count = query_feature_length - i - 1
            if (match_count + remaining_count) < τ
                break
            end
        end
    end
    return results
end

"""
Search for strings in custom DictDB string collection using the SimString algorithm
and a similarity measure.
"""
function search!(measure::AbstractSimilarityMeasure, db_collection::DictDB, query::AbstractString; α=0.7, ranked=true)
    # Generate features from query string
    features = extract_features(db_collection.feature_extractor, query)

    results = String[]

    # Generate and return results from the potential candidate size pool
    @inbounds for candidate_size in minimum_feature_size(measure, length(features), α) : maximum_feature_size(measure, db_collection, length(features), α)
        # Minimum overlap
        τ = minimum_overlap(measure, length(features), candidate_size, α)

        # Generate approximate candidates from the overlap join
        append!(results, overlap_join(db_collection, features, τ, candidate_size))
    end

    # Rank search results
    return rank_search_results(measure, db_collection, query, results; ranked=ranked)
end


struct OptimizerStats
    simulation_step::Int
    execution_index::Int
    objective_value::Float64
    termination_status::Int
    primal_status::Int
    dual_status::Int
    solver_solve_time::Float64
    timed_solve_time::Float64
    solve_bytes_alloc::Union{Nothing, Float64}
    sec_in_gc::Union{Nothing, Float64}
end

_SIMULATION_FIELDS = Set((:simulation_step, :execution_index))
_BASE_FIELDS = setdiff(fieldnames(OptimizerStats), _SIMULATION_FIELDS)

"""
Construct OptimizerStats when the OperationsProblem is part of a simulation.
"""
function OptimizerStats(problem, simulation_step)
    timed_log = get_solve_timed_log(problem)
    execution_index = get_execution_count(problem)
    model = get_jump_model(problem)

    return OptimizerStats(
        simulation_step,
        execution_index,
        JuMP.objective_value(model),
        Int(JuMP.termination_status(model)),
        Int(JuMP.primal_status(model)),
        Int(JuMP.dual_status(model)),
        _get_solver_time(model),
        timed_log[:timed_solve_time],
        timed_log[:solve_bytes_alloc],
        timed_log[:sec_in_gc],
    )
end

"""
Construct OptimizerStats when the OperationsProblem is not part of a simulation.
"""
function OptimizerStats(problem)
    timed_log = get_solve_timed_log(problem)
    model = get_jump_model(problem)

    return OptimizerStats(
        0,
        0,
        JuMP.objective_value(model),
        Int(JuMP.termination_status(model)),
        Int(JuMP.primal_status(model)),
        Int(JuMP.dual_status(model)),
        _get_solver_time(model),
        timed_log[:timed_solve_time],
        timed_log[:solve_bytes_alloc],
        timed_log[:sec_in_gc],
    )
end

"""
Construct OptimizerStats from a vector that was serialized to HDF5.
"""
function OptimizerStats(data::Vector{Float64})
    return OptimizerStats(data...)
end

"""
Convert OptimizerStats to an array of floats that can be serialized to HDF5.
"""
function to_array(stats::OptimizerStats)
    return [Float64(getfield(stats, x)) for x in fieldnames(OptimizerStats)]
end

function to_dataframe(stats::OptimizerStats)
    df = DataFrames.DataFrame([to_namedtuple(stats)])
    if !_part_of_simulation(stats)
        DataFrames.select!(df, _BASE_FIELDS)
    end

    return df
end

function to_dict(stats::OptimizerStats)
    data = Dict()
    for field in fieldnames(typeof(stats))
        !_part_of_simulation(stats) && field in _SIMULATION_FIELDS && continue
        data[String(field)] = getfield(stats, field)
    end

    return data
end

function _get_solver_time(model)
    solver_solve_time = NaN
    try
        solver_solve_time = MOI.get(model, MOI.SolveTime())
    catch
        @warn "SolveTime() property not supported by the Solver"
    end

    return solver_solve_time
end

function get_column_names(::Type{OptimizerStats})
    return collect(string.(fieldnames(OptimizerStats)))
end

_part_of_simulation(stats::OptimizerStats) = stats.simulation_step != 0


using JLD
include("Transformation.jl")
include("AbstractSystem.jl")
include("Tree.jl")
include("Evaluation.jl")
include("BackPropogation.jl")
include("Facility.jl")
import Base.push!

type Axiom
    tree1 :: Tree
    tree2 :: Tree
    index :: Dict
end

function Base.show(io :: IO, m :: Axiom)
    print(io, "Axiom[")
    print(io, m.tree1)
    print(io, ", ")
    print(io, m.tree2)
    print(io, "]")
end

function beautify(m :: Axiom)
    string("Axiom:\n", beautify(m.tree1, 1), "\n", beautify(m.tree2, 1), "\n")
end

Axiom(tree1 :: Tree, tree2 :: Tree) = Axiom(tree1, tree2, add!(index(tree1), index(tree2)))

Axiom(skeleton1, skeleton2) = Axiom(toTree(skeleton1), toTree(skeleton2))

function push!(index :: Dict, ops :: Array)
    n = length(ops)
    for i in 1:n
        for t in index[i]
            t.op = ops[i]
        end
    end
end

function push!(index :: Dict, ops :: Dict)
    for key in keys(ops)
        if haskey(index, key)
            ts = index[key]
            for t in ts
                t.op = ops[key]
            end
        end
    end
end

function push!(axiom :: Axiom, ops)
    push!(axiom.index, variables)
end

function init_axiom!(axiom :: Axiom, variables)
    push!(axiom.index, variables)
    init_tree!(axiom.tree1)
    init_tree!(axiom.tree2)
end

init_axioms! = distribute(init_axiom!)

## add some loss function to deal with degenerating problem?

function loss(a, b)
    b - a
end

function train_axiom!(axiom :: Axiom, variables, n = 1, randomize = identity)
    push!(axiom.index, variables)
    d1 :: Array{Float64, 1} = axiom.tree1.value[:d]
    d2 :: Array{Float64, 1} = axiom.tree2.value[:d]
    v1 :: Array{Float64, 1} = axiom.tree1.value[:value]
    v2 :: Array{Float64, 1} = axiom.tree2.value[:value]
    for i in 1:n
        randomize(variables)
        push!(axiom.index, variables)
        eval_tree!(axiom.tree2)
        eval_tree!(axiom.tree1)
        for j in 1:length(d1)
            d1[j] = loss(v1[j], v2[j])
            d2[j] = loss(v2[j], v1[j])
        end
        bp_tree!(axiom.tree1)
        bp_tree!(axiom.tree2)
    end
end

train_axioms! = distribute(train_axiom!)

## To prevent degeneration problem, we use anti-training to deal with the problem.
## The current method to deal with degeneration is anti-training: to push random evaluation results far away,
## but the defect now is that it is unstable in long term.
## One possible remedy is to anti-train only when the random evaluation results are "too near".

const DefaultThreshold = 0.1

function anti_train_axiom!(axiom :: Axiom, n = 1, randomize = identity, thres = DefaultThreshold)
    d1 :: Array{Float64, 1} = axiom.tree1.value[:d]
    d2 :: Array{Float64, 1} = axiom.tree2.value[:d]
    v1 :: Array{Float64, 1} = axiom.tree1.value[:value]
    v2 :: Array{Float64, 1} = axiom.tree2.value[:value]
    for i in 1:n
        randomize(axiom.tree1)
        randomize(axiom.tree2)
        eval_tree!(axiom.tree2)
        eval_tree!(axiom.tree1)
        if mean((v1 - v2) .^ 2) < thres
            for j in 1:length(d1)
                d1[j] = - loss(v1[j], v2[j])
                d2[j] = - loss(v2[j], v1[j])
            end
            bp_tree!(axiom.tree1)
            bp_tree!(axiom.tree2)
        end
    end
end

anti_train_axioms! = distribute(anti_train_axiom!)

include("DataStructs.jl")

UChar = Class("Char", 8)
Variable = Class("Variable", 100)
Stream = List(UChar, Variable, [:v, :u, :v1])

Prog = Class("Prog", 100)
Bindingc = Class("Binding", 100)
Binding = Pair(Variable, Prog, Bindingc, [:b, :v, :p])

Framec = Class("Frame", 100)
Frame = List(Bindingc, Framec, [:f1, :b, :f2])

Envc = Class("Env", 200)
Env = List(Framec, Envc, [:e1, :f, :e2])

lookup = DFunction("lookup", [Envc, Variable], Prog)
set = DFunction("set", [Envc, Variable, Prog], Envc)
add_binding = DFunction("add_binding", [Framec, Variable, Prog], Framec)
def = DFunction("def", [Envc, Variable, Prog], Envc)
extend = DFunction("extend", [Envc, Variable, Prog], Envc)

var = DFunction("var", [Variable], Prog)
definition = DFunction("definition", [Variable, Prog], Prog)
assignment = DFunction("assignment", [Variable, Prog], Prog)
procedure = DFunction("procedure", [Variable, Prog], Prog)
func_call = DFunction("func_call", [Prog, Prog], Prog)
Seq = List(Prog, Prog)

f_eval = DFunction("f_eval", [Prog, Envc], Prog)
s_eval = DFunction("eval", [Prog, Envc], Envc)

@load "infinity.jld"

Interpreter = Dict()
I_names = Dict()
I_names[:base] = [:UChar, :Variable, :Stream, :Prog, :Bindingc, :Binding, :Framec, :Frame, :Envc, :Env]
I_names[:env] = [:lookup, :set, :add_binding, :def, :extend]
I_names[:prog] = [:var, :definition, :assignment, :procedure, :func_call, :Seq]
I_names[:repl] = [:f_eval, :s_eval]
inject(I_names, Interpreter)
Interpreter

axiom_lookup1 = Axiom([lookup, [Env.cons, [Frame.cons, [Binding.pair, :v, :p], :f], :e], :v], :p)
axiom_lookup2 = Axiom([lookup, [Env.cons, [Frame.cons, [Binding.pair, :v1, :p1], :f2], :e2], :v2],
                      [lookup, [Env.cons, :f2, :e2], :v2])
axiom_lookup3 = Axiom([lookup, [Env.cons, Frame.empty, :e], :v], [lookup, :e, :v])

axiom_set1 = Axiom([set, [Env.cons, [Frame.cons, [Binding.pair, :v1, :p1], :f2], :e2], :v1, :p2],
                   [Env.cons, [Frame.cons, [Binding.pair, :v1, :p2], :f2], :e2])
axiom_set2 = Axiom([set, [Env.cons, [Frame.cons, [Binding.pair, :v1, :p1], :f2], :e2], :v2, :p2],
                   [Env.cons, [Frame.cons, [Binding.pair, :v1, :p1],
                                           [Env.first, [set, [Env.cons, :f2, :e2], :v2, :p2]]],
                              [Env.rest, [set, [Env.cons, :f2, :e2], :v2, :p2]]])
axiom_set3 = Axiom([set, [Env.cons, Frame.empty, :e], :v, :p], [Env.cons, Frame.empty, [set, :e, :v, :p]])

axiom_add_binding = Axiom([add_binding, :f, :v, :p], [Frame.cons, [Binding.pair, :v, :p], :f])
axiom_def = Axiom([def, :e, :v, :p], [Env.first!, :e, [add_binding, [Env.first, :e], :v, :p]])
axiom_extend = Axiom([extend, :e, :v, :p], [def, [Env.cons, Frame.empty, :e], :v, :p])

axiom_var_s = Axiom([s_eval, [var, :v], :e], :e)
axiom_var_f = Axiom([f_eval, [var, :v], :e], [lookup, :e, :v])
axiom_definition_s = Axiom([s_eval, [definition, :v, :p], :e], [def, [s_eval, :p, :e], :v, [f_eval, :p, :e]])
axiom_definition_f = Axiom([f_eval, [definition, :v, :p], :e], [f_eval, :p, :e])
axiom_assignment_s = Axiom([s_eval, [assignment, :v, :p], :e], [set, [s_eval, :p, :e], :v, [f_eval, :p, :e]])
axiom_assignment_f = Axiom([f_eval, [assignment, :v, :p], :e], [f_eval, :p, :e])
axiom_proc_s = Axiom([s_eval, [procedure, :v, :p], :e], :e)
axiom_proc_f = Axiom([f_eval, [procedure, :v, :p], :e], [procedure, :v, :p])
axiom_func_s1 = Axiom([s_eval, [func_call, :p1, :p2], :e],
                      [s_eval, [func_call, [f_eval, :p1, :e], [f_eval, :p2, :e]], :e])
axiom_func_f1 = Axiom([f_eval, [func_call, :p1, :p2], :e],
                      [f_eval, [func_call, [f_eval, :p1, :e], [f_eval, :p2, :e]], :e])
axiom_func_s2 = Axiom([s_eval, [func_call, [procedure, :v, :p1], :p2], :e],
                      [Env.rest, [s_eval, :p1, [extend, :e, :v, :p2]]])
axiom_func_f2 = Axiom([f_eval, [func_call, [procedure, :v, :p1], :p2], :e], [f_eval, :p1, [extend, :e, :v, :p2]])
axiom_seq_s = Axiom([s_eval, [Seq.cons, :p1, :p2], :e], [s_eval, :p2, [s_eval, :p1, :e]])
axiom_seq_f = Axiom([f_eval, [Seq.cons, :p1, :p2], :e], [f_eval, :p2, [s_eval, :p1, :e]])

axioms = Dict()
axioms_lookup = [axiom_lookup1, axiom_lookup2, axiom_lookup3]
axioms_set = [axiom_set1, axiom_set2, axiom_set3]
axioms[:base_env] = [Stream.axioms, Binding.axioms, Frame.axioms, Env.axioms]
axioms[:env] = [axioms_lookup, axioms_set, axiom_add_binding, axiom_def, axiom_extend]
axioms_v = [axiom_var_s, axiom_var_f]
axioms_d = [axiom_definition_s, axiom_definition_f]
axioms_a = [axiom_assignment_s, axiom_assignment_f]
axioms_p = [axiom_proc_s, axiom_proc_f]
axioms_f = [axiom_func_s1, axiom_func_f1, axiom_func_s2, axiom_func_f2]
axioms_s = [axiom_seq_s, axiom_seq_f]
axioms[:prog] = [axioms_v, axioms_d, axioms_a, axioms_p, axioms_f, axioms_s];

## The Repl facility, note that for the program structure parsing, we rely on the julia parser;
## and we also need to read the variable name from string.
include("Repl.jl")
r = REPL(Env.empty, Seq.empty)

function object_init(dict :: Dict, name, num :: Int64, class :: Class)
    dict[Symbol(name, num)] = Object(string(name, num), class)
end

function object_init(dict :: Dict, name, class :: Class)
    dict[Symbol(name)] = Object(string(name), class)
end

function object_init(dict :: Dict, name_dict :: Dict, num :: Int64)
    for k in keys(name_dict)
        object_init(dict, k, name_dict[k])
        for i in 1:num
            object_init(dict, k, i, name_dict[k])
        end
    end
end

function object_init(dict :: Dict, name_dict :: Dict)
    for k in keys(name_dict)
        object_init(dict, k, name_dict[k])
    end
end

type Memory
    class :: Class
    mclass :: Class

    encode :: DFunction
    decode :: DFunction

    axioms :: Array{Axiom, 1}
end

function Base.show(io :: IO, m :: Memory)
    print(io, string("Module.Memory(", m.class.class_name, "->", m.mclass.class_name, ")"))
end

function Memory(class :: Class, mclass :: Class, symbols = [:s, :m])
    encode = DFunction("encode", [class], mclass)
    decode = DFunction("decode", [mclass], class)

    sym = symbols[1]
    msym = symbols[2]
    axiom1 = Axiom([decode, [encode, sym]], sym)
    axiom2 = Axiom([encode, [decode, msym]], msym)
    axioms = [axiom1, axiom2]

    Memory(class, mclass, encode, decode, axioms)
end

function Memory(class :: Class, type_len :: Int64, symbols = [:s, :m])
    memory_name = string("Memory", "_", class.class_name)
    mclass = Class(memory_name, type_len)
    Memory(class, mclass, symbols)
end

## @time l = List(Sensor, 70)

naive_randomize = distribute(function(o :: Object) randn!(o.value) end)

function another_naive_randomize(tree :: Tree)
    foreach(another_naive_randomize, tree.subtrees)
    if typeof(tree.op) == Object
        randn!(tree.op.value)
    end
    tree
end

# UChar = Class("Char", 8)
# Variable = Class("Variable", 100)
# Prog = Class("Prog", 100)
# Bindingc = Class("Binding", 100)
# Binding = Pair(Variable, Prog, Bindingc, [:b, :v, :p])
# Framec = Class("Frame", 100)
# Frame = List(Bindingc, Framec, [:f1, :b, :f2])
# Envc = Class("Env", 200)
# Env = List(Framec, Envc, [:e1, :f, :e2])

dict = Dict()
ndict = Dict()
ndict[:u] = UChar
ndict[:v] = Variable
ndict[:p] = Prog
ndict[:b] = Bindingc
ndict[:f] = Framec
ndict[:e] = Envc
object_init(dict, ndict, 2)

init_axioms!(axioms, dict)
while true
    anti_train_axioms!(axioms, 1000, another_naive_randomize)
    train_axioms!(axioms, dict, 10000, naive_randomize)
    save("infinity.jld", Interpreter)
end


# This file is a part of Julia. License is MIT: https://julialang.org/license

export LAPACKException,
       ARPACKException,
       SingularException,
       PosDefException,
       RankDeficientException

struct LAPACKException <: Exception
    info::BlasInt
end

struct ARPACKException <: Exception
    info::BlasInt
end

function Base.showerror(io::IO, ex::ARPACKException)
    print(io, "ARPACKException: ")
    if ex.info == -8
        print(io, "error return from calculation of a real Schur form.")
    elseif ex.info == -9
        print(io, "error return from calculation of eigenvectors.")
    elseif ex.info == -14
        print(io, string("did not find any eigenvalues to sufficient accuracy. ",
            "Try with a different starting vector or more Lanczos vectors ",
            "by increasing the value of ncv."))
    else
        print(io, "unspecified ARPACK error: $(ex.info)")
    end
end

struct SingularException <: Exception
    info::BlasInt
end

struct PosDefException <: Exception
    info::BlasInt
end
function Base.showerror(io::IO, ex::PosDefException)
    print(io, "PosDefException: matrix is not ")
    if ex.info == -1
        print(io, "Hermitian")
    else
        print(io, "positive definite")
    end
    print(io, "; Cholesky factorization failed.")
end

struct RankDeficientException <: Exception
    info::BlasInt
end


function EpochDerivativesEquations(arg0::JString, arg1::NumericalPropagator)
    return EpochDerivativesEquations((JString, NumericalPropagator), arg0, arg1)
end

function compute_derivatives(obj::EpochDerivativesEquations, arg0::SpacecraftState, arg1::Vector{jdouble})
    return jcall(obj, "computeDerivatives", Vector{jdouble}, (SpacecraftState, Vector{jdouble}), arg0, arg1)
end

function get_mapper(obj::EpochDerivativesEquations)
    return jcall(obj, "getMapper", AbsoluteJacobiansMapper, ())
end

function get_name(obj::EpochDerivativesEquations)
    return jcall(obj, "getName", JString, ())
end

function get_selected_parameters(obj::EpochDerivativesEquations)
    return jcall(obj, "getSelectedParameters", ParameterDriversList, ())
end

function set_initial_jacobians(obj::EpochDerivativesEquations, arg0::SpacecraftState)
    return jcall(obj, "setInitialJacobians", SpacecraftState, (SpacecraftState,), arg0)
end

function set_initial_jacobians(obj::EpochDerivativesEquations, arg0::SpacecraftState, arg1::Vector{Vector{jdouble}}, arg2::Vector{Vector{jdouble}})
    return jcall(obj, "setInitialJacobians", SpacecraftState, (SpacecraftState, Vector{Vector{jdouble}}, Vector{Vector{jdouble}}), arg0, arg1, arg2)
end


__precompile__()
module SeisDownload

include("utils.jl")
include("downloadfunc.jl")

using .Utils
using .DownloadFunc

using SeisIO, Dates, Printf, JLD2, FileIO, Distributed

#------------------------------------------------------------------#
#For the time being, we need remove_response function from obspy
#This will be replaced by SeisIO modules in the near future.
#Please activate obspy enviroment before launching Julia.
include("remove_response_obspy.jl")
using .Remove_response_obspy
#------------------------------------------------------------------#

export seisdownload

"""
    seisdownload(InputDict::Dict; MAX_MEM_PER_CPU::Float64=1.0)

    Request seismic data and save into jld2.
# Arguments
- `InputDict`    : dictionary which contains request information
- `MAX_MEM_PER_CPU` : maximum available memory for 1 cpu (unit:GB) (default = 1.0GB)
"""
function seisdownload(InputDict::Dict; MAX_MEM_PER_CPU::Float64=1.0)

    Utils.initlogo()

	DownloadType    = InputDict["DownloadType"]

    if DownloadType == "Noise" || DownloadType == "noise"

		#stationlist
		stationlist     = InputDict["stationinfo"]["stationlist"]
		starttime       = InputDict["starttime"]
		endtime         = InputDict["endtime"]
		DL_time_unit    = InputDict["DL_time_unit"]
		DownloadType    = InputDict["DownloadType"]
		fopath          = InputDict["fopath"]

		if mod((endtime - starttime).value,  DL_time_unit) != 0 || (endtime - starttime).value < DL_time_unit
			error("Total download time cannot be devided by Download Time unit; this may cause unexpected result. abort.")
		end

		# calculate start time list (starttimelist) with each Donwload_time_unit
		starttimelist = Utils.get_starttimelist(starttime, endtime, DL_time_unit)
		# generate DLtimestamplist and ststationlist
		DLtimestamplist = Utils.get_timestamplist(starttimelist)

		InputDict["starttimelist"] = starttimelist
		InputDict["DLtimestamplist"] = DLtimestamplist

		#save info into jld2
		jldopen(fopath, "w") do file
			file["info/DLtimestamplist"] = DLtimestamplist;
			file["info/stationlist"] = stationlist;
			file["info/starttime"]   = string(starttime)
			file["info/endtime"]     = string(endtime)
			file["info/DL_time_unit"]= string(DL_time_unit)
		end

        #Test download to evaluate use of memory and estimate download time.
		max_num_of_processes_per_parallelcycle = testdownload(InputDict, length(starttimelist), MAX_MEM_PER_CPU)

        if max_num_of_processes_per_parallelcycle < 1
            error("Memory allocation is not enought (currently $MAX_MEM_PER_CPU [GB]). Please inclease MAX_MEM_PER_CPU or decrease number of stations")
        end

        if max_num_of_processes_per_parallelcycle >= length(starttimelist)

            S = pmap(x -> seisdownload_NOISE(x, InputDict), 1:length(starttimelist))

            # save data to jld2
            file = jldopen(fopath, "r+")
            unavalilablefile = jldopen(join([fopath[1:end-5], "_unavailablestations.jld2"]), "w+")

            for ii = 1:size(S)[1] #loop at each starttime
                for jj = 1:size(S[1])[1] #loop at each station id

                    requeststr =S[ii][jj].id
                    varname = joinpath(DLtimestamplist[ii], requeststr)
                    #save_SeisData2JLD2(fopath, varname, S[ii][jj])
                    file[varname] = S[ii][jj]

                    if S[ii][jj].misc["dlerror"] == 1
                        unavalilablefile[varname] = S[ii][jj]
                    end
                end
            end
            JLD2.close(file)
            JLD2.close(unavalilablefile)

        else

            #parallelization by time
    	    pitr = 1

    	    # progress bar

    	    while pitr <= length(starttimelist)

    	        startid1 = pitr
    	        startid2 = pitr + max_num_of_processes_per_parallelcycle - 1

    	        if startid1 == length(starttimelist)
					S = pmap(x -> seisdownload_NOISE(x, InputDict), startid1:startid1)

				elseif startid2 <= length(starttimelist)
    	            #use all processors
	                S = pmap(x -> seisdownload_NOISE(x, InputDict), startid1:startid2)

    	        else
    	            #use part of processors
	                startid2 = startid1 + mod(length(starttimelist), nprocs()) - 1
	                S = pmap(x -> seisdownload_NOISE(x, InputDict), startid1:startid2)
    	        end

                # save data to jld2
                file = jldopen(fopath, "r+")
                unavalilablefile = jldopen(join([fopath[1:end-5], "_unavailablestations.jld2"]), "w+")

                for ii = 1:size(S)[1] #loop at each starttime
                    for jj = 1:size(S[1])[1] #loop at each station id

                        requeststr =S[ii][jj].id
                        varname = joinpath(DLtimestamplist[startid1+ii-1], requeststr)
                        #save_SeisData2JLD2(fopath, varname, S[ii][jj])
                        file[varname] = S[ii][jj]

                        if S[ii][jj].misc["dlerror"] == 1
                            unavalilablefile[varname] = S[ii][jj]
                        end
                    end
                end

                JLD2.close(file)
                JLD2.close(unavalilablefile)

    	        pitr += max_num_of_processes_per_parallelcycle

                #println("pitr: $pitr")
            end
        end

    elseif  DownloadType == "Earthquake" || DownloadType == "earthquake"

		method		    = InputDict["method"]
		event		    = InputDict["event"]
		reg			    = InputDict["reg"]
		fopath          = InputDict["fopath"]

		#save info into jld2
		jldopen(fopath, "w") do file
			file["info/method"]  = method;
			file["info/event"]   = event;
			file["info/reg"]     = reg
			file["info/fopath"]  = fopath
		end

		#Test download to evaluate use of memory and estimate download time.
		max_num_of_processes_per_parallelcycle = testdownload(InputDict, length(event), MAX_MEM_PER_CPU)

		if max_num_of_processes_per_parallelcycle < 1
			error("Memory allocation is not enought (currently $MAX_MEM_PER_CPU [GB]). Please inclease MAX_MEM_PER_CPU or decrease number of stations")
		end

		if max_num_of_processes_per_parallelcycle >= length(event)

			S = pmap(x -> seisdownload_EARTHQUAKE(x, InputDict), 1:length(event))

			# save data to jld2
			file = jldopen(fopath, "r+")
			unavalilablefile = jldopen(join([fopath[1:end-5], "_unavailablestations.jld2"]), "w+")

			for ii = 1:size(S)[1] #loop at each starttime
				varname = joinpath("event",InputDict["event"][ii]["origin"]["time"][1:end-4])
				file[varname] = S[ii]
			end

			JLD2.close(file)
			JLD2.close(unavalilablefile)

		else

			#parallelization by time
			pitr = 1

			while pitr <= length(event)

				startid1 = pitr
				startid2 = pitr + max_num_of_processes_per_parallelcycle - 1

				if startid1 == length(event)
					#use one
					S = pmap(x -> seisdownload_EARTHQUAKE(x, InputDict), startid1:startid1)

				elseif startid2 <= length(event)
					#use all processors
					S = pmap(x -> seisdownload_EARTHQUAKE(x, InputDict), startid1:startid2)

				else
					#use part of processors
					startid2 = startid1 + mod(length(event), nprocs()) - 1
					S = pmap(x -> seisdownload_EARTHQUAKE(x, InputDict), startid1:startid2)
				end

				# save data to jld2
				file = jldopen(fopath, "r+")
				unavalilablefile = jldopen(join([fopath[1:end-5], "_unavailablestations.jld2"]), "w+")

				for ii = 1:size(S)[1] #loop at each starttime
					varname = joinpath("event",InputDict["event"][startid1+ii-1]["origin"]["time"][1:end-4])
					file[varname] = S[ii]
				end

				JLD2.close(file)
				JLD2.close(unavalilablefile)

				pitr += max_num_of_processes_per_parallelcycle

				#println("pitr: $pitr")
			end
		end

    else
        println("Download type is not known (chose Noise or Earthquake).")
    end

    println("Downloading and Saving data is successfully done.\njob ended at "*string(now()))
    return 0

end

end


abstract type Face{N,T} end
Base.broadcastable(f::Face) = Ref(f)
Base.@propagate_inbounds function Base.getindex(f::Face, i::Int)
    getfield(f, :points)[i]
end


function GalileoAlmanac(arg0::jint, arg1::jint, arg10::jdouble, arg11::jdouble, arg12::jdouble, arg13::jint, arg14::jint, arg15::jint, arg16::AbsoluteDate, arg2::jdouble, arg3::jdouble, arg4::jdouble, arg5::jdouble, arg6::jint, arg7::jdouble, arg8::jdouble, arg9::jdouble)
    return GalileoAlmanac((jint, jint, jdouble, jdouble, jdouble, jint, jint, jint, AbsoluteDate, jdouble, jdouble, jdouble, jdouble, jint, jdouble, jdouble, jdouble), arg0, arg1, arg10, arg11, arg12, arg13, arg14, arg15, arg16, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end

function GalileoAlmanac(arg0::jint, arg1::jint, arg10::jdouble, arg11::jdouble, arg12::jdouble, arg13::jint, arg14::jint, arg15::jint, arg2::jdouble, arg3::jdouble, arg4::jdouble, arg5::jdouble, arg6::jint, arg7::jdouble, arg8::jdouble, arg9::jdouble)
    return GalileoAlmanac((jint, jint, jdouble, jdouble, jdouble, jint, jint, jint, jdouble, jdouble, jdouble, jdouble, jint, jdouble, jdouble, jdouble), arg0, arg1, arg10, arg11, arg12, arg13, arg14, arg15, arg2, arg3, arg4, arg5, arg6, arg7, arg8, arg9)
end

function get_af0(obj::GalileoAlmanac)
    return jcall(obj, "getAf0", jdouble, ())
end

function get_af1(obj::GalileoAlmanac)
    return jcall(obj, "getAf1", jdouble, ())
end

function get_bgd(obj::GalileoOrbitalElements)
    return jcall(obj, "getBGD", jdouble, ())
end

function get_bgde1e5a(obj::GalileoOrbitalElements)
    return jcall(obj, "getBGDE1E5a", jdouble, ())
end

function get_bgde5b_e1(obj::GalileoOrbitalElements)
    return jcall(obj, "getBGDE5bE1", jdouble, ())
end

function get_cic(obj::GalileoAlmanac)
    return jcall(obj, "getCic", jdouble, ())
end

function get_cis(obj::GalileoAlmanac)
    return jcall(obj, "getCis", jdouble, ())
end

function get_crc(obj::GalileoAlmanac)
    return jcall(obj, "getCrc", jdouble, ())
end

function get_crs(obj::GalileoAlmanac)
    return jcall(obj, "getCrs", jdouble, ())
end

function get_cuc(obj::GalileoAlmanac)
    return jcall(obj, "getCuc", jdouble, ())
end

function get_cus(obj::GalileoAlmanac)
    return jcall(obj, "getCus", jdouble, ())
end

function get_date(obj::GalileoAlmanac)
    return jcall(obj, "getDate", AbsoluteDate, ())
end

function get_e(obj::GalileoAlmanac)
    return jcall(obj, "getE", jdouble, ())
end

function get_health_e1(obj::GalileoAlmanac)
    return jcall(obj, "getHealthE1", jint, ())
end

function get_health_e5a(obj::GalileoAlmanac)
    return jcall(obj, "getHealthE5a", jint, ())
end

function get_health_e5b(obj::GalileoAlmanac)
    return jcall(obj, "getHealthE5b", jint, ())
end

function get_i0(obj::GalileoAlmanac)
    return jcall(obj, "getI0", jdouble, ())
end

function get_i_dot(obj::GalileoAlmanac)
    return jcall(obj, "getIDot", jdouble, ())
end

function get_iod(obj::GalileoAlmanac)
    return jcall(obj, "getIOD", jint, ())
end

function get_iod_nav(obj::GalileoOrbitalElements)
    return jcall(obj, "getIODNav", jint, ())
end

function get_m0(obj::GalileoAlmanac)
    return jcall(obj, "getM0", jdouble, ())
end

function get_mean_motion(obj::GalileoAlmanac)
    return jcall(obj, "getMeanMotion", jdouble, ())
end

function get_omega0(obj::GalileoAlmanac)
    return jcall(obj, "getOmega0", jdouble, ())
end

function get_omega_dot(obj::GalileoAlmanac)
    return jcall(obj, "getOmegaDot", jdouble, ())
end

function get_pa(obj::GalileoAlmanac)
    return jcall(obj, "getPa", jdouble, ())
end

function get_prn(obj::GalileoAlmanac)
    return jcall(obj, "getPRN", jint, ())
end

function get_sma(obj::GalileoAlmanac)
    return jcall(obj, "getSma", jdouble, ())
end

function get_time(obj::GalileoAlmanac)
    return jcall(obj, "getTime", jdouble, ())
end

function get_week(obj::GalileoAlmanac)
    return jcall(obj, "getWeek", jint, ())
end


export ferrite_dict

# Specific power loss data read off the charts
# frequency,(x1,y1),(x2,y2)
# frequency if Hz
# x1, x2 flux density in mT
# y1, y2 specific power loss kW/m^3
spldata_3f3 = SplInput((
  25e3,(100,14),(300,270),
  100e3,(62,20,),(240,750),
  200e3,(38,8),(190,1100),
  400e3,(20,17),(140,1700),
  700e3,(12,20),(80,1750)
  ))
spldata_3f35 = SplInput((
  500e3,(42,52),(130,1400),
  1e6,(20,70),(68,1400)
  ))
spldata_3f4 = SplInput((
  25e3,(82,19),(210,360),
  100e3,(48,19),(170,1070),
  200e3,(35,19),(150,1300),
  400e3,(25,16),(140,1700),
  1e6,(15,20),(78,1900),
  2e6,(5.5,20),(40,1850),
  3e6,(4,40),(26,2000)
  ))
spldata_3f45 = SplInput((
  500e3,(22,12),(120,1700),
  1e6,(14,10),(95,1750),
  2e6,(5.5,14),(50,2000),
  ))
spldata_3f5 = SplInput((
  1e6,(15,10),(95,1750),
  2e6,(6.8,13),(57,1600),
  3e6,(4,15),(40,1700)
  ))
spldata_4f1 = SplInput((
  3e6,(5.3,33),(28,1400),
  5e6,(4,40),(22,1500),
  10e6,(3.2,43),(13,1500)
  ))

spldata_r = SplInput((
  25e3,(110,10),(300,180),
  100e3,(51,10),(300,1100),
  200e3,(35,10),(240,2000),
  500e3,(30,45),(140,2000)
  ))

spldata_l = SplInput((
  500e3,(20,10),(110,1000),
  1e6,(15,10),(80,1000),
  2e6,(4.2,10),(36,1000),
  3e6,(2.5,10),(22,1000)
  ))

const ferrite_dict = Dict(
  "3f3" => FerriteProperties("3f3",0.2e6,0.5e6,25,100,
                             BHloop(15,.44,.15),
                             BHloop(10,.345,.120),
                             SpecificPowerLossData(spldata_3f3)),
  "3f35"=> FerriteProperties("3F35",0.5e6,1.0e6,25,100,
                              BHloop(40,.45,.2),
                              BHloop(25,.4,.15),
                              SpecificPowerLossData(spldata_3f35)),
  "3f4" => FerriteProperties("3f4",1e6,2e6,25,100,
                             BHloop(60,0.4,0.16),
                             BHloop(50,0.345,.14),
                             SpecificPowerLossData(spldata_3f4)),
  "3f45" => FerriteProperties("3f45",1e6,2e6,25,100,
                             BHloop(60,.375,.155),
                             BHloop(50,.360,.145),
                             SpecificPowerLossData(spldata_3f45)),
  "3f5" => FerriteProperties("3f5",2e6,4e6,25,100,
                             BHloop(60,.35,.15),
                             BHloop(50,.30,.12),
                             SpecificPowerLossData(spldata_3f5)),
  "4f1" => FerriteProperties("4f1",4e6,10e6,25,100,
                             BHloop(160,.275,.200),
                             BHloop(140,.250,.150),
                             SpecificPowerLossData(spldata_4f1)),
  "r" => FerriteProperties("r",0.5e6,1.8e6,25,100,
                            BHloop(NaN,NaN,NaN),
                            BHloop(NaN,NaN,NaN),
                            SpecificPowerLossData(spldata_r)),
  "l" => FerriteProperties("l",0.5e6,3e6,25,100,
                            BHloop(NaN,NaN,NaN),
                            BHloop(NaN,NaN,NaN),
                            SpecificPowerLossData(spldata_l))
)