#
println("Ed) Photoabsorption of Ne^+: AverageSCA model.")
using JLD
#
if  false
    # Calculation of the Ne^+  2s^2 2p^5; 2s --> 2p, 3p, 4p, 5p excitation cross sections
    setDefaults("print summary: open", "Ne-plus-photoexcitation.sum")
    setDefaults("method: continuum, asymptotic Coulomb")                                                      ## "method: continuum, Galerkin"
    grid = Radial.Grid(Radial.Grid(false); rnt = 3.0e-6, h = 2.0e-2, hp = 3.0e-2, NoPoints=1110)
    name = "Photoabsorption calculations for Ne^+ for energies = [1, 4] a.u."
    wa   = Cascade.Computation(Cascade.Computation(); name=name, nuclearModel=Nuclear.Model(10.), grid=grid, approach=Cascade.AverageSCA(),
                               scheme=Cascade.PhotonExcitationScheme([PhotoExc()], [E1], 0.5, 4.0, 1, [Shell("2s"), Shell("2p")],
                                                                     [Shell("2s"), Shell("2p"), Shell("3p"), Shell("4p"), Shell("5p")]),
                               initialConfigs=[Configuration("1s^2 2s^2 2p^5")] )
    println(wa)
    wb = perform(wa; output=true)
    setDefaults("print summary: close", "")
    #
elseif false
    # Calculation of the Ne^+  2s^2 2p^5; photo-ionization cross sections
    setDefaults("print summary: open", "Ne-plus-photoionization.sum")

    name = "Photoionization of Si- "
    grid = Radial.Grid(Radial.Grid(false); rnt = 3.0e-6, h = 2.0e-2, hp = 3.0e-2, NoPoints=1110)
    wa   = Cascade.Computation(Cascade.Computation(); name=name, nuclearModel=Nuclear.Model(10.), grid=grid, approach=Cascade.AverageSCA(),
                               scheme=Cascade.PhotonIonizationScheme([Photo()], 1, [1.0, 3.0, 5.0]),
                               initialConfigs=[Configuration("1s^2 2s^2 2p^5")] )
    println(wa)
    wb = perform(wa; output=true)
    setDefaults("print summary: close", "")

elseif false
    # Simulation of the Ne^+  2s^2 2p^5; photo-ionization cross sections
    setDefaults("print summary: open", "Ne-plus-photoabsorption-simulation.sum")

    data = [JLD.load("zzz-cascade-ionizing-computations-2020-07-10T19.jld"), JLD.load("zzz-cascade-excitation-computations-2020-07-10T19.jld")]
    ##x JLD2.@load "zzz-cascade-ionizing-computations-2020-06-23T09.jld"
    ##x resIon  = results
    ##x JLD2.@load "zzz-cascade-excitation-computations-2020-06-23T09.jld"
    ##x resExc = results
    ##x data = [resExc]

    name = "Simulation of photoabsorption for Ne^+"

    wc   = Cascade.Simulation(Cascade.Simulation(), name=name, property=Cascade.PhotoAbsorption(),
                              settings=Cascade.SimulationSettings(0., 0., 1., 4., 3., [(1, 1.0)]), computationData=data )
    println(wc)
    wd = perform(wc; output=true)
    setDefaults("print summary: close", "")
#
elseif false
    # Example for Atomic cascade computation (symmetry, 2021)
    # Calculation of the Mg  1s 2s^2 2p^6 3s^2 decay cascade after 1s photoionization
    setDefaults("print summary: open", "Mg-1s-decay.sum")

    decayScheme = Cascade.StepwiseDecayScheme([Auger(), Radiative()], 3, Dict{Int64,Float64}(), 0., Shell[], Shell[])

    grid = Radial.Grid(Radial.Grid(false), rnt = 4.0e-6, h = 5.0e-2, hp = 1.0e-2, rbox = 10.0)
    name = "Computation of Mg  1s 2s^2 2p^6 3s^2 decay cascade after 1s photoionization"
    wa   = Cascade.Computation(Cascade.Computation(); name=name, nuclearModel=Nuclear.Model(12.), grid=grid,
                               approach=Cascade.AverageSCA(), scheme=decayScheme,
                               ## initialConfigs=[Configuration("1s 2s^2 2p^6 3s^2")] )
                               initialConfigs=[Configuration("1s 2s^2 2p^6 3s^2 3p")] )
    @show wa
    wb = perform(wa; output=true)
    setDefaults("print summary: close", "")
#
elseif true
    # Simulation of the Mg  1s 2s^2 2p^6 3s^2 decay cascade after 1s photoionization
    setDefaults("print summary: open", "zzz-Cascade-simulation.sum")

    ## simulationSettings = Cascade.SimulationSettings(0., 0., 0., 0., 0., [(1, 1.0)])
    ## simulationSettings = Cascade.SimulationSettings(0., 0., 0., 0., 0., [(4, 1.0)])
    simulationSettings = Cascade.SimulationSettings(0., 0., 0., 0., 0., [(1, 0.25), (2, 0.25), (3, 0.25), (4, 0.25)])

    data = [JLD.load("zzz-cascade-decay-computations-2021-03-14T19.jld")]
    name = "Simulation of Mg  1s 2s^2 2p^6 3s^2 decay cascade after 1s photoionization"

    wc   = Cascade.Simulation(Cascade.Simulation(), name=name, property=Cascade.IonDistribution(),
                              settings=simulationSettings, computationData=data )
    @show wc
    wd = perform(wc; output=true)
    setDefaults("print summary: close", "")
    #

end


# Propeller Aerodynamic Validation using UiUC Propeller data,

include("propwing.jl")

module SysOptInit_M
# export sysoptinit, getvar, printmaxviol

# ----- Define function for getting values within objective function ----- #
function getvar(key,x,optvars,optrange,optval,optscale)
    # Determine whether variable is a design variable, assign default if not
    if key in optvars
        value = x[optrange[key]]./optscale[key]'
    else
        value = optval[key][:,3]
    end
    # Make sure Float64 is returned instead of Array{Float64,1} where applicable
    if length(value) == 1
        value = value[1]
    end
    return value
end

# ----- Define function for setting values within objective function ----- #
function setvar(key,x,optvars,optrange,optval,optscale,value)
    # Determine whether variable is a design variable, assign default if not
    if key in optvars
        x[optrange[key]] = value.*optscale[key]'
    end

end

# ----- Define function for printing maximum constraint violations ----- #
function printmaxviol(c,name)
    if !isempty(find(c.>0.0))
        println("$name: ",maximum(c))
    end
end

function sysoptinit(optvars::Array{Symbol,1})
    # ----- Define lower, upper, and starting values ----- #
    optval = Dict{Symbol,Array{Float64,2}}()
    #---General Geometry Variables

    #---Power System Variables
    # Propeller Defaults are for the Epema N250

    #Propulsion
    optval[:velocity]  = [1.0 50.0 19.0]  #m/s
    optval[:proptwist] = [-15.0 80.0 54.9242;
    -15.0 75.0 49.6058;
    -15.0 60.0 44.6923;
    -15.0 45.0 40.1836;
    -15.0 45.0 36.0798;
    -15.0 45.0 32.3809;
    -15.0 45.0 29.0869;
    -15.0 45.0 26.1977;
    -15.0 45.0 23.7134]  #degrees # Propeller Defaults are for the Epema N250

    optval[:propchord] = [0.03 0.3 0.158788;
    0.03 0.3 0.15083;
    0.03 0.3 0.146158;
    0.03 0.3 0.145793;
    0.03 0.3 0.148978;
    0.03 0.3 0.153175;
    0.03 0.3 0.154064;
    0.03 0.3 0.145548;
    0.03 0.3 0.119747]  #chord/Rtip # Propeller Defaults are for the Epema N250

    optval[:RPMprop]   = [10.0 20000.0 4040.0] #RPM
    optval[:Rtip]      = [0.01 2.0 .203] # Radius meters, # Propeller Defaults are for the Epema N250

    # ----- Define scaling for each variable ----- #
    optscale = Dict()

    #Propulsion
    optscale[:velocity]  = [1e-3]
    optscale[:proptwist] = [1e-1]
    optscale[:propchord] = [1e3]
    optscale[:RPMprop]   = [1e-4] #RPM
    optscale[:Rtip]      = [1e0] # Radius meters
    optscale[:T_prop]      = [1e-2] # Radius meters

    # ----- Apply scaling ----- #
    for var in optvars
        optval[var] = optval[var].*optscale[var]'
    end

    # ----- Define Ranges ----- #
    optrange = Dict()
    cx = 0
    for var in optvars
        optrange[var] = cx+(1:size(optval[var],1))
        cx += size(optval[var],1)
    end
    vartotal = cx

    # ----- Assemble lower, upper, and starting optval ----- #
    lb = zeros(vartotal)
    ub = zeros(vartotal)
    x0 = zeros(vartotal)
    for var in optvars
        lb[optrange[var]] = optval[var][:,1]
        ub[optrange[var]] = optval[var][:,2]
        x0[optrange[var]] = optval[var][:,3]
    end

    # ----- Define Optimizer Options ----- #
    options = Dict{String, Any}()
    options["Derivative level"] = 0
    # options["Function precision"] = 3.00E-4
    options["Difference interval"] = 1e-5
    options["Central difference interval"] = 1e-6
    options["Iterations limit"] = 1e8
    options["Major iterations limit"] = 500
    options["Minor iterations limit"]= 1e8
    options["Major optimality tolerance"] = 1e-4
    options["Minor optimality  tolerance"] = 1e-4
    options["Major feasibility tolerance"] = 1e-4
    options["Minor feasibility tolerance"] = 1e-4
    options["Minor print level"] = 1
    options["Print frequency"] = 100
    options["Scale option"] = 2
    options["Scale tolerance"] = .95
    # options["Verify level"] = 3 #only if specifying gradients

    return x0,lb,ub,options,optrange,optval,optscale
end #function sysoptinit

end #module

# Do all calling relative to file's location
fileLoc,_ = splitdir(@__FILE__)

include("$(fileLoc)/../../hale-module-miscellany/params.jl")
push!(LOAD_PATH,"$(fileLoc)/../../hale-module-miscellany/")
using StandardAtmosphere

using CCBlade

using JLD
using PyPlot
close("all")

function prop_aero(x0,optvars,optrange,optval,optscale,T_req,af,rho,unitareas,pitch=0.0,optimize=false,numblades = 2)
    #Unpack the variables, sysoptinit takes care of which are being optimized
    theta = (SysOptInit_M.getvar(:proptwist,x0,optvars,optrange,optval,optscale)+pitch)*pi/180 #in degrees, convert to rad
    RPMp = SysOptInit_M.getvar(:RPMprop,x0,optvars,optrange,optval,optscale)
    Rtip = SysOptInit_M.getvar(:Rtip,x0,optvars,optrange,optval,optscale)
    chords = SysOptInit_M.getvar(:propchord,x0,optvars,optrange,optval,optscale)*Rtip
    velocity = SysOptInit_M.getvar(:velocity,x0,optvars,optrange,optval,optscale)

    r = linspace(.2,.911,length(chords)) * Rtip
    B = numblades
    Rhub = r[1]/2
    chord_prop = chords
    twist_prop = theta
    Vinf = velocity
    Omega = RPMp/60*2*pi
    xtowing = 0.5
    uwake, vwake, T, Q, reff = propwing.propanalysis(Rhub, Rtip, r, chord_prop, twist_prop, B, af, Vinf, Omega, rho, xtowing)
    # Tprop,Qprop,Uprop,Utprop,propvolume,Np,Tp,J_p,eff,CT,CQ = CCcall.cccall(RPMp,theta,chords,CCBlade,Rtip,r,rho,velocity,af,unitareas,numblades)
    Vhub = Vinf
    precone = 0.0
    turbine = false
    J_p = Vhub/(RPMp/60*2*Rtip)
    eff, CT, CQ = CCBlade.nondim(T, Q, Vhub, Omega, rho, Rtip, precone, turbine)
    return T,Q,uwake, vwake, 0.0, 0.0, 0.0,J_p,eff, CT, CQ
end

# push!(LOAD_PATH,"$(fileLoc)/../../Snopt.jl/src/")
# using Snopt
#--------- Initialization ---------#
optvars = [
# :proptwist;
:RPMprop;
:velocity
# :Rtip;
]

x0,lb,ub,options,optrange,optval,optscale = SysOptInit_M.sysoptinit(optvars)

#Initialize Airfoil Data
twists = SysOptInit_M.getvar(:proptwist,x0,optvars,optrange,optval,optscale)
Rtip = SysOptInit_M.getvar(:Rtip,x0,optvars,optrange,optval,optscale)
chords = SysOptInit_M.getvar(:propchord,x0,optvars,optrange,optval,optscale)*Rtip
velocity = SysOptInit_M.getvar(:velocity,x0,optvars,optrange,optval,optscale)
RPMprop = SysOptInit_M.getvar(:RPMprop,x0,optvars,optrange,optval,optscale)
r = collect(linspace(.2,.911,length(chords))) .* Rtip
J = velocity/(RPMprop/60*2*Rtip) #Vinf/(Omega/(2*pi)*Rtip*2)
alt = 222.0 #meters
numblades = 6
rho,mu,q,a = StandardAtmosphere.atmosphere(alt)
re = rho/mu.*chords.*sqrt.((RPMprop*pi/30.0*r).^2+velocity^2)

afgeomnames = ["$(fileLoc)/EpemaExperimental/R023.dat";"$(fileLoc)/EpemaExperimental/R023.dat";
"$(fileLoc)/EpemaExperimental/R0405.dat";"$(fileLoc)/EpemaExperimental/R0405.dat";
"$(fileLoc)/EpemaExperimental/R0405.dat";"$(fileLoc)/EpemaExperimental/R079.dat";
"$(fileLoc)/EpemaExperimental/R079.dat";"$(fileLoc)/EpemaExperimental/R079.dat";"$(fileLoc)/EpemaExperimental/R1.dat"]

N=100
M = 3
new_RPM=4040 #linspace(4000,20000,M)
pitch_array = [-5.0,0.0,5.0]
new_velocity = linspace(1,60,N)
J_p = zeros(N,M)
eta_p = zeros(N,M)
CT_p = zeros(N,M)
CQ_p = zeros(N,M)

SysOptInit_M.setvar(:RPMprop,x0,optvars,optrange,optval,optscale,new_RPM)

velocity = SysOptInit_M.getvar(:velocity,x0,optvars,optrange,optval,optscale)
RPMprop = SysOptInit_M.getvar(:RPMprop,x0,optvars,optrange,optval,optscale)

J = velocity/(RPMprop/60*2*Rtip)

re = rho/mu.*chords.*sqrt.((RPMprop*pi/30.0*r).^2+velocity^2)

# af,unitareas, Al, Au = CCBladeAirfoilTools.af_performance_from_geom(afgeomnames,chords,Rtip,r,J,re)
# JLD.save("$(fileLoc)/af_prop$(new_RPM).jld", "af", af, "unitareas",unitareas,"Al", Al, "Au", Au)
JLDin = JLD.load("$(fileLoc)/af_prop$(new_RPM).jld")
af = JLDin["af"]
unitareas = JLDin["unitareas"]

for j = 1:M

    T_req = 0.0
    pitch = pitch_array[j]+3 #degrees
    optimize=false
    numblades = 6
    args = (optvars,optrange,optval,optscale,T_req,af,rho,unitareas,pitch,optimize,numblades)

    for i = 1:N
        SysOptInit_M.setvar(:velocity,x0,optvars,optrange,optval,optscale,new_velocity[i])

        Tprop,Qprop,Uprop, Utprop, propvolume, Np, Tp,J_p[i,j],eta_p[i,j], CT_p[i,j], CQ_p[i,j] = prop_aero(x0,args...)

    end
end

using CSV
CT_N250_Epema_25deg = CSV.read("$(fileLoc)/EpemaExperimental/CT_N250_25deg.txt", delim = '\t',header = false)
CP_N250_Epema_25deg = CSV.read("$(fileLoc)/EpemaExperimental/CP_N250_25deg.txt", delim = '\t',header = false)
CQ_N250_Epema_25deg = copy(CP_N250_Epema_25deg)
CQ_N250_Epema_25deg[:,2] = CQ_N250_Epema_25deg[:,2]/(2*pi)
ETA_N250_Epema_25deg = CT_N250_Epema_25deg[:,2].*CT_N250_Epema_25deg[:,1]./CP_N250_Epema_25deg[:,2]

CT_N250_Epema_30deg = CSV.read("$(fileLoc)/EpemaExperimental/CT_N250_30deg.txt", delim = '\t',header = false)
CP_N250_Epema_30deg = CSV.read("$(fileLoc)/EpemaExperimental/CP_N250_30deg.txt", delim = '\t',header = false)
CQ_N250_Epema_30deg = copy(CP_N250_Epema_30deg)
CQ_N250_Epema_30deg[:,2] = CQ_N250_Epema_30deg[:,2]/(2*pi)
ETA_N250_Epema_30deg = CT_N250_Epema_30deg[:,2].*CT_N250_Epema_30deg[:,1]./CP_N250_Epema_30deg[:,2]

CT_N250_Epema_35deg = CSV.read("$(fileLoc)/EpemaExperimental/CT_N250_35deg.txt", delim = '\t',header = false)
CP_N250_Epema_35deg = CSV.read("$(fileLoc)/EpemaExperimental/CP_N250_35deg.txt", delim = '\t',header = false)
CQ_N250_Epema_35deg = copy(CP_N250_Epema_35deg)
CQ_N250_Epema_35deg[:,2] = CQ_N250_Epema_35deg[:,2]/(2*pi)
ETA_N250_Epema_35deg = CT_N250_Epema_35deg[:,2].*CT_N250_Epema_35deg[:,1]./CP_N250_Epema_35deg[:,2]

rc("figure", figsize=(6.5, 2.6))
rc("font", size=10.0)
rc("lines", linewidth=1.5)
rc("lines", markersize=3.0)
rc("legend", frameon=false)
rc("axes.spines", right=false, top=false)
rc("figure.subplot", left=0.15, bottom=0.18, top=0.97, right=0.72)
rc("axes", color_cycle=["348ABD", "A60628", "009E73", "7A68A6", "D55E00", "CC79A7"])

mygreen = "#009e73"
myblue = "#348abd"
myred = "#a60628"
plotColors = [myblue,myred,mygreen]

#Thrust Coefficient
PyPlot.figure()
for i = 1:M
    PyPlot.plot(J_p[:,i],CT_p[:,i], marker="o",color = plotColors[i],markeredgecolor = "k",label = "BEM $(30+pitch_array[i])deg RPM=$(new_RPM)")

end
PyPlot.plot(CT_N250_Epema_25deg[:,1],CT_N250_Epema_25deg[:,2],linestyle="-", color = plotColors[1],label = "Epema 25deg")
PyPlot.plot(CT_N250_Epema_30deg[:,1],CT_N250_Epema_30deg[:,2],linestyle="-", color = plotColors[2],label = "Epema 30deg")
PyPlot.plot(CT_N250_Epema_35deg[:,1],CT_N250_Epema_35deg[:,2],linestyle="-", color = plotColors[3],label = "Epema 35deg")
PyPlot.xlabel(L"J\, (V/(n*D_{prop})")
PyPlot.ylabel(L"CT\, (T/(\rho*n^2*{D_{prop}}^4))")
PyPlot.xlim([0,2.0])
PyPlot.ylim([0,.4])
PyPlot.legend(loc = 1)

#Torque Coefficient

PyPlot.figure()
for i = 1:M
    PyPlot.plot(J_p[:,i],CQ_p[:,i], marker="o",color = plotColors[i],markeredgecolor = "k",label = "BEM $(30+pitch_array[i])deg RPM=$(new_RPM)")

end
PyPlot.plot(CQ_N250_Epema_25deg[:,1],CQ_N250_Epema_25deg[:,2],linestyle="-", color = plotColors[1],label = "Epema 25deg")
PyPlot.plot(CQ_N250_Epema_30deg[:,1],CQ_N250_Epema_30deg[:,2],linestyle="-", color = plotColors[2],label = "Epema 30deg")
PyPlot.plot(CQ_N250_Epema_35deg[:,1],CQ_N250_Epema_35deg[:,2],linestyle="-", color = plotColors[3],label = "Epema 35deg")
PyPlot.xlabel(L"J\, (V/(n*D_{prop})")
PyPlot.ylabel(L"Cq\, (T/(\rho*n^2*{D_{prop}}^5)")
PyPlot.xlim([0,2.0])
PyPlot.ylim([0,.1])
PyPlot.legend(loc = 1)

#Efficiency
PyPlot.figure()
for i = 1:M
    PyPlot.plot(J_p[:,i],eta_p[:,i], linestyle="-",color = plotColors[i])#,markeredgecolor = "k",label = "BEM $(30+pitch_array[i])deg RPM=$(new_RPM)")

end
PyPlot.plot(CT_N250_Epema_25deg[:,1],ETA_N250_Epema_25deg,marker="o",linestyle="", color = plotColors[1])#,label = "Epema 25deg")
PyPlot.plot(CT_N250_Epema_30deg[:,1],ETA_N250_Epema_30deg,marker="o",linestyle="", color = plotColors[2])#,label = "Epema 30deg")
PyPlot.plot(CT_N250_Epema_35deg[:,1],ETA_N250_Epema_35deg,marker="o",linestyle="", color = plotColors[3])#,label = "Epema 35deg")

PyPlot.plot([],[],"ko",label = "Experimental")
PyPlot.plot([],[],"k-",label = "Blade Element Momentum")
PyPlot.xlabel("Advance Ratio")
PyPlot.ylabel("Efficiency")
# PyPlot.xlim([0,2.0])
# PyPlot.ylim([0,1.5])
PyPlot.legend(loc="center left", bbox_to_anchor=(.8, 0.8))


### road_model.jl

using Dates
using Gadfly
using LightGraphs, MetaGraphs
using JuMP, BilevelJuMP
using Gurobi
using DataFrames

struct ModelParams
    lambda
    sensor_cap_ratio
    force_sensor_bidirectional::Bool
    junctions::Bool
    capacity_classes::Dict{String, Real}

    # Sensors can be forced on, off, or left arbitrary (not in Dict)
    # fixed_sensors::Dict{Tuple{Int, Int}, Bool}
end

struct SolveParams
    time_limit
    # TODO
    indicator_mode
    opt_attributes
end

# Default parameters
ModelParams(; lambda = 1, bidir = false, junctions = false) = ModelParams(
    lambda,
    1.0,
    bidir,
    junctions,
    Dict(
        "motorway"  => 500,
        "trunk"     => 400,
        "primary"   => 300,
        "secondary" => 200,
        "tertiary"  => 100,
        "other"     => 50
    ))

SolveParams(; time_limit = 60, indicator_mode = false, opt_attributes = Dict("MIPFocus" => 3)) = SolveParams(time_limit, indicator_mode, opt_attributes)

export ModelParams, SolveParams

struct RoadModel
    m
    G
    constraints
    MP::ModelParams
    SP::SolveParams
    data::Dict{Symbol, Any}
end

include("road_utils.jl")
include("road_constraints.jl")

function RoadModel(G, MP::ModelParams, SP::SolveParams)::RoadModel
    # TODO: init errors if global const
    GRB_ENV = Gurobi.Env()
    mode = SP.indicator_mode ? BilevelJuMP.IndicatorMode() : BilevelJuMP.StrongDualityMode(1e-5)
    m = BilevelModel(() -> Gurobi.Optimizer(GRB_ENV), mode = mode)
    set_optimizer_attribute(m, "TimeLimit", SP.time_limit)
    for (k, v) in pairs(SP.opt_attributes)
        set_optimizer_attribute(m, k, v)
    end

    # clone the graph, as it will be mutated
    model_G = deepcopy(G)

    # constraints and arbitrary model data is stored here
    data = Dict()

    # Storing variables as properties in the graph is convenient
    for (u, v, p) ∈ edge_prop_triples(model_G)
        cap = get(MP.capacity_classes, p[:street_type], MP.capacity_classes["other"])

        fuv = @variable(Lower(m), base_name = "f_$(u),$(v)")
        suv = @variable(Upper(m), base_name = "s_$(u),$(v)")
        set_lower_bound(fuv, 0)
        set_upper_bound(fuv, cap)
        set_binary(suv)

        set_prop!(model_G, u, v, :flow_var, fuv)
        set_prop!(model_G, u, v, :sensor_var, suv)
        set_prop!(model_G, u, v, :capacity, cap)
    end

    RoadModel(m, model_G, [], MP, SP, data)
end

function setup!(M::RoadModel)
    edge_prop_map(prop) = edge_prop_triples(M.G) .|> ((_, _, p),) -> p[prop]

    # Various constraint passes

    # Mutually exclusive ways to prevent flow looping on 2-cycles
    if M.MP.junctions
        cs_junction!(Lower(M.m), M)
    else
        cs_conserve_flow!(Lower(M.m), M)
        cs_inflow!(Lower(M.m), M)
    end
    cs_sensor!(Lower(M.m), M)

    # Optional: Not implemented
    # cs_anticycle_local!(Lower(M.m), M)
    # cs_anticycle_nonlocal!(Lower(M.m), M)

    # Objectives

    sensor_map((u, v, p),) = let
        # Sensors on both sides of the road should count as "one" sensor in bidirectional mode
        if M.MP.force_sensor_bidirectional && has_edge(M.G, v, u)
            p[:sensor_var] * 0.5
        else
            p[:sensor_var]
        end
    end
    num_sensors = 0
    # Type inference failure...
    for s in sensor_map.(edge_prop_triples(M.G))
        num_sensors += s
    end
    M.data[:num_sensors_var] = num_sensors

    weighted_flows = sum(edge_prop_map(:length) .* edge_prop_map(:flow_var)) / 100
    # try to normalise by "size" of graph
    λ = M.MP.lambda / length(edges(M.G))

    @objective(Lower(M.m), Max, weighted_flows)
    @objective(Upper(M.m), Min, num_sensors + λ * weighted_flows)
end

"""
Outputs sensor placements and flows as edge properties in model graph `M.G`.

Will throw error if solver fails to find a solution (seems to be picky about which parameters it likes)
"""
function solve!(M::RoadModel)
    optimize!(M.m)

    for (u, v, p) in edge_prop_triples(M.G)
        set_prop!(M.G, u, v, :sensor, value(p[:sensor_var]) ≈ 1)
        f = value(p[:flow_var])
        # Numerical errors...
        @assert f > -1e-5 "Got negative flow: $(f)"
        set_prop!(M.G, u, v, :flow, max(0, f))
    end

    termination_status(M.m)
    # TODO: print summary
end

#
# Drawing utjilty functions
#

@resumable function position_edges(G)
    for (u, v, p) in edge_prop_triples(G)
        up, vp = props.(Ref(G), [u, v])
        @yield (
            up[:latitude],
            up[:longitude],
            vp[:latitude],
            vp[:longitude],
            p[:flow]
        )
    end
end

@resumable function position_sensors(G)
    for (u, v, p) in edge_prop_triples(G)
        if p[:sensor]
            @yield (
            (props(G, u)[:latitude] + props(G, v)[:latitude]) / 2,
            (props(G, u)[:longitude] + props(G, v)[:longitude]) / 2
        )
        end
    end
end

"""
Draws graph as SVG file using the outputs from `solve!`
"""
function draw!(M::RoadModel, svg_name = "road_sensors.svg")
    # Compute latitude and longitude bounding box for plot
    lats = (1:length(vertices(M.G))) .|> i -> get_prop(M.G, i, :latitude)
    longs = (1:length(vertices(M.G))) .|> i -> get_prop(M.G, i, :longitude)

    ns = (Int ∘ round ∘ value)(M.data[:num_sensors_var])
    d = collect(zipv(position_edges(M.G)))
    df = DataFrame(Dict(:x => d[1], :y => d[2], :xend => d[3], :yend => d[4], :Flow => d[5]))

    p = plot(
		# plot sensors on top layer as points instead of lines
		layer(
			Geom.point;
			zip([:x, :y], zipv(position_sensors(M.G)))...
		),
		layer(
            df,
            x = :x,
            y = :y,
            xend = :xend,
            yend = :yend,
            color = :Flow,
			Geom.segment(arrow=true, filled=true)
		),
		Scale.x_continuous(minvalue=minimum(lats), maxvalue=maximum(lats)),
		Scale.y_continuous(minvalue=minimum(longs), maxvalue=maximum(longs)),
		Theme(line_width = 0.1mm),
        Guide.xlabel("Latitude"),
        Guide.ylabel("Longitude"),
        Guide.title("λ = $(M.MP.lambda) ($(ns) sensors)"),
	)
    Gadfly.draw(SVGJS(svg_name), p)
    p
end

"""
Converts result of a successful model solve into JSON
"""
function save_as_json(M::RoadModel)
    logs = Dict()

    sensor_all = true
    sensor_none = false
    # JSON does not have tuples (hashable), so a Dict for edges won't work
    repr_edge((src, dst, prop_dict),) = let
        sensor_all &= prop_dict[:sensor]
        sensor_none |= prop_dict[:sensor]
        [(src, dst), Dict(
            "sensor" => prop_dict[:sensor],
            "flow" => prop_dict[:flow],
        )]
    end
    # Not a particularly good way to serialise model - ids depends on latitude/longitude bounding box
    logs["edges"] = collect(It.map(repr_edge, edge_prop_triples(M.G)))
    logs["objective"] = objective_value(M.m)

    logs["trivial_all"] = sensor_all
    logs["trivial_none"] = !sensor_none

    logs
end

function load(path, G)
    logs = JSON.parsefile(path)

    # other parameters ignored for now
    MP = ModelParams(
        lambda = logs["model_params"]["lambda"],
        bidir = logs["model_params"]["force_sensor_bidirectional"],
        junctions = logs["model_params"]["junctions"]
    )

    # Loading an existing solution so solve should be near-instantaneous
    SP = SolveParams(
        time_limit = 10,
    )

    M = RoadModel(G, MP, SP)
    setup!(M)

    # fix variables to loaded values
    for ((u, v), json_edge_props) in logs["edges"]
        if json_edge_props["sensor"]
            fix(get_prop(M.G, u, v, :sensor_var), 1, force=true)
        else
            fix(get_prop(M.G, u, v, :sensor_var), 0, force=true)
        end
    end
    solve!(M)
    draw!(M)
    M
end

"""
Takes a graph and parameters, runs the model and dumps output as JSON in /out
"""
function run_model(G, MP::ModelParams, SP::SolveParams, name=""; draw = true, add_data = Dict())
    file_prefix = replace(Dates.format(now(), Dates.ISODateTimeFormat), ":" => "-")
    if !isempty(name)
        name = "-$(name)"
    end

    # JSON at the end
    logs = Dict()
    logs["model_params"] = MP
    logs["solve_params"] = SP

    # Record Gurobi solver output
    old_stdout = stdout
    stdout_rd, stdout_wr = redirect_stdout()
    M = let
        M = RoadModel(G, MP, SP)
        setup!(M)
        try
            solve!(M)
            if draw
                draw!(M, "out/$(file_prefix)$(name).svg")
            end
            merge!(logs, save_as_json(M))
        catch e
            println("ERROR: $(e)")
            # save failure report to JSON as merge! in try block did not happen
            name = "-FAILED$(name)"
            logs["exception"] = "$(e)"
        end
        Base.Libc.flush_cstdio()
        M
    end
    logs["stdout"] = String(readavailable(stdout_rd))
    redirect_stdout(old_stdout)

    merge!(logs, add_data)

    # Dump Dict to file
    mkpath("out")
    open("out/$(file_prefix)$(name).json", "w") do io
        write(io, JSON.json(logs, 4))
    end

    # return solved model
    M
end

export RoadModel, setup!, solve!, draw!, save_as_json, run_model, load


abstract type PDXObject end

mutable struct PDFormXObject <: PDXObject
    doc::PDDoc
    cosXObj::CosIndirectObject{CosStream}
    matrix::Matrix{Float32}
    bbox::CDRect{Float32}
    fonts::Dict{CosName, PDFont}
    xobjs::Dict{CosName, PDXObject}
    content_objects::PDPageObjectGroup
    function PDFormXObject(doc::PDDoc, cosxobj::CosIndirectObject{CosStream})
        mat = get(cosxobj, cn"Matrix")
        box = get(cosxobj, cn"BBox")
        @assert box !== CosNull "Invalid Form XObject without bounding box"
        matrix = mat === CosNull ?
            [1f0 0f0 0f0; 0f0 1f0 0f0; 0f0 0f0 1f0] :
            hcat(reshape((get.(CosFloat.(get(mat)))), (2, 3))', [0f0, 0f0, 1f0])
        bbox = CDRect{Float32}(CDRect(box))
        fonts = Dict{CosName, PDFont}()
        xobjs = Dict{CosName, PDXObject}()
        new(doc, cosxobj, matrix, bbox, fonts, xobjs, PDPageObjectGroup())
    end
end

mutable struct PDImageXObject <: PDXObject
    doc::PDDoc
    obj::CosIndirectObject{CosStream}
end

mutable struct PDDefaultXObject <: PDXObject
    doc::PDDoc
    obj::CosObject
end

function createPDXObject(doc::PDDoc, cosstm::CosObject)
    otype = get(cosstm, cn"Type")
    @assert otype === cn"XObject" || otype === CosNull
    subtype = get(cosstm, cn"Subtype")
    subtype === cn"Form"  && return PDFormXObject(doc, cosstm)
    subtype === cn"Image" && return PDImageXObject(doc, cosstm)
    return PDDefaultXObject(doc, cosobj)
end

function find_resource(xobj::PDFormXObject,
                       restype::CosName,
                       resname::CosName)
    cosdoc = xobj.doc.cosDoc
    resref = get(xobj.cosXObj, cn"Resources")
    resref === CosNull && return CosNull
    resources = cosDocGetObject(cosdoc, resref)
    resources === CosNull && return CosNull
    ress = cosDocGetObject(cosdoc, resources, restype)
    ress === CosNull && return CosNull
    res = cosDocGetObject(cosdoc, ress, resname)
    return res
end

get_font(xobj::PDXObject, fontname::CosName) =
    get!(xobj.fonts, fontname,
         get_pd_font!(xobj.doc, find_resource(xobj, cn"Font", fontname)))

get_xobject(xobj::PDXObject, xobjname::CosName) =
    get!(xobj.xobjs, xobjname,
         get_pd_xobject!(xobj.doc,
                         find_resource(xobj, cn"XObject", xobjname)))

function load_content_objects(xobj::PDFormXObject)
    stm = xobj.cosXObj
    bufstm = decode(stm)
    # try
        load_objects(xobj.content_objects, bufstm)
    # finally
    #    util_close(bufstm)
    # end
    return nothing
end

Do(xobj::PDDefaultXObject, state::GState) = nothing

Do(xobj::PDImageXObject, state::GState) = nothing

function Do(xobj::PDFormXObject, state::GState)
    isempty(xobj.content_objects) && load_content_objects(xobj)
    isempty(xobj.content_objects) && return state
    xstate = GState{:PDFIO}()
    ctm = state[:CTM]
    nctm = xobj.matrix*ctm
    xstate[:CTM] = nctm
    xstate[:source] = xobj
    xstate[:text_layout] = state[:text_layout]
    xstate[:h_profile] = state[:h_profile]
    evalContent!(xobj.content_objects, xstate)
    return state
end


@doc """
## Volume Image
This composite type contains volume image information

#### Fields

* `data`: contains the recorded data
* `x`, `y`, `z`, `t` Arrays containing spatial and time information
* `method` AbstractString of method used to compute tomography
* `info`: additional information in dictionary

#### `processing` Fields
The following standard names are used when saving data to the info dictionary.
* `Regularisation`: Regularisation used in tomography
* `NormalisationConstant`: Value used to normalise image to maximum of 1
* `FileName`: Name of file

""" ->
type VolumeImage
    data::Array{AbstractFloat, 4}
    units::AbstractString
    x::Vector{quantity(AbstractFloat, Meter)}
    y::Vector{quantity(AbstractFloat, Meter)}
    z::Vector{quantity(AbstractFloat, Meter)}
    t::Vector{quantity(AbstractFloat, Second)}
    method::AbstractString
    info::Dict
    coord_system::AbstractString

    function VolumeImage{F<:AbstractFloat, S<:AbstractString}(data::Array{F, 4}, units::S,
                         x::Vector{F}, y::Vector{F}, z::Vector{F}, t::Vector{F},
                         method::S, info::Dict, coord_system::S)

        @assert size(data, 1) == length(x)
        @assert size(data, 2) == length(y)
        @assert size(data, 3) == length(z)
        @assert size(data, 4) == length(t)

        new(data, units, x, y, z, t, method, info, coord_system)
    end

    function VolumeImage{F<:AbstractFloat, S<:AbstractString, Met<:quantity(AbstractFloat, Meter),
                         Sec<:quantity(AbstractFloat, Second)}(data::Array{F, 4}, units::S,
                         x::Vector{Met}, y::Vector{Met}, z::Vector{Met}, t::Vector{Sec},
                         method::S, info::Dict, coord_system::S)

        @assert size(data, 1) == length(x)
        @assert size(data, 2) == length(y)
        @assert size(data, 3) == length(z)
        @assert size(data, 4) == length(t)

        new(data, units, x, y, z, t, method, info, coord_system)
    end

    function VolumeImage{F<:AbstractFloat, S<:AbstractString}(data::Vector{F}, units::S,
                         x::Vector{F}, y::Vector{F}, z::Vector{F}, t::Vector{F},
                         method::S, info::Dict, coord_system::S)

        @assert length(x) == length(data)
        @assert length(y) == length(data)
        @assert length(z) == length(data)
        @assert length(t) == length(data)

        newX = sort(unique(x))
        newY = sort(unique(y))
        newZ = sort(unique(z))
        newT = sort(unique(t))

        L = zeros(typeof(data[1]), length(newX), length(newY), length(newZ), length(newT))

        for idx in 1:length(data)
            idxX = findfirst(newX, x[idx])
            idxY = findfirst(newY, y[idx])
            idxZ = findfirst(newZ, z[idx])
            idxT = findfirst(newT, t[idx])
            L[idxX, idxY, idxZ, idxT] = data[idx]
        end

        new(L, units, newX, newY, newZ, newT, method, info, coord_system)
    end

end

#
# Basic operations
# ----------------
#

import Base: show

function Base.show(io::IO, vi::VolumeImage)

    println(io, "VolumeImage of method $(vi.method) and units $(vi.units)")
    println(io, "  Spanning x: $(vi.x[1]) : $(vi.x[end])")
    println(io, "  Spanning y: $(vi.y[1]) : $(vi.y[end])")
    println(io, "  Spanning z: $(vi.z[1]) : $(vi.z[end])")
    println(io, "  Spanning t: $(vi.t[1]) : $(vi.t[end])")

    if haskey(vi.info, "Regularisation")
        println(io, "  Regularisation: $(vi.info["Regularisation"])")
    end
    if haskey(vi.info, "NormalisationConstant")
        println(io, "  Image has been normalised")
    end
end


module FovWrapper

using JavaCall

const AbstractFieldOfView = @jimport org.orekit.geometry.fov.AbstractFieldOfView
const CircularFieldOfView = @jimport org.orekit.geometry.fov.CircularFieldOfView
const Class = @jimport java.lang.Class
const DoubleDihedraFieldOfView = @jimport org.orekit.geometry.fov.DoubleDihedraFieldOfView
const EllipticalFieldOfView = @jimport org.orekit.geometry.fov.EllipticalFieldOfView
const FieldOfView = @jimport org.orekit.geometry.fov.FieldOfView
const JString = @jimport java.lang.String
const List = @jimport java.util.List
const Object = @jimport java.lang.Object
const OneAxisEllipsoid = @jimport org.orekit.bodies.OneAxisEllipsoid
const PolygonalFieldOfView = @jimport org.orekit.geometry.fov.PolygonalFieldOfView
const PolygonalFieldOfView_DefiningConeType = @jimport org.orekit.geometry.fov.PolygonalFieldOfView$DefiningConeType
const SmoothFieldOfView = @jimport org.orekit.geometry.fov.SmoothFieldOfView
const SphericalPolygonsSet = @jimport org.hipparchus.geometry.spherical.twod.SphericalPolygonsSet
const Transform = @jimport org.orekit.frames.Transform
const Vector3D = @jimport org.hipparchus.geometry.euclidean.threed.Vector3D
const VisibilityTrigger = @jimport org.orekit.propagation.events.VisibilityTrigger

export AbstractFieldOfView
export CircularFieldOfView
export DoubleDihedraFieldOfView
export EllipticalFieldOfView
export FieldOfView
export PolygonalFieldOfView
export SmoothFieldOfView
export equals
export get_center
export get_class
export get_focus1
export get_focus2
export get_footprint
export get_half_aperture
export get_half_aperture_along_x
export get_half_aperture_along_y
export get_margin
export get_x
export get_y
export get_z
export get_zone
export hash_code
export notify
export notify_all
export offset_from_boundary
export project_to_boundary
export to_string
export wait

include("abstract_field_of_view.jl")
include("circular_field_of_view.jl")
include("double_dihedra_field_of_view.jl")
include("elliptical_field_of_view.jl")
include("field_of_view.jl")
include("polygonal_field_of_view.jl")
include("smooth_field_of_view.jl")

end


__precompile__()

module GadgetIO

    using Printf

    function output_time(t1, t2)
        return @sprintf("%0.3e", Float64((t2-t1))*1.e-9)
    end

    """
        AbstractGadgetHeader

    Abstract type to unify snapshot and subfind headers.
    """
    abstract type AbstractGadgetHeader end

    # functions to read snapshots
    include(joinpath("read_snapshot", "utility", "domain_slices.jl"))
    include(joinpath("read_snapshot", "read_header.jl"))
    include(joinpath("read_snapshot", "read_info.jl"))
    include(joinpath("read_snapshot", "utility", "allocate_dict.jl"))
    include(joinpath("read_snapshot", "utility", "snapshot_utilities.jl"))
    include(joinpath("read_snapshot", "read_format_1.jl"))
    include(joinpath("read_snapshot", "read_format_2.jl"))
    include(joinpath("read_snapshot", "read_snapshot.jl"))
    include(joinpath("read_subfind",  "read_subfind.jl"))
    include(joinpath("read_subfind",  "read_halo_prop.jl"))
    include(joinpath("read_subfind",  "filter_subfind.jl"))
    include(joinpath("read_snapshot", "particles_in_box",  "read_key_files.jl"))
    include(joinpath("read_snapshot", "particles_in_box",  "peano_hilbert.jl"))
    include(joinpath("read_snapshot", "particles_in_box",  "utility.jl"))
    include(joinpath("read_snapshot", "particles_in_box",  "read_particle_in_box.jl"))
    include(joinpath("read_snapshot", "particles_in_geometry", "abstract_geometry.jl"))
    include(joinpath("read_snapshot", "particles_in_geometry", "cylinder.jl"))
    include(joinpath("read_snapshot", "particles_in_geometry", "sphere.jl"))
    include(joinpath("read_snapshot", "particles_in_geometry", "cube.jl"))
    include(joinpath("read_snapshot", "particles_in_geometry", "read_particles_in_geometry.jl"))
    include(joinpath("read_snapshot", "particles_in_halo", "read_particles_by_id.jl"))
    include(joinpath("read_snapshot", "particles_in_halo", "read_particles_in_halo.jl"))
    include(joinpath("read_snapshot", "distributed_files", "find_read_positions.jl"))
    include(joinpath("read_snapshot", "distributed_files", "read_distributed_files.jl"))

    # functions to write snapshots
    include(joinpath(dirname(@__FILE__), "write_snapshot", "write_snap.jl"))

    export  AbstractGadgetHeader,
            SnapshotHeader, SubfindHeader,
            InfoLine,       # types
            head_to_dict,
            snap_to_dict,
            head_to_obj,
            print_blocks,
            read_info,
            block_present,
            read_snap,
            read_block,      # similar to readnew.pro by Klaus Dolag
            read_header,

            # large simulations
            read_particles_in_box,
            read_particles_in_volume,
            read_particles_in_geometry,
            read_particles_in_halo,
            get_index_list,
            get_npart_to_read,
            filter_ids,
            read_blocks_over_all_files,
            find_read_positions,
            save_read_positions,
            load_read_positions,

            # geometries
            AbstractGadgetGeometry,
            GadgetCylinder,
            GadgetSphere,
            GadgetCube,

            # domain slices
            filter_cube,
            filter_cylinder,

            # subfind read
            HaloID,
            SubfindHeader,
            read_subfind_header,
            read_subfind,
            find_most_massive_halo,
            filter_subfind,
            read_ids_in_halo,
            read_particles_by_id,
            read_halo_prop,
            read_halo_prop_and_id,

            # write snapshot functions
            write_header,
            write_block,
            write_info_block

end # module


include("TwoPartCost.jl")
include("ThreePartCost.jl")
include("StorageManagementCost.jl")
include("MarketBidCost.jl")
include("MultiStartCost.jl")
include("Area.jl")
include("LoadZone.jl")
include("Bus.jl")
include("Arc.jl")
include("Line.jl")
include("MonitoredLine.jl")
include("PhaseShiftingTransformer.jl")
include("TapTransformer.jl")
include("Transformer2W.jl")
include("HVDCLine.jl")
include("VSCDCLine.jl")
include("InterruptibleLoad.jl")
include("FixedAdmittance.jl")
include("PowerLoad.jl")
include("ExponentialLoad.jl")
include("SingleCageInductionMachine.jl")
include("SimplifiedSingleCageInductionMachine.jl")
include("HydroEnergyReservoir.jl")
include("HydroDispatch.jl")
include("HydroPumpedStorage.jl")
include("RenewableDispatch.jl")
include("RenewableFix.jl")
include("ThermalStandard.jl")
include("ThermalMultiStart.jl")
include("GenericBattery.jl")
include("BatteryEMS.jl")
include("StaticReserve.jl")
include("StaticReserveNonSpinning.jl")
include("StaticReserveGroup.jl")
include("ReserveDemandCurve.jl")
include("VariableReserve.jl")
include("VariableReserveNonSpinning.jl")
include("AGC.jl")
include("Transfer.jl")
include("AVRFixed.jl")
include("AVRSimple.jl")
include("SEXS.jl")
include("ESDC1A.jl")
include("ESDC2A.jl")
include("IEEET1.jl")
include("AVRTypeI.jl")
include("AVRTypeII.jl")
include("SCRX.jl")
include("ESAC1A.jl")
include("EXAC1A.jl")
include("EXAC1.jl")
include("EXAC2.jl")
include("ESAC6A.jl")
include("ESST1A.jl")
include("EXPIC1.jl")
include("ESST4B.jl")
include("EXST1.jl")
include("EX4VSA.jl")
include("BaseMachine.jl")
include("RoundRotorMachine.jl")
include("SalientPoleMachine.jl")
include("AndersonFouadMachine.jl")
include("FullMachine.jl")
include("MarconatoMachine.jl")
include("OneDOneQMachine.jl")
include("SimpleAFMachine.jl")
include("SimpleFullMachine.jl")
include("SimpleMarconatoMachine.jl")
include("PSSFixed.jl")
include("PSSSimple.jl")
include("IEEEST.jl")
include("STAB1.jl")
include("SingleMass.jl")
include("FiveMassShaft.jl")
include("TGFixed.jl")
include("GasTG.jl")
include("GeneralGovModel.jl")
include("SteamTurbineGov1.jl")
include("HydroTurbineGov.jl")
include("IEEETurbineGov1.jl")
include("TGTypeI.jl")
include("TGTypeII.jl")
include("AverageConverter.jl")
include("RenewableEnergyConverterTypeA.jl")
include("FixedDCSource.jl")
include("ZeroOrderBESS.jl")
include("LCLFilter.jl")
include("LCFilter.jl")
include("RLFilter.jl")
include("KauraPLL.jl")
include("ReducedOrderPLL.jl")
include("FixedFrequency.jl")
include("VirtualInertia.jl")
include("ActivePowerDroop.jl")
include("ActivePowerPI.jl")
include("ActiveRenewableControllerAB.jl")
include("ReactiveRenewableControllerAB.jl")
include("ReactivePowerDroop.jl")
include("ReactivePowerPI.jl")
include("VoltageModeControl.jl")
include("CurrentModeControl.jl")
include("RECurrentControlB.jl")
include("AggregateDistributedGenerationA.jl")
include("Source.jl")
include("PeriodicVariableSource.jl")
include("GenericDER.jl")

export get_A
export get_A1
export get_A2
export get_A3
export get_A4
export get_A5
export get_A6
export get_AT
export get_A_set
export get_Accel
export get_Ae
export get_At
export get_B
export get_B_shunt
export get_Be
export get_Brkpt
export get_C
export get_D
export get_DB_h
export get_DB_l
export get_D_12
export get_D_23
export get_D_34
export get_D_45
export get_D_T
export get_D_dn
export get_D_ex
export get_D_hp
export get_D_ip
export get_D_lp
export get_D_turb
export get_D_up
export get_Dm
export get_E_lim
export get_E_sat
export get_Efd_lim
export get_FES_lim
export get_FRT_pnts
export get_Freq_Flag
export get_Ftrip_Flag
export get_G
export get_Gen_Flag
export get_H
export get_H_ex
export get_H_hp
export get_H_ip
export get_H_lim
export get_H_lp
export get_I_lr
export get_I_max
export get_Iflim
export get_Io_lim
export get_Iq_lim
export get_Iqinj_lim
export get_Iqr_lim
export get_Iqr_lims
export get_K
export get_K0
export get_K1
export get_K2
export get_K3
export get_K4
export get_K5
export get_K6
export get_K7
export get_K8
export get_KT
export get_K_c
export get_K_d
export get_K_ex
export get_K_hp
export get_K_hv
export get_K_i
export get_K_ig
export get_K_im
export get_K_ip
export get_K_ir
export get_K_lp
export get_K_lr
export get_K_p
export get_K_pg
export get_K_pm
export get_K_pr
export get_K_qi
export get_K_qp
export get_K_qv
export get_K_turb
export get_K_vi
export get_K_vp
export get_K_ω
export get_Ka
export get_Kb
export get_Kc
export get_Kd
export get_Kd_gov
export get_Ke
export get_Kf
export get_Kg
export get_Kh
export get_Ki
export get_Ki_gov
export get_Ki_load
export get_Ki_mw
export get_Ki_p
export get_Ki_q
export get_Kig
export get_Kip
export get_Kiq
export get_Kl
export get_Kp
export get_Kp_gov
export get_Kp_load
export get_Kp_p
export get_Kp_q
export get_Kpg
export get_Kpp
export get_Kpq
export get_Ks
export get_Kt
export get_Kv
export get_L_1d
export get_L_1q
export get_L_ad
export get_L_aq
export get_L_d
export get_L_f1d
export get_L_ff
export get_L_q
export get_Ld_ref
export get_Ls_lim
export get_Lv_pnts
export get_Lvpl1
export get_Lvpl_sw
export get_Oel_lim
export get_PF_Flag
export get_PQ_Flag
export get_PSS_flags
export get_P_lim
export get_P_lim_inner
export get_P_ref
export get_PerOp_Flag
export get_Pf_Flag
export get_Pfa_ref
export get_Q_Flag
export get_Q_lim
export get_Q_lim_inner
export get_Q_ref
export get_Qref_Flag
export get_R
export get_R_1d
export get_R_1q
export get_R_c
export get_R_close
export get_R_f
export get_R_lim
export get_R_open
export get_R_r
export get_R_s
export get_R_source
export get_R_th
export get_Recon_Flag
export get_Ref_Flag
export get_Rp
export get_Rrpwr
export get_Rselect
export get_SOC_ini
export get_SOC_lim
export get_Se
export get_Spar
export get_T
export get_T1
export get_T1T3
export get_T2
export get_T2T4
export get_T3
export get_T4
export get_T5
export get_T6
export get_T7
export get_TFRT_pnts
export get_TVRT_pnts
export get_T_AA
export get_T_act
export get_T_eng
export get_T_fltr
export get_T_ft
export get_T_fv
export get_T_g
export get_T_iq
export get_T_p
export get_T_pord
export get_T_rate
export get_T_rv
export get_Ta
export get_Ta_2
export get_Ta_3
export get_Ta_4
export get_Ta_Tb
export get_Tb
export get_Tb1
export get_Tc
export get_Tc1
export get_Td0_p
export get_Td0_pp
export get_Td_gov
export get_Te
export get_Tf
export get_Tf_1
export get_Tf_2
export get_Tf_load
export get_Tg
export get_Th
export get_Tj
export get_Tk
export get_Tp
export get_Tpelec
export get_Tpord
export get_Tq0_p
export get_Tq0_pp
export get_Tr
export get_Trf
export get_Trv
export get_Ts
export get_Tsa
export get_Tsb
export get_Tv
export get_Tw
export get_U0
export get_UEL_flags
export get_U_c
export get_VB_max
export get_VC_Flag
export get_VELM
export get_VES_lim
export get_VFE_lim
export get_VH_max
export get_VRT_pnts
export get_VV_pnts
export get_V_Flag
export get_V_frz
export get_V_lim
export get_V_lr
export get_V_pss
export get_V_ref
export get_V_ref0
export get_Va_lim
export get_Vcl
export get_Vcu
export get_Vdc_ref
export get_Vdip_lim
export get_Vf
export get_Vi_lim
export get_Vm_lim
export get_Vo_lim
export get_Vpr
export get_Vr_lim
export get_Vrfrac
export get_Vtrip_Flag
export get_Wf_nl
export get_X_ad
export get_X_aq
export get_X_c
export get_X_lr
export get_X_ls
export get_X_m
export get_X_p
export get_X_rr
export get_X_source
export get_X_ss
export get_X_th
export get_Xd
export get_Xd_p
export get_Xd_pp
export get_Xl
export get_Xq
export get_Xq_p
export get_Xq_pp
export get_Y
export get_Zerox
export get_active_power
export get_active_power_coefficient
export get_active_power_flow
export get_active_power_limits
export get_active_power_limits_from
export get_active_power_limits_pump
export get_active_power_limits_to
export get_ancillary_services
export get_angle
export get_angle_limits
export get_arc
export get_area
export get_available
export get_b
export get_base_power
export get_base_voltage
export get_battery_resistance
export get_battery_voltage
export get_bias
export get_branch_id_control
export get_bus
export get_bus_control
export get_bustype
export get_cf
export get_contributing_services
export get_conversion_factor
export get_d
export get_dP_lim
export get_db
export get_dbd_pnts
export get_dc_dc_inductor
export get_dc_link_capacitance
export get_delta_t
export get_dynamic_injector
export get_e_lim
export get_efficiency
export get_energy_shortage_cost
export get_energy_surplus_cost
export get_eq_p
export get_ext
export get_f
export get_fdbd_pnts
export get_fe_lim
export get_fh
export get_fixed
export get_fl
export get_flow_limits
export get_frequency
export get_from
export get_from_branch_control
export get_fs
export get_fuel
export get_fuel_flag
export get_gate_position_limits
export get_inflow
export get_initial_ace
export get_initial_energy
export get_initial_storage
export get_input_active_power_limits
export get_input_code
export get_internal_angle
export get_internal_angle_bias
export get_internal_angle_coefficients
export get_internal_angle_frequencies
export get_internal_voltage
export get_internal_voltage_bias
export get_internal_voltage_coefficients
export get_internal_voltage_frequencies
export get_inv_d_fluxlink
export get_inv_q_fluxlink
export get_inverter_firing_angle
export get_inverter_tap_limits
export get_inverter_xrc
export get_kWh_Cap
export get_kad
export get_kd
export get_kffi
export get_kffv
export get_ki_pll
export get_kic
export get_kii
export get_kiv
export get_kp_pll
export get_kpc
export get_kpi
export get_kpv
export get_kq
export get_kω
export get_lf
export get_lg
export get_load_response
export get_load_zone
export get_loss
export get_lv
export get_magnitude
export get_max_active_power
export get_max_reactive_power
export get_model
export get_must_run
export get_n_states
export get_name
export get_no_load
export get_number
export get_operation_cost
export get_outflow
export get_output_active_power_limits
export get_peak_active_power
export get_peak_reactive_power
export get_power_factor
export get_power_trajectory
export get_primary_shunt
export get_prime_mover
export get_pump_efficiency
export get_q_nl
export get_r
export get_ramp_limits
export get_ramp_limits_pump
export get_rate
export get_rated_current
export get_rated_voltage
export get_rating
export get_rating_pump
export get_rc_rfd
export get_reactive_power
export get_reactive_power_coefficient
export get_reactive_power_flow
export get_reactive_power_limits
export get_reactive_power_limits_from
export get_reactive_power_limits_pump
export get_reactive_power_limits_to
export get_rectifier_firing_angle
export get_rectifier_tap_limits
export get_rectifier_xrc
export get_remote_bus_control
export get_requirement
export get_rf
export get_rg
export get_rrpwr
export get_rv
export get_saturation_coeffs
export get_services
export get_shut_down
export get_speed_error_signal
export get_start_time_limits
export get_start_types
export get_start_up
export get_state_of_charge_limits
export get_states
export get_states_types
export get_status
export get_storage_capacity
export get_storage_target
export get_switch
export get_tF_delay
export get_tV_delay
export get_tap
export get_tfh
export get_tfl
export get_time_at_status
export get_time_frame
export get_time_limits
export get_time_limits_pump
export get_time_series_container
export get_to
export get_to_branch_control
export get_valve_position_limits
export get_variable
export get_vh_pnts
export get_vl_pnts
export get_voltage
export get_voltage_limits
export get_x
export get_α
export get_γ_d1
export get_γ_d2
export get_γ_q1
export get_γ_q2
export get_γ_qd
export get_γd
export get_γq
export get_θp
export get_θp_rad
export get_τ_limits
export get_τ_ref
export get_ω_lp
export get_ωad
export get_ωf
export get_ωz
export set_A!
export set_A1!
export set_A2!
export set_A3!
export set_A4!
export set_A5!
export set_A6!
export set_AT!
export set_A_set!
export set_Accel!
export set_Ae!
export set_At!
export set_B!
export set_B_shunt!
export set_Be!
export set_Brkpt!
export set_C!
export set_D!
export set_DB_h!
export set_DB_l!
export set_D_12!
export set_D_23!
export set_D_34!
export set_D_45!
export set_D_T!
export set_D_dn!
export set_D_ex!
export set_D_hp!
export set_D_ip!
export set_D_lp!
export set_D_turb!
export set_D_up!
export set_Dm!
export set_E_lim!
export set_E_sat!
export set_Efd_lim!
export set_FES_lim!
export set_FRT_pnts!
export set_Freq_Flag!
export set_Ftrip_Flag!
export set_G!
export set_Gen_Flag!
export set_H!
export set_H_ex!
export set_H_hp!
export set_H_ip!
export set_H_lim!
export set_H_lp!
export set_I_lr!
export set_I_max!
export set_Iflim!
export set_Io_lim!
export set_Iq_lim!
export set_Iqinj_lim!
export set_Iqr_lim!
export set_Iqr_lims!
export set_K!
export set_K0!
export set_K1!
export set_K2!
export set_K3!
export set_K4!
export set_K5!
export set_K6!
export set_K7!
export set_K8!
export set_KT!
export set_K_c!
export set_K_d!
export set_K_ex!
export set_K_hp!
export set_K_hv!
export set_K_i!
export set_K_ig!
export set_K_im!
export set_K_ip!
export set_K_ir!
export set_K_lp!
export set_K_lr!
export set_K_p!
export set_K_pg!
export set_K_pm!
export set_K_pr!
export set_K_qi!
export set_K_qp!
export set_K_qv!
export set_K_turb!
export set_K_vi!
export set_K_vp!
export set_K_ω!
export set_Ka!
export set_Kb!
export set_Kc!
export set_Kd!
export set_Kd_gov!
export set_Ke!
export set_Kf!
export set_Kg!
export set_Kh!
export set_Ki!
export set_Ki_gov!
export set_Ki_load!
export set_Ki_mw!
export set_Ki_p!
export set_Ki_q!
export set_Kig!
export set_Kip!
export set_Kiq!
export set_Kl!
export set_Kp!
export set_Kp_gov!
export set_Kp_load!
export set_Kp_p!
export set_Kp_q!
export set_Kpg!
export set_Kpp!
export set_Kpq!
export set_Ks!
export set_Kt!
export set_Kv!
export set_L_1d!
export set_L_1q!
export set_L_ad!
export set_L_aq!
export set_L_d!
export set_L_f1d!
export set_L_ff!
export set_L_q!
export set_Ld_ref!
export set_Ls_lim!
export set_Lv_pnts!
export set_Lvpl1!
export set_Lvpl_sw!
export set_Oel_lim!
export set_PF_Flag!
export set_PQ_Flag!
export set_PSS_flags!
export set_P_lim!
export set_P_lim_inner!
export set_P_ref!
export set_PerOp_Flag!
export set_Pf_Flag!
export set_Pfa_ref!
export set_Q_Flag!
export set_Q_lim!
export set_Q_lim_inner!
export set_Q_ref!
export set_Qref_Flag!
export set_R!
export set_R_1d!
export set_R_1q!
export set_R_c!
export set_R_close!
export set_R_f!
export set_R_lim!
export set_R_open!
export set_R_r!
export set_R_s!
export set_R_source!
export set_R_th!
export set_Recon_Flag!
export set_Ref_Flag!
export set_Rp!
export set_Rrpwr!
export set_Rselect!
export set_SOC_ini!
export set_SOC_lim!
export set_Se!
export set_Spar!
export set_T!
export set_T1!
export set_T1T3!
export set_T2!
export set_T2T4!
export set_T3!
export set_T4!
export set_T5!
export set_T6!
export set_T7!
export set_TFRT_pnts!
export set_TVRT_pnts!
export set_T_AA!
export set_T_act!
export set_T_eng!
export set_T_fltr!
export set_T_ft!
export set_T_fv!
export set_T_g!
export set_T_iq!
export set_T_p!
export set_T_pord!
export set_T_rate!
export set_T_rv!
export set_Ta!
export set_Ta_2!
export set_Ta_3!
export set_Ta_4!
export set_Ta_Tb!
export set_Tb!
export set_Tb1!
export set_Tc!
export set_Tc1!
export set_Td0_p!
export set_Td0_pp!
export set_Td_gov!
export set_Te!
export set_Tf!
export set_Tf_1!
export set_Tf_2!
export set_Tf_load!
export set_Tg!
export set_Th!
export set_Tj!
export set_Tk!
export set_Tp!
export set_Tpelec!
export set_Tpord!
export set_Tq0_p!
export set_Tq0_pp!
export set_Tr!
export set_Trf!
export set_Trv!
export set_Ts!
export set_Tsa!
export set_Tsb!
export set_Tv!
export set_Tw!
export set_U0!
export set_UEL_flags!
export set_U_c!
export set_VB_max!
export set_VC_Flag!
export set_VELM!
export set_VES_lim!
export set_VFE_lim!
export set_VH_max!
export set_VRT_pnts!
export set_VV_pnts!
export set_V_Flag!
export set_V_frz!
export set_V_lim!
export set_V_lr!
export set_V_pss!
export set_V_ref!
export set_V_ref0!
export set_Va_lim!
export set_Vcl!
export set_Vcu!
export set_Vdc_ref!
export set_Vdip_lim!
export set_Vf!
export set_Vi_lim!
export set_Vm_lim!
export set_Vo_lim!
export set_Vpr!
export set_Vr_lim!
export set_Vrfrac!
export set_Vtrip_Flag!
export set_Wf_nl!
export set_X_ad!
export set_X_aq!
export set_X_c!
export set_X_lr!
export set_X_ls!
export set_X_m!
export set_X_p!
export set_X_rr!
export set_X_source!
export set_X_ss!
export set_X_th!
export set_Xd!
export set_Xd_p!
export set_Xd_pp!
export set_Xl!
export set_Xq!
export set_Xq_p!
export set_Xq_pp!
export set_Y!
export set_Zerox!
export set_active_power!
export set_active_power_coefficient!
export set_active_power_flow!
export set_active_power_limits!
export set_active_power_limits_from!
export set_active_power_limits_pump!
export set_active_power_limits_to!
export set_ancillary_services!
export set_angle!
export set_angle_limits!
export set_arc!
export set_area!
export set_available!
export set_b!
export set_base_power!
export set_base_voltage!
export set_battery_resistance!
export set_battery_voltage!
export set_bias!
export set_branch_id_control!
export set_bus!
export set_bus_control!
export set_bustype!
export set_cf!
export set_contributing_services!
export set_conversion_factor!
export set_d!
export set_dP_lim!
export set_db!
export set_dbd_pnts!
export set_dc_dc_inductor!
export set_dc_link_capacitance!
export set_delta_t!
export set_dynamic_injector!
export set_e_lim!
export set_efficiency!
export set_energy_shortage_cost!
export set_energy_surplus_cost!
export set_eq_p!
export set_ext!
export set_f!
export set_fdbd_pnts!
export set_fe_lim!
export set_fh!
export set_fixed!
export set_fl!
export set_flow_limits!
export set_frequency!
export set_from!
export set_from_branch_control!
export set_fs!
export set_fuel!
export set_fuel_flag!
export set_gate_position_limits!
export set_inflow!
export set_initial_ace!
export set_initial_energy!
export set_initial_storage!
export set_input_active_power_limits!
export set_input_code!
export set_internal_angle!
export set_internal_angle_bias!
export set_internal_angle_coefficients!
export set_internal_angle_frequencies!
export set_internal_voltage!
export set_internal_voltage_bias!
export set_internal_voltage_coefficients!
export set_internal_voltage_frequencies!
export set_inv_d_fluxlink!
export set_inv_q_fluxlink!
export set_inverter_firing_angle!
export set_inverter_tap_limits!
export set_inverter_xrc!
export set_kWh_Cap!
export set_kad!
export set_kd!
export set_kffi!
export set_kffv!
export set_ki_pll!
export set_kic!
export set_kii!
export set_kiv!
export set_kp_pll!
export set_kpc!
export set_kpi!
export set_kpv!
export set_kq!
export set_kω!
export set_lf!
export set_lg!
export set_load_response!
export set_load_zone!
export set_loss!
export set_lv!
export set_magnitude!
export set_max_active_power!
export set_max_reactive_power!
export set_model!
export set_must_run!
export set_n_states!
export set_name!
export set_no_load!
export set_number!
export set_operation_cost!
export set_outflow!
export set_output_active_power_limits!
export set_peak_active_power!
export set_peak_reactive_power!
export set_power_factor!
export set_power_trajectory!
export set_primary_shunt!
export set_prime_mover!
export set_pump_efficiency!
export set_q_nl!
export set_r!
export set_ramp_limits!
export set_ramp_limits_pump!
export set_rate!
export set_rated_current!
export set_rated_voltage!
export set_rating!
export set_rating_pump!
export set_rc_rfd!
export set_reactive_power!
export set_reactive_power_coefficient!
export set_reactive_power_flow!
export set_reactive_power_limits!
export set_reactive_power_limits_from!
export set_reactive_power_limits_pump!
export set_reactive_power_limits_to!
export set_rectifier_firing_angle!
export set_rectifier_tap_limits!
export set_rectifier_xrc!
export set_remote_bus_control!
export set_requirement!
export set_rf!
export set_rg!
export set_rrpwr!
export set_rv!
export set_saturation_coeffs!
export set_services!
export set_shut_down!
export set_speed_error_signal!
export set_start_time_limits!
export set_start_types!
export set_start_up!
export set_state_of_charge_limits!
export set_states!
export set_states_types!
export set_status!
export set_storage_capacity!
export set_storage_target!
export set_switch!
export set_tF_delay!
export set_tV_delay!
export set_tap!
export set_tfh!
export set_tfl!
export set_time_at_status!
export set_time_frame!
export set_time_limits!
export set_time_limits_pump!
export set_time_series_container!
export set_to!
export set_to_branch_control!
export set_valve_position_limits!
export set_variable!
export set_vh_pnts!
export set_vl_pnts!
export set_voltage!
export set_voltage_limits!
export set_x!
export set_α!
export set_γ_d1!
export set_γ_d2!
export set_γ_q1!
export set_γ_q2!
export set_γ_qd!
export set_γd!
export set_γq!
export set_θp!
export set_θp_rad!
export set_τ_limits!
export set_τ_ref!
export set_ω_lp!
export set_ωad!
export set_ωf!
export set_ωz!


module TestExamples2DEulerMulticomponent

using Test
using Trixi

include("test_trixi.jl")

# pathof(Trixi) returns /path/to/Trixi/src/Trixi.jl, dirname gives the parent directory
EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "2d")

@testset "Compressible Euler Multicomponent" begin
  # NOTE: Some of the L2/Linf errors are comparably large. This is due to the fact that some of the
  #       simulations are set up with dimensional states. For example, the reference pressure in SI
  #       units is 101325 Pa, i.e., pressure has values of O(10^5)

  @trixi_testset "elixir_eulermulti_shock_bubble.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_shock_bubble.jl"),
      l2   = [7.38219722e+01, 8.80169627e-01, 5.79496021e+04, 1.83101743e-01, 1.11794879e-02],
      linf = [1.96834444e+02, 7.19013908e+00, 1.58095793e+05, 8.19793567e-01, 8.00831673e-02],
      tspan = (0.0, 0.001))
  end

  @trixi_testset "elixir_eulermulti_shock_bubble_3components.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_shock_bubble_3components.jl"),
      l2   = [6.69469420e+01, 1.24137635e+00, 5.19736818e+04, 1.67537081e-01, 6.94144768e-03, 7.33198795e-02],
      linf = [2.14068118e+02, 1.82870581e+01, 1.93352191e+05, 1.02911816e+00, 7.00111358e-02, 9.34275213e-01],
      tspan = (0.0, 0.001))
  end

  @trixi_testset "elixir_eulermulti_shock_bubble_7components.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_shock_bubble_7components.jl"),
    l2   = [6.70895845e+01, 1.50857110e+00, 5.35958153e+04, 1.88695032e-01, 5.30253136e-03, 3.89776034e-02, 7.99227155e-02, 3.50734339e-03, 3.06391963e-02, 1.17354254e-01],
    linf = [2.27410552e+02, 1.95446320e+01, 2.96650723e+05, 1.09905431e+00, 4.40228926e-02, 5.08315400e-01, 1.06801398e+00, 1.76102931e-02, 4.01158590e-01, 1.47984635e+00],
    tspan = (0.0, 0.001))
  end

  @trixi_testset "elixir_eulermulti_ec.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_ec.jl"),
      l2   = [5.01948074e-02, 5.02023248e-02, 2.25886833e-01, 6.17286464e-02],
      linf = [3.06937711e-01, 3.06807092e-01, 1.06295287e+00, 2.98135725e-01])
  end

  @trixi_testset "elixir_eulermulti_es.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_es.jl"),
      l2   = [4.96608585e-02, 4.96617550e-02, 2.24265059e-01, 4.08685600e-03, 8.17371200e-03, 1.63474240e-02, 3.26948480e-02],
      linf = [2.48820395e-01, 2.48311640e-01, 9.35790843e-01, 1.72574990e-02, 3.45149980e-02, 6.90299959e-02, 1.38059992e-01])
  end

  @trixi_testset "elixir_eulermulti_eoc_ec.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_eoc_ec.jl"),
      l2   = [1.15269058e-04, 1.15269058e-04, 1.66578747e-04, 4.61097337e-05, 9.22194673e-05],
      linf = [5.05618608e-04, 5.05618608e-04, 7.87591809e-04, 2.06476960e-04, 4.12953919e-04])
  end

  @trixi_testset "elixir_eulermulti_eoc_es.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_eoc_es.jl"),
      l2   = [1.74779183e-06, 1.74779183e-06, 5.62550396e-06, 6.15682712e-07, 1.23136542e-06],
      linf = [1.51966473e-05, 1.51966473e-05, 5.51065526e-05, 5.58096521e-06, 1.11619304e-05])
  end
end

end # module


################################################################################
#
# Routines for the the evaluation and gradient computation of FWI
#
################################################################################

export objFWI, objFWI!, objFWI_rawinput

### Optim.jl-adapted wrapper

function objFWI!(
	F, Gm, m::Array{R, 2},
	n::NTuple{2, Int64}, d::NTuple{2, R}, o::NTuple{2, R},
	wav::judiVector,
	dat::judiVector;
	opt = Options(space_order=SPACE_ORDER),
	dt_comp::Union{Nothing, Array{Any, 1}} = nothing,
	Filter::Union{Nothing, Array{C, 1}, Array{Array{C, 1}, 1}} = nothing,
	gradmprec_fun = g->g)

	if Gm == nothing
		mode = "eval"
	else
		mode = "grad"
	end
	argout = objFWI(m, n, d, o, wav, dat; opt = opt, mode = mode, dt_comp = dt_comp, Filter = Filter, gradmprec_fun = gradmprec_fun)
	if Gm != nothing
		fval = argout[1]
		Gm .= argout[2]
	else
		fval = argout
	end
	return fval

end

function objFWI!(
	F, Gm, m::Array{R, 2},
	model::Modelall,
	wav::judiVector,
	dat::judiVector;
	opt = Options(space_order=SPACE_ORDER),
	dt_comp::Union{Nothing, Array{Any, 1}} = nothing,
	Filter::Union{Nothing, Array{C, 1}, Array{Array{C, 1}, 1}} = nothing,
	gradmprec_fun = g->g)

	if Gm == nothing
		mode = "eval"
	else
		mode = "grad"
	end
	model.m = m
	argout = objFWI(m, model, wav, dat; opt = opt, mode = mode, dt_comp = dt_comp, Filter = Filter, gradmprec_fun = gradmprec_fun)
	if Gm != nothing
		fval = argout[1]
		Gm .= argout[2]
	else
		fval = argout
	end
	return fval

end

### Objective wrapper for JUDI data types

function objFWI(
	m::Modelall,
	wav::judiVector,
	dat::judiVector;
	opt = Options(space_order=SPACE_ORDER),
	mode = "eval", dt_comp::Union{Nothing, Array{Any, 1}} = nothing,
	Filter::Union{Nothing, Array{C, 1}, Array{Array{C, 1}, 1}} = nothing,
	gradmprec_fun = g->g)
	return objFWI_rawinput(m, wav.geometry, dat.geometry, wav.data, dat.data, 1:length(dat.data); opt = opt, mode = mode, dt_comp = dt_comp, Filter = Filter, gradmprec_fun = gradmprec_fun)
end

### Objective wrapper for array input

function objFWI(
	m::Array{R, 2}, n::NTuple{2, Int64}, d::NTuple{2, R}, o::NTuple{2, R},
	wav::judiVector, dat::judiVector;
	opt = Options(space_order=SPACE_ORDER),
	mode = "eval", dt_comp::Union{Nothing, Array{Any, 1}} = nothing,
	Filter::Union{Nothing, Array{C, 1}, Array{Array{C, 1}, 1}} = nothing,
	gradmprec_fun = g->g)

	return objFWI(Model(n, d, o, m), wav, dat; opt = opt, mode = mode, dt_comp = dt_comp, Filter = Filter, gradmprec_fun = gradmprec_fun)

end

function objFWI(
	m::Array{R, 2}, model::Modelall,
	wav::judiVector, dat::judiVector;
	opt = Options(space_order=SPACE_ORDER),
	mode = "eval", dt_comp::Union{Nothing, Array{Any, 1}} = nothing,
	Filter::Union{Nothing, Array{C, 1}, Array{Array{C, 1}, 1}} = nothing,
	gradmprec_fun = g->g)
	model.m = m
	return objFWI(model, wav, dat; opt = opt, mode = mode, dt_comp = dt_comp, Filter = Filter, gradmprec_fun = gradmprec_fun)

end

### Objective: serial/parallel implementation

## Serial implementation

function objFWI_rawinput(
	model_full::Modelall,
	src_geom::Geometry, rcv_geom::Geometry,
	src_data::Array{R, 2}, dat_data::Array{R, 2};
	opt = Options(space_order=SPACE_ORDER),
	mode = "eval", dt_comp::Union{Nothing, R} = nothing,
	Filter::Union{Nothing, Array{C, 1}} = nothing,
	gradmprec_fun = g->g)

	# Setting pre-defined absorbing layer size
	model_full.nb = NB

	# Load full geometry for out-of-core geometry containers
	typeof(rcv_geom) == GeometryOOC && (rcv_geom = Geometry(rcv_geom))
	typeof(src_geom) == GeometryOOC && (src_geom = Geometry(src_geom))
	length(model_full.n) == 3 ? dims = [3, 2, 1] : dims = [2, 1] # model dimensions for Python are (z,y,x) and (z,x)

	# Limit model to area with sources/receivers
	if opt.limit_m == true
		model = deepcopy(model_full)
		model = limit_model_to_receiver_area(src_geom, rcv_geom, model, opt.buffer_size)
	else
		model = model_full
	end

	# Set up Python model structure
	model_py = devito_model(model, "F", 1, opt, 0)

	# Remove receivers outside the modeling domain (otherwise leads to segmentation faults)
	rcv_geom = remove_out_of_bounds_receivers(rcv_geom, model)

	# Call to objective with Julia/Devito interface function
	return objFWI_jldevito(model_py, model.o, src_geom, rcv_geom, src_data, dat_data; opt = opt, mode = mode, dt_comp = dt_comp, Filter = Filter, gradmprec_fun = gradmprec_fun)

end

## Parallel implementation

function objFWI_rawinput(
	model::Modelall,
	src_geom::Geometry, rcv_geom::Geometry,
	src_data, dat_data,
	src_idx::UnitRange{Int64};
	opt = Options(space_order=SPACE_ORDER),
	mode = "eval", dt_comp::Union{Nothing, Array{Any, 1}} = nothing,
	Filter::Union{Nothing, Array{C, 1}, Array{Array{C, 1}, 1}} = nothing,
	gradmprec_fun = g->g)

	# Setup parallelization
	p = default_worker_pool()
	objFWI_rawinput_par = remote(TWRIdual.objFWI_rawinput)
	objFWI_rawinput = retry(objFWI_rawinput_par)

	# Initialize output
	nsrc = length(src_idx)
	results = Array{Any}(undef, nsrc)

	# Process shots from source channel asynchronously
	@sync begin
		for j = 1:nsrc

			# Local geometry for current position
			opt_loc = subsample(opt, j)
			src_geom_loc = subsample(src_geom, j)
			rcv_geom_loc = subsample(rcv_geom, j)

			# Selecting variables for current shot index
			src_data_loc = src_data[j]
			dat_data_loc = dat_data[j]
			if isa(Filter, Array{Array{C, 1}, 1})
				Filter_loc = Filter[j]
			else
				Filter_loc = Filter
			end
			if dt_comp == nothing
				dt_comp_loc = nothing
			else
				dt_comp_loc = dt_comp[j]
			end

			# Local result
			results[j] = @spawn objFWI_rawinput(model, src_geom_loc, rcv_geom_loc, src_data_loc, dat_data_loc, opt = opt_loc; mode = mode, dt_comp = dt_comp_loc, Filter = Filter_loc, gradmprec_fun = gradmprec_fun)

		end
	end

	# Aggregating results
	if mode == "eval"
		argout = fetch(results[1])
		for j = 2:nsrc
			argout += fetch(results[j])
		end
	elseif mode == "grad"
		arg = fetch(results[1])
		argout1 = arg[1]
		argout2 = arg[2]
		for j = 2:nsrc
			arg = fetch(results[j])
			argout1 += arg[1]
			argout2 += arg[2]
		end
		argout = (argout1, argout2)
	end
	return argout./nsrc

end

### Julia/devito call

function objFWI_jldevito(
	model_py::PyCall.PyObject,
	origin,
	src_geom::Geometry, rcv_geom::Geometry,
	src_data::Array{R, 2}, dat_data::Array{R, 2};
	opt = Options(space_order=SPACE_ORDER),
	mode = "eval", dt_comp::Union{Nothing, R} = nothing,
	Filter::Union{Nothing, Array{C, 1}} = nothing,
	gradmprec_fun = g->g)
	# Interpolate input data to computational grid
	if dt_comp == nothing
		dt_comp = R(model_py.critical_dt)
	end
	q_in = time_resample(src_data, src_geom, dt_comp)[1]
	dat_in = time_resample(dat_data, rcv_geom, dt_comp)[1]
	nt_comp = size(q_in, 1)
	nt_rcv = Int(trunc(rcv_geom.t[1]/dt_comp+1))

	# Set up coordinates with devito dimensions
	src_coords = setup_grid(src_geom, model_py.shape)
	rcv_coords = setup_grid(rcv_geom, model_py.shape)

	fwi_ops = load_codegen_FWI(model_py)
	# Computing output
	if mode == "eval"
		argout = pycall(fwi_ops.fwi_fun, R, src_coords, rcv_coords, q_in, dat_in, Filter, mode)
	elseif mode == "grad"
		argout = pycall(fwi_ops.fwi_fun, Tuple{R, Array{R, 2}}, src_coords, rcv_coords, q_in, dat_in, Filter, mode)
	end

	# Output post-processing
	if mode == "grad"
		gm = remove_padding(argout[2], model_py.nbl, true_adjoint = opt.sum_padding) # remove AB layers
		argout = (argout[1], gradmprec_fun(gm))
	end

	return argout

end

# Loader for python devito modeling
@cache function load_codegen_FWI(model)

	pushfirst!(PyVector(pyimport("sys")."path"), string(pwd(), "/src/TWRI_py/"))
	int = pyimport("wri_interface")
	return int.WRIInterface(model, SPACE_ORDER)
end


function guess_tid_offset()
    task = Task(nothing)
    l = nfields(task)
    ptr0 = UInt(pointer_from_objref(task))
    ptr1 = ptr0 + fieldoffset(Task, l) + sizeof(fieldtype(Task, l))
    imask = UInt(sizeof(Int16) - 1)
    ptr1 += imask
    ptr1 &= ~imask

    tid = rand(Int16(1):typemax(Int16))
    ans = ccall(:jl_set_task_tid, Cint, (Any, Cint), task, tid)
    @assert ans != 0
    @assert Threads.threadid(task) == tid + 1
    GC.@preserve task begin
        loaded = unsafe_load(Ptr{Int16}(ptr1))
        loaded == tid && return Int(ptr1 - ptr0)
    end
    error("failed to guess threadid offset")
end

if abspath(PROGRAM_FILE) == @__FILE__
    const TASK_TID_OFFSET = guess_tid_offset()
    include("taskhack_impl.jl")
    check_set_raw_tid()
    println(TASK_TID_OFFSET)
end


# To pass incomplete specifications, we provide the "only_"
# family of functions. It allows the users to simply provide
# fg! for optimization or fj! for solving systems of equations
# and have NLSolversBase wrap them up properly so "F" can be
# calculated on its own and the save for partial derivatives.
# Note, that for TwiceDifferentiable we cannot provide con-
# structors if h == nothing, as that requires automatic dif-
# fferentiation of some sort.
struct InplaceObjective{DF, FDF, FGH}
    df::DF
    fdf::FDF
    fgh::FGH
end
struct NotInplaceObjective{DF, FDF, FGH}
    df::DF
    fdf::FDF
    fgh::FGH
end
# Mutating version
only_fg!(fg)   = InplaceObjective(nothing, fg,      nothing)
only_fgh!(fgh) = InplaceObjective(nothing, nothing, fgh)
only_fj!(fj)   = InplaceObjective(nothing, fj,      nothing)

# Non-mutating version
only_fg(fg)  = NotInplaceObjective(nothing, fg,      nothing)
only_fj(fj)  = NotInplaceObjective(nothing, fj,      nothing)

only_g_and_fg(g, fg) = NotInplaceObjective(g, fg, nothing)
only_j_and_fj(j, fj) = NotInplaceObjective(j, fj, nothing)

df(t::Union{InplaceObjective, NotInplaceObjective}) = t.df
fdf(t::Union{InplaceObjective, NotInplaceObjective}) = t.fdf

# Mutating version
make_f(t::InplaceObjective, x, F::Real) = x -> fdf(t)(F, nothing, x)
make_f(t::InplaceObjective, x, F) =  (F, x) -> fdf(t)(F, nothing, x)
make_df(t::InplaceObjective, x, F) = (DF, x) -> fdf(t)(nothing, DF, x)
make_fdf(t::InplaceObjective, x, F::Real) = (G, x) -> fdf(t)(F, G, x)
make_fdf(t::InplaceObjective, x, F) = fdf(t)

# Non-mutating version
# The contract with the user is that fdf returns (F, DF)
# and then we simply need to pick out the appropriate element
# of whatever fdf returns.
make_f(t::NotInplaceObjective, x, F::Real) = x -> fdf(t)(x)[1]
make_f(t::NotInplaceObjective, x, F) = (F, x) -> copyto!(F, fdf(t)(x)[1])
make_df(t::NotInplaceObjective{DF, TDF}, x, F) where {DF<:Nothing, TDF} = (DF, x) -> copyto!(DF, fdf(t)(x)[2])
make_df(t::NotInplaceObjective, x, F) = t.df
function make_fdf(t::NotInplaceObjective, x, F::Real)
    return function ffgg!(G, x)
        f, g = fdf(t)(x)
        copyto!(G, g)
        f
    end
end
function make_fdf(t::NotInplaceObjective, x, F)
    return function ffjj!(F, J, x)
        f, j = fdf(t)(x)
        copyto!(J, j)
        copyto!(F, f)
    end
end

# Constructors
function NonDifferentiable(t::Union{InplaceObjective, NotInplaceObjective}, x::AbstractArray, F::Real = real(zero(eltype(x))))
    f = make_f(t, x, F)
    NonDifferentiable(f, x, F)
end
# this would not be possible if we could mark f, g, ... as non-AbstractArrays
function NonDifferentiable(t::Union{InplaceObjective, NotInplaceObjective}, x::AbstractArray, F::AbstractArray)
    f = make_f(t, x, F)
    NonDifferentiable(f, x, F)
end

function TwiceDifferentiable(t::InplaceObjective{<: Nothing, <: Nothing, TH}, x::AbstractArray, F::Real = real(zero(eltype(x))), G::AbstractArray = similar(x), H = alloc_H(x)) where {TH}
    f   =     x  -> t.fgh(F, nothing, nothing, x)
    df  = (G, x) -> t.fgh(nothing, G, nothing, x)
    fdf = (G, x) -> t.fgh(F, G, nothing, x)
    h   = (H, x) -> t.fgh(F, nothing, H, x)
    TwiceDifferentiable(f, df, fdf, h, x, F, G, H)
end

function TwiceDifferentiable(t::InplaceObjective{<: Nothing, <: Nothing, TH}, x::AbstractVector, F::Real = real(zero(eltype(x))), G::AbstractVector = similar(x)) where {TH}

    H = alloc_H(x)
    f   =     x  -> t.fgh(F, nothing, nothing, x)
    df  = (G, x) -> t.fgh(nothing, G, nothing, x)
    fdf = (G, x) -> t.fgh(F, G, nothing, x)
    h   = (H, x) -> t.fgh(F, nothing, H, x)
    TwiceDifferentiable(f, df, fdf, h, x, F, G, H)
end


module NMRquant

using Dates
using LinearAlgebra: norm
using Mustache
using NMR
using RecipesBase
using Pkg
using Printf
using Sockets
using Statistics
using TimeZones

function write_config(path)
    open(joinpath(path, "config"), "w") do f
        write(f, match(r"inet ([\d\.]+)", read(`ip address show enp0s3`, String)).captures[1])
    end
end

function sorted_kv(dict,args...)
    k = sort(collect(keys(dict)), args...)
    ((x, dict[x]) for x in k)
end

function dark_signals(s::NMR.Spectrum, dark_areas)
    darks = NMR.extract.(s, [area[1] for area in dark_areas])
    for (d,name) in zip(darks, [area[2] for area in dark_areas])
        d[d.default_proc].title = "Name = $name"
    end
    darks
end

function remove_darks!(s::NMR.Spectrum, darks::Array{NMR.Spectrum,1})
    d = NMR.decompose(NMR.lsq_analyze(s, darks)) # find dark signals in s
    s[:] = d[3] # residue (non-dark part)
end

function remove_darks!(lib)
    darks = [l for l in lib if !haskey(sampleinfo(l), "Protons")]
    for l in lib
        if haskey(sampleinfo(l), "Protons")
            remove_darks!(l, darks)
        end
    end
end

"""
    analyze(s::NMR.Spectrum, lib::Array{NMR.Spectrum,1}, stan::Spectrum; out = stdout)

Perform analysis on the NMR spectrum `s` against the library group `lib`, using the internal standard spectrum `stan` for quantitation.

Returns the tuple (`decomposition results`, `quantitation results`) of type (DecompositionResult, Dict{Int, Array{Float64}}).
`Quantitation results` is a dictionary with library reference numbers as keys, and an array of % quantitations as values.
"""
function analyze(s::NMR.Spectrum, lib::Array{NMR.Spectrum,1}, stan::Spectrum; out = stdout, tol = NMR.TOL)
    callback(refs) = println(out, "Found $(join([name(lib[r]) for r in refs], ", ")).")
    d = NMR.lsq_analyze(s, lib; callback=callback, tol=tol)
    quants = quantitate(s, lib, d, stan)
    (d, quants)
end

"""
    quantitate(s::NMR.Spectrum, lib::Vector{NMR.Spectrum}, d::NMR.DecompositionResult, stan::Spectrum, minquant = quant_threshold(s))

Return a Dictionary of quantitation results based on DecompositionResult `d`, reported as percent by weight based internal standard spectrum `stan`,
library spectra `lib` and unknown spectrum `s`. An identification is only reported if the result is above the quantitation threshold `minquant`.
"""
function quantitate(s::NMR.Spectrum, lib::Vector{NMR.Spectrum}, d::NMR.DecompositionResult, stan::Spectrum, minquant = quant_threshold(s))
    results = Dict{Int,Array{Float64}}()
    sampinfo = sampleinfo(s)
    stanint = NMR.integrate(s, stan[stan.default_proc].intrng[1])
    stanconc = blank_conc(s) / MW(stan)
    stanprotons = protons(stan)[1]
    sampleweight = parse(Float64, sampinfo["Sample weight (mg)"])
    volume = parse(Float64, sampinfo["Volume (mL)"])
    for (refno, ref, coeff) in zip(d.refnums, lib[d.refnums], d.coefficients)
        if !isdark(ref)
            pr = protons(ref)
            mw = MW(ref)
            q = NMR.integrate(ref)./pr.*(coeff/stanint*stanprotons*mw*stanconc*volume/sampleweight*100.0)
            if mean(q) > max(minquant, quant_threshold(ref)) # only report more than meanquant %
                results[refno] = q
            end
        end
    end
    results
end

"""
    serve(port, home, libs)

Establish a listening port specified by `port` for receiving NMR spectra data located in a directory defined by `home`.

Valid NMR spectra will be analyzed against a library group `lib`, which must be a Dictionary with library names as keys,
and ([Library Spectrum], [Dark Spectrum], IS Spectrum) as values. A report will be generated at the location of the NMR spectra.
"""
function serve(port, home, libs)
    @async begin
        server = listen(ip"0.0.0.0", port)
        while true
            sock = accept(server)
            @info "Connection opened"
            l = strip(readline(sock))
            @info "Received $l"
            if strip(l) == "bye"
                @info "Shutting down server"
                close(sock)
                close(server)
                return 0
            end
            try
                s = NMR.Spectrum(joinpath(home,l),1)
                sampleinfovalid(s, libs)
                lib, darks, is = getlib(s, libs)

                #3.4
                if lowercase(get(sampleinfo(lib[1]), "Reference Deconvolution", "false")) ∈ ["true", "yes"]
                    tm1 = get(sampleinfo(lib[1]), "tm1", NMR.TM1)
                    tm2 = get(sampleinfo(lib[1]), "tm2", NMR.TM2)
                    s = refdecon(s, lib[3];tm1=tm1, tm2=tm2)
                    s[1].procno = 99
                    s[1].title *= "\r\ntm1 = $tm1\r\ntm2 = $tm2"
                    dump(joinpath(home,l,"pdata","99"),s[1])
                end

                d,q = analyze(s, lib, is; out = sock)
                outfile = report(s, lib, d, q, darks, is, joinpath(home,l))
                println(sock, "Done")
            catch err
                @error err
                @error stacktrace(catch_backtrace())
                println(sock, err)
            finally
                close(sock)
                @info "Connection closed, listening"
            end
        end
    end
end

include("plots.jl")
include("report.jl")
include("util.jl")

export serve, analyze, report, library_report, quantitate, sampleinfo, library

end


function loopwinFixsize(nSNP, nReads, rawdata, snpID, bin2geno, nPer,
                        SNPSize, flagAnnotate, snpWtType, keepIdx,
                        nPerKeep, kernel, kinship, test,
                        XtNullBasis, WPreSim, nBlockAscent, nMMmax,
                        nNullSimPts, tolX, pvalueComputing, device,
                        nNullSimNewtonIter, rankQPhi, evalPhiAdj,
                        XPhitNullBasis, yShift, y, KPhiAdj,
                        weightedW, QPhi, PrePartialSumW,
                        PreTotalSumW, windowSize, nPreRank, X, ynew)

  QRes = Array(Float64, nPerKeep, rankQPhi);
  nSNPredVec = fill(0, nReads);
  snpSqrtWts = Float64[];
  tmpvec = similar(yShift);
  tmpvecQRes = Array(Float64, rankQPhi);
  yWork = similar(evalPhiAdj);
  partialSumWConst = Array(Float64, nNullSimPts);
  totalSumWConst = Array(Float64, nNullSimPts);
  subXPhitSV = Array(Float64, size(XPhitNullBasis, 2), rankQPhi);
  pSubXPhitSV = pointer(subXPhitSV);
  offset = 0;
  oneConst = ones(nPerKeep);
  geno = Array(Float64, nPer, SNPSize);
  genoOri = Array(Float64, nPer, SNPSize);
  snpIDred = similar(snpID);
  tmpMat = Array(Float64, windowSize, size(XPhitNullBasis, 2));
  VWorkSqrt = Array(Float64, length(evalPhiAdj), windowSize);
  VWorkSqrt2 = Array(Float64, length(evalPhiAdj), windowSize);
  mafOri = Array(Float64, SNPSize);
  maf = Array(Float64, SNPSize);
  sumIsnan = Array(Float64, 1, SNPSize);
  nChrObs = Array(Float64, 1, SNPSize);
  tmpvecKernel = Array(Float64, windowSize);
  if pvalueComputing == "chi2"
    nPtsChi2 = 300;
    partialSumW = Array(Float64, nPtsChi2);
    totalSumW = Array(Float64, nPtsChi2);
    lambda = Array(Float64, 1, nPtsChi2);
    W = Array(Float64, windowSize, nPtsChi2);
    tmpmat0 = Array(Float64, windowSize, nPtsChi2);
    tmpmat1 = Array(Float64, windowSize, nPtsChi2);
    tmpmat2 = Array(Float64, windowSize, nPtsChi2);
    tmpmat3 = Array(Float64, windowSize, nPtsChi2);
    tmpmat4 = Array(Float64, windowSize, nPtsChi2);
    tmpmat5 = Array(Float64, windowSize, nPtsChi2);
    denomvec = Array(Float64, nPtsChi2);
    d1f = Array(Float64, nPtsChi2);
    d2f = Array(Float64, nPtsChi2);
    if test == "eScore"
      simnull = Array(Float64, nPtsChi2);
    else
      simnull = Float64[];
    end
  else
    nPtsChi2 = 300;
    partialSumW = Array(Float64, nNullSimPts);
    totalSumW = Array(Float64, nNullSimPts);
    lambda = Array(Float64, 1, nNullSimPts);
    W = Array(Float64, windowSize, nNullSimPts);
    tmpmat0 = Array(Float64, windowSize, nNullSimPts);
    tmpmat1 = Array(Float64, windowSize, nNullSimPts);
    tmpmat2 = Array(Float64, windowSize, nNullSimPts);
    tmpmat3 = Array(Float64, windowSize, nNullSimPts);
    tmpmat4 = Array(Float64, windowSize, nNullSimPts);
    tmpmat5 = Array(Float64, windowSize, nNullSimPts);
    denomvec = Array(Float64, nNullSimPts);
    d1f = Array(Float64, nNullSimPts);
    d2f = Array(Float64, nNullSimPts);
    if test == "eScore"
      simnull = Array(Float64, nNullSimPts);
    else
      simnull = Float64[];
    end
  end

  results = Array(Any, nReads * iceil(SNPSize / windowSize), 3);
  idx = 0;

  for idxRead = 1 : nReads

    # read the binary data
    if idxRead == nReads
      curSNPSize = nSNP - (idxRead - 1) * SNPSize;
    else
      curSNPSize = SNPSize;
    end

    readgeno!(genoOri, curSNPSize, nPer, SNPSize, idxRead, bin2geno, rawdata,
              offset, flagAnnotate);

    tmpMatIsnan = isnan(genoOri);
    sum!(sumIsnan, tmpMatIsnan);
    for i = 1 : curSNPSize
      nChrObs[1, i] = 2 * (nPer - sumIsnan[1, i]);
    end

    # minor allele frequencies and get rid of mono-allelic SNPs
    counter = 0;
    offset = (idxRead - 1) * SNPSize;
    for i = 1 : curSNPSize
      mafOri[i] = 0.0;
      for j = 1 : nPer
        if !tmpMatIsnan[j, i]
          mafOri[i] += genoOri[j, i];
        end
      end
      mafOri[i] = mafOri[i] / nChrObs[1, i];
      if mafOri[i] != 0
        counter += 1;
        maf[counter] = mafOri[i];
        snpIDred[counter] = snpID[i + offset];
        pGenoOri = pointer(genoOri) + (i - 1) * nPer * sizeof(Float64);
        pGeno = pointer(geno) + (counter - 1) * nPer * sizeof(Float64);
        BLAS.blascopy!(nPer, pGenoOri, 1, pGeno, 1);
      end
    end
    nSNPredVec[idxRead] = curSNPSize - counter;

    if counter == 0
      #println("No valid SNPs at the group which starting from row ",
      #        offset + 1);
      continue;
    end

    # impute missing genotype by expected minor allele counts
    for j = 1 : counter
      for i = 1 : nPer
        if isnan(geno[i, j])
          geno[i, j] = 2 * maf[j];
        end
      end
    end

    # boundary of windows
    if idxRead == nReads
      nSNPUpdate = nSNP - (idxRead - 1) * SNPSize - nSNPredVec[idxRead];
    else
      nSNPUpdate = SNPSize - nSNPredVec[idxRead];
    end
    gLB = [1:windowSize:nSNPUpdate];
    gUB = [gLB[2:end]-1; nSNPUpdate];
    nGrps = length(gLB);
    vc0List = zeros(nGrps);
    vc1List = zeros(nGrps);
    pvalList = zeros(nGrps);

    # looping over windows
    for g = 1 : nGrps

      # get SNP weights
      grpSize = gUB[g] - gLB[g] + 1;
      if isempty(snpWtType)
        # constant weights
        if g > 1 && g < nGrps
          snpSqrtWts = fill!(snpSqrtWts, 1);
        else
          snpSqrtWts = ones(grpSize);
        end
      elseif snpWtType == "beta"
        # weights by beta density evaluated at MAF
        snpSqrtWts = sqrt(Distributions.pdf(Distributions.Beta(1, 25), maf[gLB[g] : gUB[g]]));
      elseif snpWtType == "invvar"
        # weights by inverse variance maf*(1-maf)
        snpSqrtWts = 1 ./ sqrt( maf[gLB[g] : gUB[g]] .*
                               (1 - maf[gLB[g] : gUB[g]]) );
      end

      # retrieve group genotypes
      if kernel == "GRM"
        gMAF = maf[gLB[g] : gUB[g]];
        gSNP = geno[keepIdx, gLB[g] : gUB[g]];
        # center and scale genotype matrix by MAF
        BLAS.ger!(-2.0, oneConst, gMAF, gSNP);
        if g == nGrps && grpSize != windowSize
          tmpvecKernel = tmpvecKernel[1 : grpSize];
        end
        for i = 1 : grpSize
          tmpvecKernel[i] = snpSqrtWts[i] / sqrt(2 * gMAF[i] * (1 - gMAF[i]));
        end
        scale!(gSNP, tmpvecKernel);
        #gSNP = gSNP .* (snpSqrtWts ./ sqrt(2 * gMAF .* (1 - gMAF)))';
        if g == nGrps && grpSize != windowSize
          tmpvecKernel = [tmpvecKernel; Array(Float64, windowSize - grpSize)];
        end
      elseif kernel == "IBS1"
        # Table 3 in notes
        gSNP = hcat(geno[keepIdx, gLB[g] : gUB[g]],
                    2 - geno[keepIdx, gLB[g] : gUB[g]]);
        scale!(gSNP, [snpSqrtWts; snpSqrtWts]);
      elseif kernel == "IBS2"
        # Table 2 in notes
        gSNP = hcat(geno[keepIdx, gLB[g] : gUB[g]] .>= 1,
                    geno[keepIdx, gLB[g] : gUB[g]] .<= 1);
        scale!(gSNP, [snpSqrtWts; snpSqrtWts]);
      elseif kernel == "IBS3"
        # Table 1 in notes
        gSNP = hcat(-2 * (geno[keepIdx, gLB[g] : gUB[g]] .<= 1) + 1,
                    2 * (geno[keepIdx, gLB[g] : gUB[g]] .>= 1) - 1,
                    fill(sqrt(2), nPerKeep, gUB[g] - gLB[g] + 1));
        scale!(gSNP, [snpSqrtWts; snpSqrtWts; snpSqrtWts]);
      end

      # testing

      if kinship == "none"

        if test == "eRLRT"
          (b, vc0List[g], vc1List[g], pvalList[g]) =
            vctest(ynew, [], XtNullBasis * gSNP, WPreSim = WPreSim, tests = test,
                   Vform = "half", nBlockAscent = nBlockAscent, nMMmax = nMMmax,
                   nNullSimPts = nNullSimPts, pvalueComputings = pvalueComputing,
                   nNullSimNewtonIter = nNullSimNewtonIter, tolX = tolX,
                   devices = device, PrePartialSumW = PrePartialSumW,
                   PreTotalSumW = PreTotalSumW, partialSumWConst = partialSumWConst,
                   totalSumWConst = totalSumWConst, windowSize = windowSize,
                   partialSumW = partialSumW, totalSumW = totalSumW,
                   lambda = lambda, W = W, nPreRank = nPreRank,
                   tmpmat0 = tmpmat0, tmpmat1 = tmpmat1, tmpmat2 = tmpmat2,
                   tmpmat3 = tmpmat3, tmpmat4 = tmpmat4, tmpmat5 = tmpmat5,
                   denomvec = denomvec, d1f = d1f, d2f = d2f, offset = offset,
                   nPtsChi2 = nPtsChi2, simnull = simnull);
        else
          (b, vc0List[g], vc1List[g], pvalList[g]) =
            vctest(y, X, gSNP, WPreSim = WPreSim, tests = test,
                   Vform = "half", nBlockAscent = nBlockAscent, nMMmax = nMMmax,
                   nNullSimPts = nNullSimPts, pvalueComputings = pvalueComputing,
                   nNullSimNewtonIter = nNullSimNewtonIter, tolX = tolX,
                   devices = device, PrePartialSumW = PrePartialSumW,
                   PreTotalSumW = PreTotalSumW, partialSumWConst = partialSumWConst,
                   totalSumWConst = totalSumWConst, windowSize = windowSize,
                   partialSumW = partialSumW, totalSumW = totalSumW,
                   lambda = lambda, W = W, nPreRank = nPreRank,
                   tmpmat0 = tmpmat0, tmpmat1 = tmpmat1, tmpmat2 = tmpmat2,
                   tmpmat3 = tmpmat3, tmpmat4 = tmpmat4, tmpmat5 = tmpmat5,
                   denomvec = denomvec, d1f = d1f, d2f = d2f, offset = offset,
                   nPtsChi2 = nPtsChi2, simnull = simnull);
        end

      else

        # obtain some orthonormal vectors of space R^n - [QX,QPHI,S]
        # See Golub and Van Loan (1996) Algorithm 12.4.2 on p602
        if g == nGrps && grpSize != windowSize
          tmpMat = tmpMat[1 : grpSize, :];
        end
        BLAS.gemm!('T', 'N', 1.0, gSNP, XPhitNullBasis, 0.0, tmpMat);
        (UXPhitS, svalXPhitS, VXPhitS) = svd(tmpMat, thin = false);
        if g == nGrps && grpSize != windowSize
          tmpMat = [tmpMat; Array(Float64, windowSize - grpSize,
                                  size(XPhitNullBasis, 2))];
        end
        rankXPhitS =
          countnz(svalXPhitS .>
                  max(size(XPhitNullBasis, 2),
                      size(gSNP, 2) * eps(svalXPhitS[1])));
        pXPhitSV = pointer(VXPhitS) +
          rankXPhitS * size(XPhitNullBasis, 2) * sizeof(Float64);
        BLAS.blascopy!(size(XPhitNullBasis, 2) * rankQPhi,
                       pXPhitSV, 1, pSubXPhitSV, 1);
        BLAS.gemm!('N', 'N', 1.0, XPhitNullBasis, subXPhitSV, 0.0, QRes);

        # working Y and working non-trivial variance component
        #yWork = (PhiAdjsvd[:U]' * (yShift + KPhiAdj * (QRes' * y))) ./ sqrt(evalPhiAdj);
        BLAS.blascopy!(length(yShift), yShift, 1, tmpvec, 1);
        BLAS.gemv!('T', 1.0, QRes, y, 0.0, tmpvecQRes);
        BLAS.gemv!('N', 1.0, KPhiAdj, tmpvecQRes, 1.0, tmpvec);
        #yWork = BLAS.gemv('T', 1.0, PhiAdjsvd[:U], tmpvec) ./ sqrt(evalPhiAdj);
        BLAS.gemv!('T', 1.0, weightedW, tmpvec, 0.0, yWork);
        #for i = 1 : length(yWork)
        #  yWork[i] = yWork[i] * InvSqrtevalPhiAdj[i];
        #end
        #VWorkSqrt = PhiAdjsvd[:U]' * (QPhi' * gSNP) ./ sqrt(evalPhiAdj);
        if g == nGrps && grpSize != windowSize
          VWorkSqrt = VWorkSqrt[:, 1 : grpSize];
          VWorkSqrt2 = VWorkSqrt2[:, 1 : grpSize];
        end
        BLAS.gemm!('T', 'N', 1.0, QPhi, gSNP, 0.0, VWorkSqrt2);
        BLAS.gemm!('T', 'N', 1.0, weightedW, VWorkSqrt2, 0.0, VWorkSqrt);
        #scale!(InvSqrtevalPhiAdj, VWorkSqrt);

        # obtain LRT and p-value
        (b, vc0List[g], vc1List[g], pvalList[g]) =
          vctest(yWork, [], VWorkSqrt, WPreSim = WPreSim, tests = test,
                 Vform = "half", nBlockAscent = nBlockAscent, nMMmax = nMMmax,
                 nNullSimPts = nNullSimPts, pvalueComputings = pvalueComputing,
                 nNullSimNewtonIter = nNullSimNewtonIter, tolX = tolX,
                 devices = device, PrePartialSumW = PrePartialSumW,
                 PreTotalSumW = PreTotalSumW, partialSumWConst = partialSumWConst,
                 totalSumWConst = totalSumWConst, windowSize = windowSize,
                 partialSumW = partialSumW, totalSumW = totalSumW,
                 lambda = lambda, W = W, nPreRank = nPreRank,
                 tmpmat0 = tmpmat0, tmpmat1 = tmpmat1, tmpmat2 = tmpmat2,
                 tmpmat3 = tmpmat3, tmpmat4 = tmpmat4, tmpmat5 = tmpmat5,
                 denomvec = denomvec, d1f = d1f, d2f = d2f, offset = offset,
                 nPtsChi2 = nPtsChi2, simnull = simnull);

        # recover VWorkSqrt
        if g == nGrps && grpSize != windowSize
          VWorkSqrt = hcat(VWorkSqrt,
                           Array(Float64, length(evalPhiAdj),
                                 windowSize - grpSize));
          VWorkSqrt2 = hcat(VWorkSqrt2,
                            Array(Float64, length(evalPhiAdj),
                                  windowSize - grpSize));
        end

      end

      idx += 1;
      results[idx, :] = hcat(snpIDred[gLB[g]], snpIDred[gUB[g]], pvalList[g]);

    end

  end

  return results[1:idx, :];

end