#=
This file is auto-generated. Do not edit.
=#
"""
    mutable struct AndersonFouadMachine <: Machine
        R::Float64
        Xd::Float64
        Xq::Float64
        Xd_p::Float64
        Xq_p::Float64
        Xd_pp::Float64
        Xq_pp::Float64
        Td0_p::Float64
        Tq0_p::Float64
        Td0_pp::Float64
        Tq0_pp::Float64
        ext::Dict{String, Any}
        states::Vector{Symbol}
        n_states::Int64
        internal::InfrastructureSystemsInternal
    end

Parameters of 6-states synchronous machine: Anderson-Fouad model

# Arguments
- `R::Float64`: Resistance after EMF in machine per unit, validation range: (0, nothing)
- `Xd::Float64`: Reactance after EMF in d-axis per unit, validation range: (0, nothing)
- `Xq::Float64`: Reactance after EMF in q-axis per unit, validation range: (0, nothing)
- `Xd_p::Float64`: Transient reactance after EMF in d-axis per unit, validation range: (0, nothing)
- `Xq_p::Float64`: Transient reactance after EMF in q-axis per unit, validation range: (0, nothing)
- `Xd_pp::Float64`: Sub-Transient reactance after EMF in d-axis per unit, validation range: (0, nothing)
- `Xq_pp::Float64`: Sub-Transient reactance after EMF in q-axis per unit, validation range: (0, nothing)
- `Td0_p::Float64`: Time constant of transient d-axis voltage, validation range: (0, nothing)
- `Tq0_p::Float64`: Time constant of transient q-axis voltage, validation range: (0, nothing)
- `Td0_pp::Float64`: Time constant of sub-transient d-axis voltage, validation range: (0, nothing)
- `Tq0_pp::Float64`: Time constant of sub-transient q-axis voltage, validation range: (0, nothing)
- `ext::Dict{String, Any}`
- `states::Vector{Symbol}`
- `n_states::Int64`
- `internal::InfrastructureSystemsInternal`: power system internal reference, do not modify
"""
mutable struct AndersonFouadMachine <: Machine
    "Resistance after EMF in machine per unit"
    R::Float64
    "Reactance after EMF in d-axis per unit"
    Xd::Float64
    "Reactance after EMF in q-axis per unit"
    Xq::Float64
    "Transient reactance after EMF in d-axis per unit"
    Xd_p::Float64
    "Transient reactance after EMF in q-axis per unit"
    Xq_p::Float64
    "Sub-Transient reactance after EMF in d-axis per unit"
    Xd_pp::Float64
    "Sub-Transient reactance after EMF in q-axis per unit"
    Xq_pp::Float64
    "Time constant of transient d-axis voltage"
    Td0_p::Float64
    "Time constant of transient q-axis voltage"
    Tq0_p::Float64
    "Time constant of sub-transient d-axis voltage"
    Td0_pp::Float64
    "Time constant of sub-transient q-axis voltage"
    Tq0_pp::Float64
    ext::Dict{String, Any}
    states::Vector{Symbol}
    n_states::Int64
    "power system internal reference, do not modify"
    internal::InfrastructureSystemsInternal
end

function AndersonFouadMachine(R, Xd, Xq, Xd_p, Xq_p, Xd_pp, Xq_pp, Td0_p, Tq0_p, Td0_pp, Tq0_pp, ext=Dict{String, Any}(), )
    AndersonFouadMachine(R, Xd, Xq, Xd_p, Xq_p, Xd_pp, Xq_pp, Td0_p, Tq0_p, Td0_pp, Tq0_pp, ext, [:ψq, :ψd, :eq_p, :ed_p, :eq_pp, :ed_pp], 6, InfrastructureSystemsInternal(), )
end

function AndersonFouadMachine(; R, Xd, Xq, Xd_p, Xq_p, Xd_pp, Xq_pp, Td0_p, Tq0_p, Td0_pp, Tq0_pp, ext=Dict{String, Any}(), )
    AndersonFouadMachine(R, Xd, Xq, Xd_p, Xq_p, Xd_pp, Xq_pp, Td0_p, Tq0_p, Td0_pp, Tq0_pp, ext, )
end

# Constructor for demo purposes; non-functional.
function AndersonFouadMachine(::Nothing)
    AndersonFouadMachine(;
        R=0,
        Xd=0,
        Xq=0,
        Xd_p=0,
        Xq_p=0,
        Xd_pp=0,
        Xq_pp=0,
        Td0_p=0,
        Tq0_p=0,
        Td0_pp=0,
        Tq0_pp=0,
        ext=Dict{String, Any}(),
    )
end

"""Get AndersonFouadMachine R."""
get_R(value::AndersonFouadMachine) = value.R
"""Get AndersonFouadMachine Xd."""
get_Xd(value::AndersonFouadMachine) = value.Xd
"""Get AndersonFouadMachine Xq."""
get_Xq(value::AndersonFouadMachine) = value.Xq
"""Get AndersonFouadMachine Xd_p."""
get_Xd_p(value::AndersonFouadMachine) = value.Xd_p
"""Get AndersonFouadMachine Xq_p."""
get_Xq_p(value::AndersonFouadMachine) = value.Xq_p
"""Get AndersonFouadMachine Xd_pp."""
get_Xd_pp(value::AndersonFouadMachine) = value.Xd_pp
"""Get AndersonFouadMachine Xq_pp."""
get_Xq_pp(value::AndersonFouadMachine) = value.Xq_pp
"""Get AndersonFouadMachine Td0_p."""
get_Td0_p(value::AndersonFouadMachine) = value.Td0_p
"""Get AndersonFouadMachine Tq0_p."""
get_Tq0_p(value::AndersonFouadMachine) = value.Tq0_p
"""Get AndersonFouadMachine Td0_pp."""
get_Td0_pp(value::AndersonFouadMachine) = value.Td0_pp
"""Get AndersonFouadMachine Tq0_pp."""
get_Tq0_pp(value::AndersonFouadMachine) = value.Tq0_pp
"""Get AndersonFouadMachine ext."""
get_ext(value::AndersonFouadMachine) = value.ext
"""Get AndersonFouadMachine states."""
get_states(value::AndersonFouadMachine) = value.states
"""Get AndersonFouadMachine n_states."""
get_n_states(value::AndersonFouadMachine) = value.n_states
"""Get AndersonFouadMachine internal."""
get_internal(value::AndersonFouadMachine) = value.internal

"""Set AndersonFouadMachine R."""
set_R!(value::AndersonFouadMachine, val::Float64) = value.R = val
"""Set AndersonFouadMachine Xd."""
set_Xd!(value::AndersonFouadMachine, val::Float64) = value.Xd = val
"""Set AndersonFouadMachine Xq."""
set_Xq!(value::AndersonFouadMachine, val::Float64) = value.Xq = val
"""Set AndersonFouadMachine Xd_p."""
set_Xd_p!(value::AndersonFouadMachine, val::Float64) = value.Xd_p = val
"""Set AndersonFouadMachine Xq_p."""
set_Xq_p!(value::AndersonFouadMachine, val::Float64) = value.Xq_p = val
"""Set AndersonFouadMachine Xd_pp."""
set_Xd_pp!(value::AndersonFouadMachine, val::Float64) = value.Xd_pp = val
"""Set AndersonFouadMachine Xq_pp."""
set_Xq_pp!(value::AndersonFouadMachine, val::Float64) = value.Xq_pp = val
"""Set AndersonFouadMachine Td0_p."""
set_Td0_p!(value::AndersonFouadMachine, val::Float64) = value.Td0_p = val
"""Set AndersonFouadMachine Tq0_p."""
set_Tq0_p!(value::AndersonFouadMachine, val::Float64) = value.Tq0_p = val
"""Set AndersonFouadMachine Td0_pp."""
set_Td0_pp!(value::AndersonFouadMachine, val::Float64) = value.Td0_pp = val
"""Set AndersonFouadMachine Tq0_pp."""
set_Tq0_pp!(value::AndersonFouadMachine, val::Float64) = value.Tq0_pp = val
"""Set AndersonFouadMachine ext."""
set_ext!(value::AndersonFouadMachine, val::Dict{String, Any}) = value.ext = val
"""Set AndersonFouadMachine states."""
set_states!(value::AndersonFouadMachine, val::Vector{Symbol}) = value.states = val
"""Set AndersonFouadMachine n_states."""
set_n_states!(value::AndersonFouadMachine, val::Int64) = value.n_states = val
"""Set AndersonFouadMachine internal."""
set_internal!(value::AndersonFouadMachine, val::InfrastructureSystemsInternal) = value.internal = val


using Test, OceanBasins

ENV["DATADEPS_ALWAYS_ACCEPT"] = true
const OCEANS = oceanpolygons()

@testset "Testing Oceanbasins" begin
    @test ispacific(0, -160, OCEANS)
    @test isarctic(89, 0, OCEANS)
    @test isatlantic(0, -30, OCEANS)
    @test isindian(0, 90, OCEANS)
    @test ispacific2(0, -160, OCEANS)
    @test isatlantic2(0, -30, OCEANS)
    @test isindian2(0, 90, OCEANS)
    @test isSOpacific(-80, -160, OCEANS)
    @test isSOatlantic(-80, -30, OCEANS)
    @test isSOindian(-80, 90, OCEANS)
    @test isantarctic(-40, 0, OCEANS)
    @test ismediterranean(31, 15, OCEANS)
    @test isindonesian(0, 120, OCEANS)
    @test ismediterranean2(31, 15, OCEANS)
    @test isNPAC(30, -160, OCEANS)
    @test isNATL(30, -30, OCEANS)
    @test isSPAC(-30, -160, OCEANS)
    @test isSATL(-30, -30, OCEANS)
end


module NaiveNASlib

import Statistics
import Logging
using Logging: LogLevel, @logmsg

import JuMP
using JuMP: @variable, @constraint, @objective, @expression, MOI, MOI.INFEASIBLE, MOI.FEASIBLE_POINT
import Cbc
import Functors
using Functors: @functor, functor

# Computation graph
export CompGraph, nvertices, vertices, findvertices, inputs, outputs, name

# Vertex size operations
export nin, nout, Δnin!, Δnout!, Δsize!, relaxed

# Connectivity mutation
export remove!, insert!, create_edge!, remove_edge!

# Create vertices
export inputvertex, immutablevertex, absorbvertex, invariantvertex, conc

include("vertex.jl")
include("compgraph.jl")
include("prettyprint.jl")

include("mutation/vertex.jl")
include("mutation/graph.jl")

include("mutation/jumpnorm.jl")
include("mutation/sizestrategies.jl")

include("mutation/size.jl")
include("mutation/select.jl")
include("mutation/structure.jl")

include("api/vertex.jl")
include("api/size.jl")
include("api/Advanced.jl")
include("api/Extend.jl")

end # module


# RwaJur4

export VERTICES_RWAJUR4,
		RwaJur4Exposure,
		RwaJur4Parameters,
		getLongExposureArray,
		getShortExposureArray,
		#getRwaJur4NetExposure,
		#getRwaJur4VerticalGap,
		#getRwaJur4Zones,
		#getRwaJur4HorizontalGapWithinZones,
		#getRwaJur4HorizontalGapBetweenZones,
		getPJur4,
		getRwaJur4

"""
Standardized vertices for mapping interest rate coupon rate exposures (RWAJUR4)
"""
const VERTICES_RWAJUR4 = VERTICES_RWAJUR2

"""
    RwaJur4Exposure

Holds exposures in interest rate coupon rates.

# Fields
* `date::Date`: date of evaluation
* `VXX::Float64`: exposure to vertex XX,
given by the sum of present value in BRL
of all cashflows that matures in XX business days (BD).

# Constructors
    RwaJur4Exposure(date::Date)

Create an instance with all exposures equal to zero.

	RwaJur4Exposure(date::Date, currency::AbstractString, longExposures::Array{Float64, 1}, shortExposures::Array{Float64, 1})

Create an instance with exposures given by two vectors of length 10 containing long and short exposures.
Exposures are assigned in sequence from vertex 1 BD to vertex 2520 BD.

"""
type RwaJur4Exposure
	# Date of evaluation
	date::Date
	# Currency
	currency::AbstractString
	# Exposures in standard vertices: VXX, where XX is a maturity calculated in business days
	# LONG EXPOSURES
	V1Long::Float64
	V21Long::Float64
	V42Long::Float64
	V63Long::Float64
	V126Long::Float64
	V252Long::Float64
	V504Long::Float64
	V756Long::Float64
	V1008Long::Float64
	V1260Long::Float64
	V2520Long::Float64
	# SHORT EXPOSURES
	V1Short::Float64
	V21Short::Float64
	V42Short::Float64
	V63Short::Float64
	V126Short::Float64
	V252Short::Float64
	V504Short::Float64
	V756Short::Float64
	V1008Short::Float64
	V1260Short::Float64
	V2520Short::Float64

	# Constructors
	RwaJur4Exposure(date::Date, currency::AbstractString) = new(date, currency, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0)

	function RwaJur4Exposure(date::Date, currency::AbstractString, longExposures::Array{Float64, 1}, shortExposures::Array{Float64, 1})
		if size(longExposures, 1) != 11
			error("long exposures must have 11 values")
		end #if
		if any(longExposures .< 0)
			error("all long exposures must be positive")
		end #if

		if size(shortExposures, 1) != 11
			error("short exposures must have 11 values")
		end #if
		if any(shortExposures .< 0)
			error("all short exposures must be positive")
		end #if

		new(date,
			currency,
		    longExposures[1], longExposures[2], longExposures[3], longExposures[4],
			longExposures[5], longExposures[6], longExposures[7], longExposures[8],
			longExposures[9], longExposures[10], longExposures[11],
			shortExposures[1], shortExposures[2], shortExposures[3], shortExposures[4],
			shortExposures[5], shortExposures[6], shortExposures[7], shortExposures[8],
			shortExposures[9], shortExposures[10], shortExposures[11]
			)
	end #function

end #type

"""
	RWAJUR4Parameters

Holds BCB parameters to calculate interest rate coupon stantardized capital (RWA JUR4)

"""
type RwaJur4Parameters
	date::Date
	M4::Float64
end #type

"""
    getLongExposureArray(rExp::RwaJur4Exposure)

Return a vector with all long exposures in sequence,
from vertex 1 BD to vertex 2520 BD.

"""
getLongExposureArray(rExp::RwaJur4Exposure) = [rExp.V1Long, rExp.V21Long, rExp.V42Long, rExp.V63Long, rExp.V126Long, rExp.V252Long, rExp.V504Long, rExp.V756Long, rExp.V1008Long, rExp.V1260Long, rExp.V2520Long]

"""
    getShortExposureArray(rExp::RwaJur4Exposure)

Return a vector with all short exposures in sequence,
from vertex 1 BD to vertex 2520 BD.

"""
getShortExposureArray(rExp::RwaJur4Exposure) = [rExp.V1Short, rExp.V21Short, rExp.V42Short, rExp.V63Short, rExp.V126Short, rExp.V252Short, rExp.V504Short, rExp.V756Short, rExp.V1008Short, rExp.V1260Short, rExp.V2520Short]

# Casts
"""
    convert(::Type{RwaJur2Exposure}, x::RwaJur4Exposure)

Casts an RwaJur4Exposure into an RwaJur2Exposure.
"""
convert(::Type{RwaJur2Exposure}, x::RwaJur4Exposure) = RwaJur2Exposure(x.date, x.currency, getLongExposureArray(x), getShortExposureArray(x))

"""
    convert(::Type{RwaJur2Parameters}, x::RwaJur4Parameters)

Casts an RwaJur4Parameters into an RwaJur2Parameters.
"""
convert(::Type{RwaJur2Parameters}, x::RwaJur4Parameters) = RwaJur2Parameters(x.date, x.M4)

"""
    getRwaJur4NetExposure(rExp::RwaJur4Exposure)

Return a vector containing RWA JUR4 weighted net exposures.

"""
getRwaJur4NetExposure(rExp::RwaJur4Exposure) = getRwaJur2NetExposure(convert(RwaJur2Exposure, rExp))

"""
    getRwaJur4VerticalGap(rExp::RwaJur4Exposure)

Return a vector containing RWA JUR4 vertical gaps.

"""
getRwaJur4VerticalGap(rExp::RwaJur4Exposure) = getRwaJur2VerticalGap(convert(RwaJur2Exposure, rExp))

"""
    getRwaJur4Zones(rExp::RwaJur4Exposure)

Return a matrix containing RWA JUR4 zone exposures.
Column 1 contains long exposures, column2 contains short exposures.
Zones are represented in each of the 3 rows.

"""
getRwaJur4Zones(rExp::RwaJur4Exposure) = getRwaJur2Zones(convert(RwaJur2Exposure, rExp))

"""
    getRwaJur3HorizontalGapWithinZones(rExp::RwaJur4Exposure)

Return a vector containing RWA JUR4 horizontal gap within zones.

"""
getRwaJur4HorizontalGapWithinZones(rExp::RwaJur4Exposure) = getRwaJur2HorizontalGapWithinZones(convert(RwaJur2Exposure, rExp))

"""
    getRwaJur4HorizontalGapBetweenZones(rExp::RwaJur4Exposure)

Return a vector containing RWA JUR4 horizontal gap between zones.

"""
getRwaJur4HorizontalGapBetweenZones(rExp::RwaJur4Exposure) = getRwaJur2HorizontalGapBetweenZones(convert(RwaJur2Exposure, rExp))

"""
	getPJur3(rExp::RwaJur4Exposure, par::RwaJur4Parameters)

Calculate PJUR4 using maturity ladder methodology.

"""
getPJur4(rExp::RwaJur4Exposure, par::RwaJur4Parameters) = getPJur2(convert(RwaJur2Exposure, rExp), convert(RwaJur2Parameters, par))

"""
	getRwaJur4(rExp::RwaJur4Exposure, par::RwaJur4Parameters)

Calculate risk-weighted assets for interest rate coupon exposures (RWA JUR4),
using maturity ladder methodology.

"""
getRwaJur4(rExp::RwaJur4Exposure, par::RwaJur4Parameters) = getRwaJur2(convert(RwaJur2Exposure, rExp), convert(RwaJur2Parameters, par))


using CSFML.LibCSFML
using ModernGL
using Images, Colors

include("ModernGL_utils.jl")

# https://www.tomdalling.com/blog/modern-opengl/02-textures/

### Init

mode = sfVideoMode(700, 720, 32)
# settings = SF::ContextSettings.new(
#   depth: 24, stencil: 8, antialiasing: 4,
#   major: 3, minor: 0
# )
window = sfRenderWindow_create(mode, "SFML window", sfResize | sfClose, C_NULL)
@assert window != C_NULL
sfWindow_setVerticalSyncEnabled(window,true)
sfWindow_setActive(window, sfTrue)
println(createcontextinfo()) # used by ModernGL_utils;
glEnable(GL_DEPTH_TEST)
glDepthFunc(GL_LESS)
glClearColor(0.4 ,0.2, 0.3, 1.0)

### shaders

vsh = """
    $(get_glsl_version_string())
    //layout(location = 0) in vec3 v_position;
    //layout(location = 1) in vec3 v_color;

    in vec3 v_position;
    in vec2 v_texCoord;
    out vec2 fragTexCoord;

    //in vec3 v_color;
    //out vec3 initial_Color;
    void main() {
        fragTexCoord = v_texCoord;
        gl_Position = vec4(v_position, 1.0);
    }
    """

fsh = """
    $(get_glsl_version_string())
    // in vec3 initial_Color;

    in vec2 fragTexCoord;

    uniform sampler2D tex;
    out vec4 outColor;
    void main() {
        //outColor = vec4(0.5, 0.2, 0.3, 1.0);
        //outColor = vec4(frag_colour, 1.0 );
        outColor = texture(tex, fragTexCoord);
    }
    """

vertexShader = createShader(vsh, GL_VERTEX_SHADER)
fragmentShader = createShader(fsh, GL_FRAGMENT_SHADER)

function bindAttrib(shader_program)
    glBindAttribLocation(shader_program, 0, "v_position");
    glBindAttribLocation(shader_program, 1, "v_TextCoord");
end

program = createShaderProgram( bindAttrib, vertexShader, fragmentShader)
#glUseProgram(program)
#println("program : $program")
print("shader ---> ")
glCheckError()

### vertex, colors and buffers

vao = glGenVertexArray()
glBindVertexArray(vao)

points_vbo = glGenBuffer()
glBindBuffer(GL_ARRAY_BUFFER, points_vbo)

vertexData = GLfloat[
        #  X     Y     Z       U     V
         0.0, 0.8, 0.0,        0.5, 1.0,
        -0.8,-0.8, 0.0,        0.0, 0.0,
         0.8,-0.8, 0.0,        1.0, 0.0
]

glBufferData(GL_ARRAY_BUFFER, sizeof(vertexData), vertexData, GL_STATIC_DRAW)
println("points_vbo : $points_vbo")
glCheckError()

glEnableVertexAttribArray(0);
glVertexAttribPointer(0, 3, GL_FLOAT, GL_FALSE, 5*sizeof(GLfloat) , C_NULL)

glEnableVertexAttribArray(1)
glVertexAttribPointer(1, 2, GL_FLOAT, GL_TRUE, 5*sizeof(GLfloat) , C_NULL)

glBindBuffer(GL_ARRAY_BUFFER, 0)
glBindVertexArray(0)

### textures

#test = Array{Int32}(1)

# glGenTextures(n::GLsizei, textures::Ptr{GLuint})::Cvoid

minMagFiler = 0
wrapMode = 0

tex = GLint[0]
glGenTextures(1, tex)
glBindTexture(GL_TEXTURE_2D, tex[1]);
println("text ---> $tex")

## glTexParameteri(target::GLenum, pname::GLenum, param::GLint)::Cvoid

minMagFiler = GL_LINEAR
wrapMode = GL_CLAMP_TO_EDGE

glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, minMagFiler)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, minMagFiler)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, wrapMode)
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, wrapMode)

img = load("/julia/playgrounds/resources/hazard_256x256.png")
#img = rand(GLuint, 256, 256)
w, h = size(img)
rawTexData = []
for i in 1:w
    for j in 1:h
        push!(raw, RGBA(img[i,j]).r)
        push!(raw, RGBA(img[i,j]).g)
        push!(raw, RGBA(img[i,j]).b)
        #push!(raw, RGBA(img[i,j]).alpha)
    end
end

# glTexImage2D( target::GLenum,
                    # level::GLint,
                    # internalformat::GLint,
                    # width::GLsizei,
                    # height::GLsizei,
                    # border::GLint,
                    # format::GLenum,
                    # type_::GLenum,
                    # pixels::Ptr{Cvoid})
                    # (returns)::Cvoid

# glTexImage2D(GL_TEXTURE_2D,
#                  0,
#                  TextureFormatForBitmapFormat(bitmap.format()),
#                  (GLsizei)bitmap.width(),
#                  (GLsizei)bitmap.height(),
#                  0,
#                  TextureFormatForBitmapFormat(bitmap.format()),
#                  GL_UNSIGNED_BYTE,
#                  bitmap.pixelBuffer())

glTexImage2D(GL_TEXTURE_2D,
                0,
                GL_RGB,
                256,
                256,
                0,
                GL_RGB,
                GL_UNSIGNED_BYTE,
                rawTexData )
glCheckError()

glBindTexture(GL_TEXTURE_2D, 0)

###
event_ref = Ref{sfEvent}()
clock = sfClock_create()
# Let's express time in seconds.
function get_time(clock::Ptr{sfClock})
    sfTime_asSeconds(sfClock_getElapsedTime(clock))
end
function restart(clock::Ptr{sfClock})
     sfTime_asSeconds(sfClock_restart(clock))
end

target_frequency = 24.0
running = true
t0 = 9999999
while (running)

    while Bool(sfRenderWindow_isOpen(window)) && running
        frame_timestamp = get_time(clock)
        frame_time = frame_timestamp - t0
        global t0 = frame_timestamp
        actual_frequency = 1 / frame_time

        # process events
        while Bool(sfRenderWindow_pollEvent(window, event_ref))
            # close window : exit
            event_ref[].type == sfEvtClosed && sfRenderWindow_close(window)
            event_ref[].type == sfEvtResized && println("Trigger sfEvtResized.")
            event_ref[].type == sfEvtLostFocus && println("Trigger sfEvtLostFocus.")
            event_ref[].type == sfEvtGainedFocus && println("Trigger sfEvtGainedFocus.")
            event_ref[].type == sfEvtTextEntered && println("Trigger sfEvtTextEntered: $(event_ref[].text.unicode)")
            event_ref[].type == sfEvtKeyPressed && println("Trigger sfEvtKeyPressed: $(event_ref[].key.code)")
            event_ref[].type == sfEvtKeyReleased && println("Trigger sfEvtKeyReleased: $(event_ref[].key.code)")
            event_ref[].type == sfEvtMouseWheelMoved && println("Trigger sfEvtMouseWheelMoved: $(event_ref[].mouseWheel.x), $(event_ref[].mouseWheel.y)")
            event_ref[].type == sfEvtMouseWheelScrolled && println("Trigger sfEvtMouseWheelScrolled: $(event_ref[].mouseWheelScroll.wheel)")
            event_ref[].type == sfEvtMouseButtonPressed && println("Trigger sfEvtMouseButtonPressed: $(event_ref[].mouseButton.button)")
            event_ref[].type == sfEvtMouseButtonReleased && println("Trigger sfEvtMouseButtonReleased: $(event_ref[].mouseButton.x), $(event_ref[].mouseButton.y)")
            event_ref[].type == sfEvtMouseMoved && println("Trigger sfEvtMouseMoved: $(event_ref[].mouseMove.x), $(event_ref[].mouseMove.y)")

            if event_ref[].type == sfEvtClosed
                global running = false
                println("Render window closed.")
            elseif event_ref[].type == sfEvtResized
                glViewport(9, 9, event_ref[].size.width, event_ref[].size.height)
                println("Viewport updated.")
            elseif event_ref[].type == sfEvtKeyPressed
                if event_ref[].key.code == sfKeyQ
                    global running = false
                    println("Quiting application.")
                end
            end
        end
        time_event_processing = round(get_time(clock) - t0, digits=9)

        glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)

        glUseProgram(program)

        glActiveTexture(GL_TEXTURE0)
        glBindTexture(GL_TEXTURE_2D, tex[1])
        # gProgram->setUniform("tex", 0);
        # void Program::setUniform(const GLchar* name, OGL_TYPE v0) \
        # glUniform1 ## TYPE_SUFFIX (uniform(name), v0);
        id = glGetUniformLocation(program,"tex")
        println("id ---> $id")
        glUniform1ui(id, 0)

        #glCheckError()

        glEnableVertexAttribArray(0)
        #glVertexAttribPointer(1, 2, GL_FLOAT, GL_TRUE,  5*sizeof(GLfloat), (const GLvoid*)(3 * sizeof(GLfloat)))

        glBindVertexArray(vao)
        glDrawArrays(GL_TRIANGLES, 0 , 3)

        glBindVertexArray(0)
        glUseProgram(0)

        time_render = round(get_time(clock) - time_event_processing - t0, digits=9)
        sfRenderWindow_display(window)
        time_to_sleep = 1 / target_frequency - time_render - time_event_processing

        if time_to_sleep > 0.005
            sleep(time_to_sleep-0.005)
        end
    end
end

sfRenderWindow_destroy(window)


for (func, typ) in [(:clblasCherk, cl.CL_float),
                    (:clblasZherk, cl.CL_double)]

    @eval @blasfun $func(order::clblasOrder, uplo::clblasUplo,
                              trans::clblasTranspose,
                              N::Csize_t, K::Csize_t,
                              alpha::$typ,
                              A::cl.CL_mem, offa::Csize_t, lda::Csize_t,
                              beta::$typ,
                              C::cl.CL_mem, offc::Csize_t, ldc::Csize_t,
                              n_queues::cl.CL_uint,
                              queues::Ptr{cl.CL_command_queue},
                              n_events_in_wait_list::cl.CL_uint,
                              event_wait_list::Ptr{cl.CL_event},
                              events::Ptr{cl.CL_event})

    @eval @blasfun2 $func(order::clblasOrder, uplo::clblasUplo,
                               trans::clblasTranspose,
                               N::Csize_t, K::Csize_t,
                               alpha::$typ,
                               A::cl.CL_mem, offa::Csize_t, lda::Csize_t,
                               beta::$typ,
                               C::cl.CL_mem, offc::Csize_t, ldc::Csize_t)

end


println("Dv) Test of the PairAnnihilation1Photon module with ASF from an internally generated initial- and final-state multiplet.")

@warn("\n\n !!! This example does not work properly at present !!! \n\n")
#
wa = Atomic.Computation("xx",  Nuclear.Model(26.), JAC.Radial.Grid("grid: exponential"), false, [JAC.NoProperty],
     Configuration[],
     Configuration[Configuration("1s^2")],
     Configuration[],
     Configuration[Configuration("1s") ],
     Einstein.Settings(),  Hfs.Settings(),  IsotopeShift.Settings(),   PlasmaShift.Settings(), LandeZeeman.Settings(),
     JAC.PairA1P,  PairAnnihilation1Photon.Settings([E1, M1], [JAC.UseCoulomb, JAC.UseBabushkin], [0.], true, false, Tuple{Int64,Int64}[])  )

wb = perform(wa)

#     [Configuration("[Ne] 3s^2 3p^5"), Configuration("[Ne] 3s 3p^6"), Configuration("[Ne] 3s^2 3p^4 3d")],


# *********************************************************************************
# REopt, Copyright (c) 2019-2020, Alliance for Sustainable Energy, LLC.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# Redistributions of source code must retain the above copyright notice, this list
# of conditions and the following disclaimer.
#
# Redistributions in binary form must reproduce the above copyright notice, this
# list of conditions and the following disclaimer in the documentation and/or other
# materials provided with the distribution.
#
# Neither the name of the copyright holder nor the names of its contributors may be
# used to endorse or promote products derived from this software without specific
# prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
# INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
# OF THE POSSIBILITY OF SUCH DAMAGE.
# *********************************************************************************
function xpress_model(MAXTIME, MIPRELSTOP)
    m = direct_model(Xpress.Optimizer(MAXTIME=-MAXTIME, MIPRELSTOP=MIPRELSTOP, OUTPUTLOG = 0)) #logfile="output.log"))
	m[:solver_name] = "Xpress"
	m[:timeout_seconds] = MAXTIME
	m[:optimality_tolerance] = MIPRELSTOP
	return m
end


# ******************************************************************************************
# Notices:
#
# Copyright © 2022 United States Government as represented by the Administrator of the
# National Aeronautics and Space Administration.  All Rights Reserved.
#
# Disclaimers
#
# No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY KIND,
# EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED TO, ANY WARRANTY
# THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS, ANY IMPLIED WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY
# WARRANTY THAT THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION,
# IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER,
# CONSTITUTE AN ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
# RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS RESULTING FROM
# USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY DISCLAIMS ALL WARRANTIES AND
# LIABILITIES REGARDING THIRD-PARTY SOFTWARE, IF PRESENT IN THE ORIGINAL SOFTWARE, AND
# DISTRIBUTES IT "AS IS."
#
# Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST THE UNITED
# STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL AS ANY PRIOR RECIPIENT.
# IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS IN ANY LIABILITIES, DEMANDS, DAMAGES,
# EXPENSES OR LOSSES ARISING FROM SUCH USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON,
# OR RESULTING FROM, RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND
# HOLD HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
# AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.  RECIPIENT'S SOLE REMEDY FOR ANY
# SUCH MATTER SHALL BE THE IMMEDIATE, UNILATERAL TERMINATION OF THIS AGREEMENT.
# ******************************************************************************************

"""
Compute loss for value function under current policy.
"""
function compute_loss_q(
	ac::MLPActorCritic,
	ac_targ::MLPActorCritic,
	data::NamedTuple,
	gamma::Float64,
	alpha;
    average::Bool = true
)
	o, a, r, o2, d = data
    qs = [q(o, a) for q in ac.qs]
    a2, logp_a2 = ac.pi(o2)

    # Target q-values
    qs_pi_targ = [q(o2, a2) for q in ac_targ.qs]
    q_pi_targ = average ? sum(qs_pi_targ) / length(qs_pi_targ) : min.(qs_pi_targ...)
    backup = r .+ Float32(gamma) .* (1.0f0 .- d) .* (q_pi_targ .- alpha .* logp_a2)

    # MSE loss against Bellman backup
    loss_qs = [mean((q .- backup).^2) for q in qs]
    loss_q = sum(loss_qs)
    return loss_q
end

"""
Compute loss for current policy.
"""
function compute_loss_pi(
    ac::MLPActorCritic,
    data::NamedTuple,
    alpha;
    average::Bool = true
)
    o = data.obs
    pi, logp_pi = ac.pi(o)
    qs_pi = [q(o, pi) for q in ac.qs]
    q_pi = average ? sum(qs_pi) / length(qs_pi) : min.(qs_pi...)

    # Entropy-regularized policy loss
    loss_pi = mean(alpha .* logp_pi .- q_pi)
    return loss_pi
end

"""
Compute loss for current alpha.
"""
function compute_loss_alpha(ac::MLPActorCritic, data::NamedTuple, alpha, target_entropy::Float64)
    o = data.obs
    _, logp_pi = ac.pi(o)
    loss_alpha = mean(-1.0f0 .* alpha .* (logp_pi .+ Float32(target_entropy)))
    return loss_alpha
end

"""
Update policy with observations.
"""
function update(
	ac::MLPActorCritic,
	ac_targ::MLPActorCritic,
	data::NamedTuple,
	q_optimizer::Any,
	pi_optimizer::Any,
	polyak::Float64,
	gamma::Float64,
	alpha_optimizer::Any,
	alpha::AbstractVector{Float32},
	target_entropy::Float64
)
    average = length(ac.qs) > 2 # if ensemble is size 2, take min, otherwise take mean

    # Transfer data to GPU
    data = (; zip(keys(data), dev.(values(data)))...)

    # Gradient descent step for value networks
    loss_q = 0.0
    q_ps = params((q -> q.q).(ac.qs))
    q_gs = gradient(q_ps) do
        loss_q = compute_loss_q(ac, ac_targ, data, gamma, alpha; average=average)
        return loss_q
    end
    Flux.update!(q_optimizer, q_ps, q_gs)

    # Gradient descent step for policy network
    loss_pi = 0.0
    pi_ps = params([ac.pi.net, ac.pi.mu_layer, ac.pi.log_std_layer])
    pi_gs = gradient(pi_ps) do
        loss_pi = compute_loss_pi(ac, data, alpha; average=average)
        return loss_pi
    end
    Flux.update!(pi_optimizer, pi_ps, pi_gs)

    # Gradient descent step for alpha
    loss_alpha = 0.0
    alpha_ps = params(alpha)
    alpha_gs = gradient(alpha_ps) do
        loss_alpha = compute_loss_alpha(ac, data, alpha, target_entropy)
        return loss_alpha
    end
    Flux.update!(alpha_optimizer, alpha_ps, alpha_gs)

    # Update target networks with in-place Polyak averaging
    for (dest, src) in zip(params((q -> q.q).(ac_targ.qs)), params((q -> q.q).(ac.qs)))
        dest .= polyak .* dest .+ (1.0 .- polyak) .* src
    end

    # Logging
    @debug "Losses" loss_q loss_pi loss_alpha
end

"""
Test current policy and generate display statistics.
"""
function test_agent(
    ac::MLPActorCritic,
    test_env::CommonRLInterface.AbstractEnv,
    displays::Vector{<:Tuple},
    max_ep_len::Int,
    num_test_episodes::Int
)

    # Initialize collections for displayed values
    rets, stdevs = Float32[], Float32[]
    dispvals = [[] for _ in displays]

    # Perform rollouts
    for _ in 1:num_test_episodes

        # Single trajectory from deterministic (mean) policy
        d, ep_ret, ep_len = false, 0.0, 0
        reset!(test_env)
        o = observe(test_env)
        os, as = Vector{Float32}[], Vector{Float32}[]
        while !(d || ep_len > max_ep_len)
            a = ac(o)
            push!(os, o)
            push!(as, a)
            d = terminated(test_env)
            r = act!(test_env, a)
            o = observe(test_env)
            ep_ret += r
            ep_len += 1
        end

        # Batch process q-values
        O = reduce(hcat, os)
        A = reduce(hcat, as)
        qs = [q(O, A) for q in ac.qs]
        Q = reduce(hcat, qs)
        append!(stdevs, vec(std(Q, dims=2)))

        # Compute custom statistics and add to collections
        push!(rets, ep_ret)
        push!.(dispvals, f(test_env) for (_, f) in displays)
    end

    # Average displayed values
    dispvals = [rets, stdevs, dispvals...]
    dispvals_avg = mean.(dispvals)
    return dispvals_avg
end

"""
Soft actor-critic algorithm.
"""
Base.@kwdef mutable struct SAC <: GlobalSolver
    # Environment
    obs_dim::Int                                    # dimension of observation space
    act_dim::Int                                    # dimension of action space
    act_mins::Vector{Float64}                       # minimum values of actions
    act_maxs::Vector{Float64}                       # maximum values of actions
    gamma::Float64 = 0.999                          # discount factor

    # Replay buffer
    max_buffer_size::Int = 100000                   # maximum number of timesteps in buffer
    buffer::ReplayBuffer = ReplayBuffer(			# replay buffer
  		obs_dim, act_dim, max_buffer_size)

    # Actor-critic network
    ac::Union{MLPActorCritic, Nothing} = nothing    # actor-critic object
    hidden_sizes::Vector{Int} = [100,100,100]       # dimensions of any hidden layers
    num_q::Int = 2                                  # size of critic ensemble
    activation::Function = SoftActorCritic.relu     # activation after each hidden layer
    linearized::Bool = false                        # linearized policy squashing

    # Training
    q_optimizer::Any = AdaBelief(1e-4)              # optimizer for value networks
    pi_optimizer::Any = AdaBelief(1e-4)             # optimizer for policy network
    alpha_optimizer::Any = AdaBelief(1e-4)          # optimizer for alpha
    batch_size::Int = 64                            # size of each update to networks
    epochs::Int = 200                               # number of epochs
    steps_per_epoch::Int = 200                      # steps of simulation per epoch
    start_steps::Int = 1000                         # steps before following policy
    max_ep_len::Int = 50                            # maximum number of steps per episode
    update_after::Int = 300                         # steps before networks begin to update
    update_every::Int = 50                          # steps between updates
    num_batches::Int = update_every                 # number of batches per update
    polyak::Float64 = 0.995                         # target network averaging parameter
    target_entropy::Float64 = -act_dim              # target entropy (default is heuristic)
    rng::AbstractRNG = Random.GLOBAL_RNG            # random number generator
    use_gpu::Bool = true                            # use GPU if available

    # Testing
    num_test_episodes::Int = 100                    # number of test episodes
    displays::Vector{Tuple} = Tuple[]               # display values (list of tuples of
                                                    # name and function to apply to MDP
                                                    # after each trajectory)
    info::Dict{String, Any} = Dict{String, Any}()   # accumulated stats
    progress::Bool = true                           # show progress bar

    # Checkpointing
    save::Bool = false                              # to enable checkpointing
    save_every::Int = 10000                         # steps between checkpoints
    save_dir::String = DEFAULT_SAVE_DIR             # directory to save checkpoints
    max_saved::Int = 0                              # maximum number of checkpoints; set to
                                                    # zero or negative for unlimited
end

function Solvers.solve(sac::SAC, env_fn::Function)
    # Initialize AC agent and auxiliary data structures
    env = env_fn()
    env.flatten = true # TODO: infer automatically
    test_env = env_fn()

    set_gpu_status(sac.use_gpu)
    if sac.ac === nothing
        sac.ac = MLPActorCritic(sac.obs_dim, sac.act_dim, sac.act_mins, sac.act_maxs,
            sac.hidden_sizes, sac.num_q, sac.activation, sac.rng, sac.linearized)
    end
    ac = sac.ac
    ac_targ = deepcopy(ac)
    ac_cpu = to_cpu(ac)
    alpha = [1.0f0] |> dev
    total_steps = sac.steps_per_epoch * sac.epochs
    mkpath(sac.save_dir)

    # Initialize displayed information and progress meter
    @debug "Solve" total_steps
    disp_tups = initialize(sac.displays)
    p = Progress(total_steps - sac.update_after; enabled=sac.progress)

    ep_ret, ep_len = 0.0, 0
    reset!(env)
    o = observe(env)
    for t in 1:total_steps
        # Choose action
    	random_policy = t <= sac.start_steps
    	a = random_policy ? rand(actions(env)) : ac_cpu(o)

        # Step environment
        d = terminated(env)
        r = act!(env, a)
        o2 = observe(env)
        ep_ret += r
        ep_len += 1

        # Ignore done signal if due to overtime
        d = (ep_len > sac.max_ep_len) ? false : d

        store!(sac.buffer, o, a, r, o2, d)
        o = o2

        # End of trajectory handling
        if d || ep_len > sac.max_ep_len
            ep_ret, ep_len = 0.0, 0
            reset!(env)
            o = observe(env)
        end

        t <= sac.update_after && continue

        # Actor-critic update
        if t % sac.update_every == 0
            @debug "Updating models" t
            for b in 1:sac.num_batches
                @debug "Batch" b
                batch = sample_batch(sac.buffer, sac.batch_size)
                update(ac, ac_targ, batch, sac.q_optimizer, sac.pi_optimizer, sac.polyak,
                    sac.gamma, sac.alpha_optimizer, alpha, sac.target_entropy)
            end
            ac_cpu = to_cpu(ac)
        end

        # End of epoch handling
        epoch = (t - 1) ÷ sac.steps_per_epoch + 1
        if t % sac.steps_per_epoch == 0
            # Update display values
            disp_vals = test_agent(ac_cpu, test_env, sac.displays, sac.max_ep_len, sac.num_test_episodes)
            update!(disp_tups, disp_vals)

            # Log info about epoch
            @debug("Evaluation",
                alpha,
                mean(mean.(params([q.q for q in ac_cpu.qs]))),
                mean(mean.(params([ac_cpu.pi.net, ac_cpu.pi.mu_layer, ac_cpu.pi.log_std_layer])))
            )
        end

        # Checkpointing
        if sac.save && t % sac.save_every == 0
            checkpoint(ac_cpu, sac.save_dir, sac.max_saved)
        end

        # Progress meter
        sac.progress && ProgressMeter.next!(p; showvalues=gen_showvalues(epoch, disp_tups))
    end

    # Save display values
    for (sym, hist) in disp_tups
        k = String(sym)
        if k in keys(sac.info)
            append!(sac.info[k], hist)
        else
            sac.info[k] = hist
        end
    end

    ac_cpu.pi.rng_gpu = nothing
    return ac_cpu, sac.info
end


using Test, YaoBlocks, BitBasis
using YaoBlocks: check_dumpload

@testset "check_dumpload" begin
    @test check_dumpload(X)
    @test check_dumpload(X + Y)
    @test check_dumpload(+(put(5, 4 => X), put(5, 5 => X)))
    @test check_dumpload(kron(put(1, 1 => X), Y))
    @test check_dumpload(kron(5, 2 => X, 4 => Y))
    @test check_dumpload(shift(0.5))
    @test check_dumpload(phase(0.5))
    @test check_dumpload(time_evolve(X, 0.5))
    @test check_dumpload(put(5, 2 => X))
    @test check_dumpload(chain(put(5, 2 => X)))
    @test check_dumpload(put(5, 2 => rot(X, 0.5)))
    @test check_dumpload(control(5, 1, 2 => rot(X, 0.5)))
    @test check_dumpload(control(5, (1, -4), 2 => rot(X, 0.5)))
    @test check_dumpload(subroutine(5, rot(SWAP, 0.5), (2, 5)))
    @test check_dumpload(repeat(5, put(1, 1 => X), (2, 5)))
    @test check_dumpload(Measure(5))
    @test check_dumpload(Measure(5, operator = put(5, 2 => X)))
    @test check_dumpload(Measure(5, locs = (3, 1), resetto = bit"01"))
    @test check_dumpload(
        Measure(5, locs = (3, 2), operator = put(2, 2 => X), resetto = bit"11"),
    )
    @test check_dumpload(Daggered(X))
    @test check_dumpload(2 * X)
    @test check_dumpload(cache(2 * X))
    @test_throws ErrorException check_dumpload(kron(5, 2:3 => SWAP))
end

@testset "yao macro" begin
    c = yao"""
    let nqubits = 5
        3 => rot(X, 0.3)
        2 => X
    end
    """
    c2 = @yaoscript let nqubits = 5
        3 => rot(X, 0.3)
        2 => X
    end

    y = chain(5, put(3 => rot(X, 0.3)), put(2 => X))
    @test c == y
    @test c == c2
    @test check_dumpload(y)
    yaotofile("_test.yao", y)
    yy = @eval $(yaofromfile("_test.yao"))
    @test y == yy

    g = eval(yaofromfile(joinpath(dirname(@__FILE__), "yaoscript.yao")))
    s = string(yaotoscript(g))
    g1 = eval(yaofromstring(s))
    @test g == g1
end


##==============================================================================
## Abstract Types
##==============================================================================

abstract type InferenceVariable end

##==============================================================================
## VariableNodeData
##==============================================================================

"""
$(TYPEDEF)
Data container for solver-specific data.

  ---
Fields:
$(TYPEDFIELDS)
"""
mutable struct VariableNodeData{T<:InferenceVariable, P}
    val::Vector{P}
    bw::Matrix{Float64}
    BayesNetOutVertIDs::Vector{Symbol}
    dimIDs::Vector{Int} # Likely deprecate

    dims::Int
    eliminated::Bool
    BayesNetVertID::Symbol #  Union{Nothing, }
    separator::Vector{Symbol}

    variableType::T
    initialized::Bool
    """
    Replacing previous `inferdim::Float64`, new `.infoPerCoord::Vector{Float64}` will in
    future stores the amount information (per measurement dimension) captured in each
    coordinate dimension.
    """
    infoPerCoord::Vector{Float64}
    ismargin::Bool

    dontmargin::Bool
    solveInProgress::Int
    solvedCount::Int
    solveKey::Symbol

    events::Dict{Symbol,Threads.Condition}

    # VariableNodeData{T,P}() = new{T,P}()
    VariableNodeData{T,P}(w...)  where {T <:InferenceVariable, P} = new{T,P}(w...)
    VariableNodeData{T,P}(;solveKey::Symbol=:default ) where {T <:InferenceVariable, P} = new{T,P}(
            Vector{P}(),
            zeros(0,0),
            Symbol[],
            Int[],
            0,
            false,
            :NOTHING,
            Symbol[],
            T(),
            false,
            Float64[0.0;],
            false,
            false,
            0,
            0,
            solveKey,
            Dict{Symbol,Threads.Condition}() )
    #
end

##------------------------------------------------------------------------------
## Constructors

VariableNodeData{T}(;solveKey::Symbol=:default ) where T <: InferenceVariable = VariableNodeData{T, getPointType(T)}(solveKey=solveKey)

# VariableNodeData(   val::Vector{P},
#                     bw::Matrix{<:Real},
#                     BayesNetOutVertIDs::AbstractVector{Symbol},
#                     dimIDs::AbstractVector{Int},
#                     dims::Int,
#                     eliminated::Bool,
#                     BayesNetVertID::Symbol,
#                     separator::Array{Symbol,1},
#                     variableType::T,
#                     initialized::Bool,
#                     inferdim::Float64,
#                     ismargin::Bool,
#                     dontmargin::Bool,
#                     solveInProgress::Int=0,
#                     solvedCount::Int=0,
#                     solveKey::Symbol=:default,
#                     events::Dict{Symbol,Threads.Condition}=Dict{Symbol,Threads.Condition}()
#                 ) where {T <: InferenceVariable, P} =
#                     VariableNodeData{T,P}(  val,bw,BayesNetOutVertIDs,dimIDs,dims,
#                                             eliminated,BayesNetVertID,separator,
#                                             variableType,initialized,inferdim,ismargin,
#                                             dontmargin, solveInProgress, solvedCount,
#                                             solveKey, events  )
#

#

VariableNodeData(   val::Vector{P},
                    bw::AbstractMatrix{<:Real},
                    BayesNetOutVertIDs::AbstractVector{Symbol},
                    dimIDs::AbstractVector{Int},
                    dims::Int,
                    eliminated::Bool,
                    BayesNetVertID::Symbol,
                    separator::AbstractVector{Symbol},
                    variableType::T,
                    initialized::Bool,
                    ipc::AbstractVector{<:Real},
                    ismargin::Bool,
                    dontmargin::Bool,
                    solveInProgress::Int=0,
                    solvedCount::Int=0,
                    solveKey::Symbol=:default,
                    events::Dict{Symbol,Threads.Condition}=Dict{Symbol,Threads.Condition}()
                ) where {T <: InferenceVariable, P} =
                    VariableNodeData{T,P}(  val,bw,BayesNetOutVertIDs,dimIDs,dims,
                                            eliminated,BayesNetVertID,separator,
                                            variableType,initialized,ipc,ismargin,
                                            dontmargin, solveInProgress, solvedCount,
                                            solveKey, events  )
#

function VariableNodeData(variableType::T; solveKey::Symbol=:default) where T <: InferenceVariable
    #
    # p0 = getPointIdentity(T)
    P0 = Vector{getPointType(T)}()
    # P0[1] = p0
    BW = zeros(0,0)
    # BW[1] = zeros(getDimension(T))
    VariableNodeData(   P0, BW, Symbol[], Int[],
                        0, false, :NOTHING, Symbol[],
                        variableType, false, 0.0, false,
                        false, 0, 0, solveKey  )
end

##==============================================================================
## PackedVariableNodeData.jl
##==============================================================================

"""
$(TYPEDEF)
Packed VariabeNodeData structure for serializing DFGVariables.

  ---
Fields:
$(TYPEDFIELDS)
"""
mutable struct PackedVariableNodeData
    vecval::Array{Float64,1}
    dimval::Int
    vecbw::Array{Float64,1}
    dimbw::Int
    BayesNetOutVertIDs::Array{Symbol,1} # Int
    dimIDs::Array{Int,1}
    dims::Int
    eliminated::Bool
    BayesNetVertID::Symbol # Int
    separator::Array{Symbol,1} # Int
    variableType::String
    initialized::Bool
    infoPerCoord::Vector{Float64}
    ismargin::Bool
    dontmargin::Bool
    solveInProgress::Int
    solvedCount::Int
    solveKey::Symbol
    PackedVariableNodeData() = new()
    PackedVariableNodeData(x1::Vector{Float64},
                         x2::Int,
                         x3::Vector{Float64},
                         x4::Int,
                         x5::Vector{Symbol}, # Int
                         x6::Vector{Int},
                         x7::Int,
                         x8::Bool,
                         x9::Symbol, # Int
                         x10::Vector{Symbol}, # Int
                         x11::String,
                         x12::Bool,
                         x13::AbstractVector{<:Real},
                         x14::Bool,
                         x15::Bool,
                         x16::Int,
                         solvedCount::Int,
                         solveKey::Symbol) = new(x1,x2,x3,x4,x5,x6,x7,x8,x9,x10,x11,x12,x13,x14,x15,x16, solvedCount, solveKey)
     # More permissive constructor needed for unmarshalling
     PackedVariableNodeData(x1::Vector,
                          x2::Int,
                          x3::Vector,
                          x4::Int,
                          x5::Vector, # Int
                          x6::Vector,
                          x7::Int,
                          x8::Bool,
                          x9::Symbol, # Int
                          x10::Vector, # Int
                          x11::String,
                          x12::Bool,
                          x13::AbstractVector{<:Real},
                          x14::Bool,
                          x15::Bool,
                          x16::Int,
                          solvedCount::Int,
                          solveKey::Symbol) = new(
                                convert(Vector{Float64},x1),x2,
                                convert(Vector{Float64},x3),x4,
                                convert(Vector{Symbol},x5),
                                convert(Vector{Int},x6),x7,x8,x9,
                                convert(Vector{Symbol},x10),x11,x12,
                                x13,x14,x15,x16, solvedCount, solveKey )
end

##==============================================================================
## PointParametricEst
##==============================================================================

##------------------------------------------------------------------------------
## AbstractPointParametricEst interface
##------------------------------------------------------------------------------

abstract type AbstractPointParametricEst end

##------------------------------------------------------------------------------
## MeanMaxPPE
##------------------------------------------------------------------------------
"""
    $TYPEDEF

Data container to store Parameteric Point Estimate (PPE) for mean and max.
"""
struct MeanMaxPPE <: AbstractPointParametricEst
    # repeat key value internally (from a design request by Sam)
    solveKey::Symbol
    suggested::Vector{Float64}
    max::Vector{Float64}
    mean::Vector{Float64}
    lastUpdatedTimestamp::DateTime
end

##------------------------------------------------------------------------------
## Constructors

MeanMaxPPE(solveKey::Symbol, suggested::Vector{Float64}, max::Vector{Float64}, mean::Vector{Float64}) = MeanMaxPPE(solveKey, suggested, max, mean, now(UTC))

## Metadata
"""
    $SIGNATURES
Return the fields of MeanMaxPPE that are estimates.
NOTE: This is needed for each AbstractPointParametricEst.
Closest we can get to a decorator pattern.
"""
getEstimateFields(::MeanMaxPPE) = [:suggested, :max, :mean]

##==============================================================================
## DFG Variables
##==============================================================================

const SmallDataTypes = Union{Int, Float64, String, Bool, Vector{Int}, Vector{Float64}, Vector{String}, Vector{Bool}}

##------------------------------------------------------------------------------
## DFGVariable lv2
##------------------------------------------------------------------------------
"""
$(TYPEDEF)
Complete variable structure for a DistributedFactorGraph variable.

  ---
Fields:
$(TYPEDFIELDS)
"""
struct DFGVariable{T<:InferenceVariable} <: AbstractDFGVariable
    """Variable label, e.g. :x1.
    Accessor: [`getLabel`](@ref)"""
    label::Symbol
    """Variable timestamp.
    Accessors: [`getTimestamp`](@ref), [`setTimestamp`](@ref)"""
    timestamp::ZonedDateTime
    """Nano second time, for more resolution on timestamp (only subsecond information)"""
    nstime::Nanosecond
    """Variable tags, e.g [:POSE, :VARIABLE, and :LANDMARK].
    Accessors: [`getTags`](@ref), [`mergeTags!`](@ref), and [`removeTags!`](@ref)"""
    tags::Set{Symbol}
    """Dictionary of parametric point estimates keyed by solverDataDict keys
    Accessors: [`addPPE!`](@ref), [`updatePPE!`](@ref), and [`deletePPE!`](@ref)"""
    ppeDict::Dict{Symbol, <: AbstractPointParametricEst}
    """Dictionary of solver data. May be a subset of all solutions if a solver key was specified in the get call.
    Accessors: [`addVariableSolverData!`](@ref), [`updateVariableSolverData!`](@ref), and [`deleteVariableSolverData!`](@ref)"""
    solverDataDict::Dict{Symbol, <: VariableNodeData{T}}
    """Dictionary of small data associated with this variable.
    Accessors: [`getSmallData`](@ref), [`setSmallData!`](@ref)"""
    smallData::Dict{Symbol, SmallDataTypes}
    """Dictionary of large data associated with this variable.
    Accessors: [`addDataEntry!`](@ref), [`getDataEntry`](@ref), [`updateDataEntry!`](@ref), and [`deleteDataEntry!`](@ref)"""
    dataDict::Dict{Symbol, AbstractDataEntry}
    """Solvable flag for the variable.
    Accessors: [`getSolvable`](@ref), [`setSolvable!`](@ref)"""
    solvable::Base.RefValue{Int}
end

##------------------------------------------------------------------------------
## Constructors

"""
    $SIGNATURES
The default DFGVariable constructor.
"""
function DFGVariable(label::Symbol, variableType::Type{T};
            timestamp::Union{DateTime,ZonedDateTime}=now(localzone()),
            nstime::Nanosecond = Nanosecond(0),
            tags::Set{Symbol}=Set{Symbol}(),
            estimateDict::Dict{Symbol, <: AbstractPointParametricEst}=Dict{Symbol, MeanMaxPPE}(),
            solverDataDict::Dict{Symbol, VariableNodeData{T,P}}=Dict{Symbol, VariableNodeData{T,getPointType(T)}}(),
            smallData::Dict{Symbol, SmallDataTypes}=Dict{Symbol, SmallDataTypes}(),
            dataDict::Dict{Symbol, AbstractDataEntry}=Dict{Symbol,AbstractDataEntry}(),
            solvable::Int=1) where {T <: InferenceVariable, P}
    #
    if timestamp isa DateTime
        DFGVariable{T}(label, ZonedDateTime(timestamp, localzone()), nstime, tags, estimateDict, solverDataDict, smallData, dataDict, Ref(solvable))
    else
        DFGVariable{T}(label, timestamp, nstime, tags, estimateDict, solverDataDict, smallData, dataDict, Ref(solvable))
    end
end

DFGVariable(label::Symbol,
            variableType::T;
            solverDataDict::Dict{Symbol, VariableNodeData{T,P}}=Dict{Symbol, VariableNodeData{T,getPointType(T)}}(),
            kw...) where {T <: InferenceVariable, P} = DFGVariable(label, T; solverDataDict=solverDataDict, kw...)
#

function DFGVariable(label::Symbol,
            solverData::VariableNodeData{T};
            timestamp::Union{DateTime,ZonedDateTime}=now(localzone()),
            nstime::Nanosecond = Nanosecond(0),
            tags::Set{Symbol}=Set{Symbol}(),
            estimateDict::Dict{Symbol, <: AbstractPointParametricEst}=Dict{Symbol, MeanMaxPPE}(),
            smallData::Dict{Symbol, SmallDataTypes}=Dict{Symbol, SmallDataTypes}(),
            dataDict::Dict{Symbol, <: AbstractDataEntry}=Dict{Symbol,AbstractDataEntry}(),
            solvable::Int=1) where {T <: InferenceVariable}
    #
    if timestamp isa DateTime
        DFGVariable{T}(label, ZonedDateTime(timestamp, localzone()), nstime, tags, estimateDict, Dict{Symbol, VariableNodeData{T, getPointType(T)}}(:default=>solverData), smallData, dataDict, Ref(solvable))
    else
        DFGVariable{T}(label, timestamp, nstime, tags, estimateDict, Dict{Symbol, VariableNodeData{T, getPointType(T)}}(:default=>solverData), smallData, dataDict, Ref(solvable))
    end
end

Base.getproperty(x::DFGVariable,f::Symbol) = begin
    if f == :solvable
        getfield(x,f)[]
    else
        getfield(x,f)
    end
end

Base.setproperty!(x::DFGVariable,f::Symbol, val) = begin
    if f == :solvable
        getfield(x,f)[] = val
    else
        setfield!(x,f,val)
    end
end

##------------------------------------------------------------------------------
# TODO: can't see the reason to overwrite copy, leaving it here for now
# function Base.copy(o::DFGVariable)::DFGVariable
#     return DFGVariable(o.label, getVariableType(o)(), tags=copy(o.tags), estimateDict=copy(o.estimateDict),
#                         solverDataDict=copy(o.solverDataDict), smallData=copy(o.smallData),
#                         dataDict=copy(o.dataDict), solvable=getSolvable(o))
# end

##------------------------------------------------------------------------------
## DFGVariableSummary lv1
##------------------------------------------------------------------------------

"""
$(TYPEDEF)
Summary variable structure for a DistributedFactorGraph variable.

  ---
Fields:
$(TYPEDFIELDS)
"""
struct DFGVariableSummary <: AbstractDFGVariable
    """Variable label, e.g. :x1.
    Accessor: [`getLabel`](@ref)"""
    label::Symbol
    """Variable timestamp.
    Accessors: [`getTimestamp`](@ref), [`setTimestamp`](@ref)"""
    timestamp::ZonedDateTime
    """Variable tags, e.g [:POSE, :VARIABLE, and :LANDMARK].
    Accessors: [`getTags`](@ref), [`mergeTags!`](@ref), and [`removeTags!`](@ref)"""
    tags::Set{Symbol}
    """Dictionary of parametric point estimates keyed by solverDataDict keys
    Accessors: [`addPPE!`](@ref), [`updatePPE!`](@ref), and [`deletePPE!`](@ref)"""
    ppeDict::Dict{Symbol, <:AbstractPointParametricEst}
    """Symbol for the variableType for the underlying variable.
    Accessor: [`getVariableType`](@ref)"""
    variableTypeName::Symbol
    """Dictionary of large data associated with this variable.
    Accessors: [`addDataEntry!`](@ref), [`getDataEntry`](@ref), [`updateDataEntry!`](@ref), and [`deleteDataEntry!`](@ref)"""
    dataDict::Dict{Symbol, AbstractDataEntry}
end

##------------------------------------------------------------------------------
## SkeletonDFGVariable.jl
##------------------------------------------------------------------------------

"""
$(TYPEDEF)
Skeleton variable structure for a DistributedFactorGraph variable.

  ---
Fields:
$(TYPEDFIELDS)
"""
struct SkeletonDFGVariable <: AbstractDFGVariable
    """Variable label, e.g. :x1.
    Accessor: [`getLabel`](@ref)"""
    label::Symbol
    """Variable tags, e.g [:POSE, :VARIABLE, and :LANDMARK].
    Accessors: [`getTags`](@ref), [`mergeTags!`](@ref), and [`removeTags!`](@ref)"""
    tags::Set{Symbol}
end

SkeletonDFGVariable(label::Symbol) = SkeletonDFGVariable(label, Set{Symbol}())

##==============================================================================
# Define variable levels
##==============================================================================
const VariableDataLevel0 = Union{DFGVariable, DFGVariableSummary, SkeletonDFGVariable}
const VariableDataLevel1 = Union{DFGVariable, DFGVariableSummary}
const VariableDataLevel2 = Union{DFGVariable}

##==============================================================================
## Convertion constructors
##==============================================================================

DFGVariableSummary(v::DFGVariable) =
        DFGVariableSummary(v.label, v.timestamp, deepcopy(v.tags), deepcopy(v.ppeDict), Symbol(typeof(getVariableType(v))), v.dataDict)

SkeletonDFGVariable(v::VariableDataLevel1) =
            SkeletonDFGVariable(v.label, deepcopy(v.tags))


printstyled("  time test with synthetic timeseries data\n", color=:light_green)

ts = round(Int64, sμ*d2u(DateTime("2020-03-01T00:00:00")))
fs = 100.0
gain = 32.0
id = "XX.STA..BHZ"
loc = GeoLoc( lat = 45.560121,
              lon = -122.617068,
              el = 53.04 )
Δ = 10000
files = ["1.sac", "2.sac", "3.sac", "4.sac", "5.sac", "6.sac", "7.sac"]
tos   = [    0.0,    10.0,   20.01,   20.02,    30.0,    40.0,    60.0]
nx    = [   1000,     999,       1,     100,       1,    1000,    1000]

C = SeisChannel(id = id,
                gain = gain,
                loc = loc,
                fs = fs )
S0 = SeisData()

printstyled("    creating files\n", color=:light_green)
for i = 1:length(files)
  fname = files[i]
  C.x = randn(Float32, nx[i])
  C.t = [1 ts+round(Int64, tos[i]*1000000); nx[i] 0]
  push!(S0, C)
  writesac(C, fname=fname)
end

# Read back in
printstyled("    reading to test all channel-extension cases\n", color=:light_green)
S = SeisData()
read_data!(S, "sac", files[1])
xi = cumsum(nx)
δt = round.(Int64, tos.*sμ) .+ (nx.-1).*Δ

# Expect:
# no gaps, precise read
@test S.t[1] == [1 ts; nx[1] 0]
@test endtime(S.t[1], S.fs[1]) == Δ*(nx[1]-1)+ts
@test S0.x[1] == S.x[1]
@test S0.gain[1] == S.gain[1]

# append file 2 to create case 1
read_data!(S, "sac", files[2])
@test S.t[1] == [1 ts; xi[2] 0]
@test S0.x[2] == S.x[1][nx[1]+1:xi[2]]

# append file 3 to create case 4
read_data!(S, "sac", files[3])
@test S.t[1] == [1 ts; xi[3] 20000]
@test S0.x[3] == S.x[1][xi[2]+1:xi[3]]

# append file 4 to create case 2
read_data!(S, "sac", files[4])
@test S.t[1] == [1 ts; xi[3] 20000; xi[4] 0]
@test S0.x[4] == S.x[1][xi[3]+1:xi[4]]

# append file 5 to create case 3
δt0 = round(Int64, sμ*(tos[5]-tos[4]-nx[4]/fs)) # I canceled a +Δ and a -Δ
read_data!(S, "sac", files[5])
@test S.t[1] == [1 ts; xi[3] 20000; xi[5] δt0]
@test S0.x[5] == S.x[1][xi[4]+1:xi[5]]

# append file 6 to create case 6
t_old = copy(S.t[1])
δt0 = round(Int64, sμ*(tos[6]-tos[5]-nx[5]/fs)) # I canceled a +Δ and a -Δ
read_data!(S, "sac", files[6])
@test S.t[1] == vcat(t_old, [xi[5]+1 δt0; xi[6] 0])
@test S0.x[6] == S.x[1][xi[5]+1:xi[6]]

# append file 7 to create case 5
t_old = copy(S.t[1])
read_data!(S, "sac", files[7])
δt0 = round(Int64, sμ*(tos[7]-tos[6]-nx[7]/fs))
@test S.t[1] == vcat(t_old[1:end-1,1:2], [t_old[end,1]+1 δt0], [xi[7] 0])

# finally, did we get it all right?
printstyled("    final checks\n", color=:light_green)
T = t_collapse(vcat([t_expand(i, fs) for i in S0.t]...), fs)
X = vcat([i for i in S0.x]...)
@test T == S.t[1]
@test X == S.x[1]
@test gain == S.gain[1]
@test fs == S.fs[1]
@test id == S.id[1]
@test ≈(loc.lat, S.loc[1].lat, rtol=2eps(Float32))
@test ≈(loc.lon, S.loc[1].lon, rtol=2eps(Float32))
@test ≈(loc.el, S.loc[1].el, rtol=2eps(Float32))

printstyled("    cleanup\n", color=:light_green)
for i in files
  safe_rm(i)
end


struct IndexedCounts{M<:AbstractMer,C<:CountMode}
    mer_index::Vector{M}            # Ordered list of kmer that contain counts
    count_names::Vector{Symbol}     # Names of the count vectors
    counts::Vector{Vector{UInt16}}  # Count vectors, each contains an entry per mer in the index
    name::Symbol                    # The name of this MerCounts
end

# Construction of empty MerCounts with a name
@inline function IndexedCounts{M,C}(name::Symbol) where {M<:AbstractMer,C<:CountMode}
    return IndexedCounts{M,C}(Vector{M}(), Vector{Symbol}(), Vector{Vector{UInt16}}(), name)
end

@inline function IndexedCounts(::Type{M}, name::Symbol, mode::C = CANONICAL) where {M<:AbstractMer,C<:CountMode}
    return IndexedCounts{M,C}(name)
end

# Construction from a sequence distance graph
function IndexedCounts(name::Symbol, sequences, mode::C = CANONICAL) where {M<:AbstractMer,C<:CountMode}
    @info string("Creating an empty kmer count datastore called ", name)
    mc = IndexedCounts{M,C}(name, mode)
    index!(mc, sequences)
    return mc
end

function _count_and_collapse!(mers::Vector{M}, counts::Vector{UInt16}) where {M<:AbstractMer}
    wi = 1
    ri = 1
    stop = lastindex(mers) + 1
    @inbounds while ri < stop
        mers[wi] = mers[ri]
        ci = one(UInt16)
        while (ri += 1) < stop && mers[ri] == mers[wi]
            ci = ci + one(UInt16)
        end
        push!(counts, ci)
        wi = wi + 1
    end
    resize!(mers, ri)
    return nothing
end

function index!(mc::IndexedCounts{M,C}, ref_sequences::Vector{<:LongSequence}) where {M<:AbstractMer,C<:CountMode}
    @info "Indexing kmer counts of reference sequences"
    @info "Populating index with reference sequence mers"
    # Add all Kmers from reference sequences.
    # t = expected number of kmers.
    t = sum(length(x) for x in ref_sequences) - BioSequences.ksize(M) + 1
    mcindex = mc.mer_index
    resize!(mcindex, t)
    vec_i = 1
    f = C()
    for seq in ref_sequences
        for mer in each(M, seq)
            mcindex[vec_i] = f(mer)
            vec_i = vec_i + 1
        end
    end
    resize!(mcindex, vec_i)
    @info "Sorting the index"
    # Sort the index
    sort!(mcindex)
    @info "Counting and collapsing mers in index"
    # Create a first count;
    # Collapse the kmer index, saving the coverage to the first count.
    first_count = Vector{UInt16}()
    sizehint!(first_count, length(mcindex))
    push!(mc.counts, first_count)
    push!(mc.count_names, :sdg)
    _count_and_collapse!(mcindex, first_count)
    return mc
end

function Base.summary(io::IO, mc::IndexedCounts{M}) where {M}
    print(io, BioSequences.ksize(M), "-mer Indexed Counts Datastore '",  mc.name, "': ", length(mc.counts), " stored ", BioSequences.ksize(M), "-mer counts")
end

function Base.show(io::IO, mc::IndexedCounts{M}) where {M}
    summary(io, mc)
end


#=
Copyright (c) 2018-2022 Chris Coey, Lea Kapelevich, and contributors

This Julia package Hypatia.jl is released under the MIT license; see LICENSE
file in the root directory or at https://github.com/chriscoey/Hypatia.jl
=#

relaxed_tols = (default_tol_relax = 100,)
insts = OrderedDict()
insts["minimal"] = [
    ((1, 1, 1, 2, true, true, false), nothing, relaxed_tols),
    ((1, 1, 1, 2, true, false, true),),
    ((1, 1, 1, 2, false, true, false),),
    ((1, 1, 1, 2, false, false, true),),
    ((1, 1, 1, 2, false, false, false),),
    ]
insts["fast"] = [
    ((2, 2, 2, 2, true, true, false),),
    ((2, 2, 2, 2, true, false, true),),
    ((2, 2, 2, 2, false, true, false),),
    ((2, 2, 2, 2, false, false, true),),
    ((2, 2, 2, 2, false, false, false),),
    ((4, 2, 3, 5, true, true, false),),
    ((4, 2, 3, 5, true, false, true),),
    ]
insts["various"] = vcat(insts["fast"], [
    ((4, 2, 3, 5, false, true, false),),
    ((4, 2, 3, 5, false, false, true),),
    ((4, 2, 3, 5, false, false, false),),
    ])
return (PolyNormJuMP, insts)


module Weights

import JuLIP, IPFitting
using IPFitting: LsqDB, Dat
using JuLIP: forces, Atoms, positions

function hess_weights!(cn::String, db::LsqDB; h = :auto, wE = 0.0, rscal = 3, verbose=false)

   @error("`hess_weights!` needs to be rewritten!")

   # temporarily assume h is a floating point number
   # => this needs to be automated
   @assert (h isa AbstractFloat)

   # loop through the data_groups
   for (key, dg) in db.data_groups
      # if the data_group has the correct configname
      if cn == configname(key)
         # then loop through the dats
         for d in dg
            if forces(d) == nothing
               verbose && @warn("""hess_weights!: a training configuration does
                                  not contain energy and forces => ignore""")
               d.info["W"] = Dict( "F" => zeros(3*length(d)),
                                   "E" => 0.0,
                                   "V" => zeros(6) )
               continue
            end
            # get configuration info, R vectors
            at = Atoms(d)
            X = positions(at)
            R = [ JuLIP.project_min(at, x - X[1])  for x in X ]

            # if h == :auto => COMPUTE h <<<<<<<<

            # now fix the scaling for the force weights
            r = norm.(R)
            r3 = (ones(3) * r')[:]
            wF = r3.^rscal / h
            wF[1:3] = 0.0   # this sets the weight for atom 1 to zero
                            # it contains no information...
            # assign this weight back to `d::Dat`
            d.info["W"] = Dict( "E" => wE,
                                "F" => wF,
                                "V" => zeros(6) )
         end
      end
   end

   return nothing
end

end # module

# function hess_weights_hook!(w, d::Dat)
#    at = Atoms(d)
#    if energy(d) == nothing || forces(d) == nothing
#       warn("""hess_weights_hook!: a training configuration does not contain
#               energy and forces => ignore""")
#      return w
#   end
#    # compute R vectors
#    X = positions(at)
#    h = 0.01 # norm(X[1])
#    # if h < 1e-5 || h > 0.02
#    #    warn("unexpected location of X[1] in hess_weights_hook => ignore")
#    #    @show X[1], h
#    #    return w
#    # end
#
#    # give this energy a lot of weight to make sure we match the
#    # ground state (which we assume this is)
#    w[1] = 0.0
#
#    # now fix the scaling for the force weights
#    # X[1] *= 0
#    R = [ JuLIP.project_min(at, x - X[1])  for x in X ]
#    r = norm.(R)
#    r3 = (ones(3) * r')[:]
#    If = 2:(1+3*length(R))
#    w[If] .= w[If] .* (r3.^7) / h
#    w[2] = 0.0
#    return w
# end


# ------------------------------------------------------------------
# Licensed under the ISC License. See LICENSE in the project root.
# ------------------------------------------------------------------

"""
    CrossValidation(k, [shuffle])

Compare estimation solvers using k-fold cross-validation.
Optionally shuffle the data (default to true).

    CrossValidation(partitioner)

Compare estimation solvers using cross-validation by
splitting the data with a `partitioner`. This method
is a generalization of k-fold cross-validation, which
uses a [`UniformPartitioner`](@ref) to split the data.

The result of the comparison stores the errors for each
variable of the problem.

## Examples

Compare `solver₁` and `solver₂` on a `problem` with variable
`:var` using 10 folds. Plot error distribution:

```julia
julia> result = compare([solver₁, solver₂], problem, CrossValidation(10))
julia> plot(result, bins=50)
```
"""
struct CrossValidation{P} <: AbstractErrorEstimator
  partitioner::P
end

CrossValidation(k::Int, shuffle::Bool) =
  CrossValidation(UniformPartitioner(k, shuffle))
CrossValidation(k::Int) = CrossValidation(k, true)
CrossValidation() = CrossValidation(10)

function Base.error(solver::AbstractLearningSolver,
                    problem::LearningProblem,
                    eestimator::CrossValidation)
  # retrieve problem info
  sdata = sourcedata(problem)
  ovars = outputvars(task(problem))
  partitioner = eestimator.partitioner

  # folds for cross-validation
  folds  = subsets(partition(sdata, partitioner))
  nfolds = length(folds)

  solutions = pmap(1:nfolds) do k
    # source and target indices
    sinds = [ind for i in vcat(1:k-1, k+1:nfolds) for ind in folds[i]]
    tinds = folds[k]

    # source and target data
    train = view(sdata, sinds)
    hold  = view(sdata, tinds)

    # setup and solve sub-problem
    subproblem = LearningProblem(train, hold, task(problem))
    solve(subproblem, solver)
  end

  result = pmap(ovars) do var
    𝔏 = defaultloss(sdata[1,var])
    losses = map(1:nfolds) do k
      hold = view(sdata, folds[k])
      ŷ = solutions[k][var]
      y = hold[var]
      𝔏(ŷ, y)
    end
    var => mean(losses)
  end

  Dict(result)
end

function Base.error(solver::AbstractEstimationSolver,
                    problem::EstimationProblem,
                    eestimator::CrossValidation)
  # retrieve problem info
  pdata = data(problem)
  pdomain = domain(problem)
  partitioner = eestimator.partitioner

  # folds for cross-validation
  folds  = subsets(partition(pdata, partitioner))
  nfolds = length(folds)

  result = []
  for (var, V) in variables(problem)
    # mappings from data to domain locations
    varmap = Dict(datloc => domloc for (domloc, datloc) in datamap(problem, var))

    # k-fold validation loop
    losses = pmap(1:nfolds) do k
      # training and holdout set
      train = [ind for i in vcat(1:k-1, k+1:nfolds) for ind in folds[i]]
      hold  = folds[k]

      # discard indices that were filtered out by mapping strategy (e.g. missing values)
      train = filter(in(keys(varmap)), train)
      hold  = filter(in(keys(varmap)), hold)

      if isempty(train)
        missing
      else
        # copy data to their locations in domain
        mapper = CopyMapper([varmap[ind] for ind in train])

        # setup and solve sub-problem
        subproblem = EstimationProblem(view(pdata, train), pdomain,
                                       var, mapper=mapper)
        solution = solve(subproblem, solver)

        # get true holdout values
        y = pdata[hold,var]

        # get solver estimate at holdout locations
        μ, _ = solution[var]
        ŷ = [μ[varmap[loc]] for loc in hold]

        # loss for fold
        mean((ŷ .- y).^2)
      end
    end

    push!(result, var => mean(skipmissing(losses)))
  end

  Dict(result)
end


#####
##### Fields
#####

##### Auxiliary fields

# Center only
cent_aux_vars_en_2m(FT) = (;
    dissipation = FT(0),
    shear = FT(0),
    entr_gain = FT(0),
    detr_loss = FT(0),
    press = FT(0),
    buoy = FT(0),
    interdomain = FT(0),
    rain_src = FT(0),
)
cent_aux_vars_up(FT) = (;
    q_liq = FT(0),
    q_ice = FT(0),
    T = FT(0),
    RH = FT(0),
    s = FT(0),
    buoy = FT(0),
    area = FT(0),
    q_tot = FT(0),
    θ_liq_ice = FT(0),
    θ_liq_ice_tendency_precip_formation = FT(0),
    qt_tendency_precip_formation = FT(0),
    entr_sc = FT(0),
    detr_sc = FT(0),
    frac_turb_entr = FT(0),
    entr_turb_dyn = FT(0),
    detr_turb_dyn = FT(0),
    asp_ratio = FT(0),
    horiz_K_eddy = FT(0),
)
cent_aux_vars_edmf(FT, n_up) = (;
    turbconv = (;
        ϕ_temporary = FT(0),
        ψ_temporary = FT(0),
        bulk = (;
            area = FT(0),
            θ_liq_ice = FT(0),
            RH = FT(0),
            buoy = FT(0),
            q_tot = FT(0),
            q_liq = FT(0),
            q_ice = FT(0),
            T = FT(0),
            cloud_fraction = FT(0),
            θ_liq_ice_tendency_precip_formation = FT(0),
            qt_tendency_precip_formation = FT(0),
            Ri = FT(0),
        ),
        up = ntuple(i -> cent_aux_vars_up(FT), n_up),
        en = (;
            w = FT(0),
            area = FT(0),
            q_tot = FT(0),
            q_liq = FT(0),
            q_ice = FT(0),
            θ_liq_ice = FT(0),
            θ_virt = FT(0),
            θ_dry = FT(0),
            RH = FT(0),
            s = FT(0),
            T = FT(0),
            buoy = FT(0),
            cloud_fraction = FT(0),
            tke = FT(0),
            Hvar = FT(0),
            QTvar = FT(0),
            HQTcov = FT(0),
            qt_tendency_precip_formation = FT(0),
            θ_liq_ice_tendency_precip_formation = FT(0),
            unsat = (; q_tot = FT(0), θ_dry = FT(0), θ_virt = FT(0)),
            sat = (; T = FT(0), q_vap = FT(0), q_tot = FT(0), θ_dry = FT(0), θ_liq_ice = FT(0)),
            Hvar_rain_dt = FT(0),
            QTvar_rain_dt = FT(0),
            HQTcov_rain_dt = FT(0),
        ),
        θ_liq_ice_tendency_precip_sinks = FT(0),
        qt_tendency_precip_sinks = FT(0),
        qr_tendency_evap = FT(0),
        qs_tendency_melt = FT(0),
        qs_tendency_dep_sub = FT(0),
        qr_tendency_advection = FT(0),
        qs_tendency_advection = FT(0),
        en_2m = (;
            tke = cent_aux_vars_en_2m(FT),
            Hvar = cent_aux_vars_en_2m(FT),
            QTvar = cent_aux_vars_en_2m(FT),
            HQTcov = cent_aux_vars_en_2m(FT),
        ),
        term_vel_rain = FT(0),
        term_vel_snow = FT(0),
        KM = FT(0),
        KH = FT(0),
        mixing_length = FT(0),
        θ_virt = FT(0),
        massflux_tendency_h = FT(0),
        massflux_tendency_qt = FT(0),
        diffusive_tendency_h = FT(0),
        diffusive_tendency_qt = FT(0),
        prandtl_nvec = FT(0),
        # Added by Ignacio : Length scheme in use (mls), and smooth min effect (ml_ratio)
        # Variable Prandtl number initialized as neutral value.
        mls = FT(0),
        b_exch = FT(0),
        ml_ratio = FT(0),
        w_up_c = FT(0),
        w_en_c = FT(0),
        Shear² = FT(0),
        ∂θv∂z = FT(0),
        ∂qt∂z = FT(0),
        ∂θl∂z = FT(0),
        ∂θv∂z_unsat = FT(0),
        ∂qt∂z_sat = FT(0),
        ∂θl∂z_sat = FT(0),
        l_entdet = FT(0),
        ϕ_gm = FT(0), # temporary for grid-mean variables
        ϕ_gm_cov = FT(0), # temporary for grid-mean covariance variables
        ϕ_en_cov = FT(0), # temporary for environmental covariance variables
        ϕ_up_cubed = FT(0), # temporary for cubed updraft variables in grid mean 3rd moment functions
    ),
)

# Face only
face_aux_vars_up(FT) = (;
    w = FT(0),
    nh_pressure = FT(0),
    nh_pressure_b = FT(0),
    nh_pressure_adv = FT(0),
    nh_pressure_drag = FT(0),
    massflux = FT(0),
)

face_aux_vars_edmf(FT, n_up) = (;
    turbconv = (;
        bulk = (; w = FT(0)),
        ρ_ae_KM = FT(0),
        ρ_ae_KH = FT(0),
        ρ_ae_K = FT(0),
        en = (; w = FT(0)),
        up = ntuple(i -> face_aux_vars_up(FT), n_up),
        massflux_h = FT(0),
        massflux_qt = FT(0),
        ϕ_temporary = FT(0),
        diffusive_flux_h = FT(0),
        diffusive_flux_qt = FT(0),
        diffusive_flux_u = FT(0),
        diffusive_flux_v = FT(0),
    ),
)

##### Diagnostic fields

# Center only
cent_diagnostic_vars_edmf(FT, n_up) = (;
    turbconv = (;
        asp_ratio = FT(0),
        entr_sc = FT(0),
        detr_sc = FT(0),
        massflux = FT(0),
        frac_turb_entr = FT(0),
        horiz_K_eddy = FT(0),
    ),
)

# Face only
face_diagnostic_vars_edmf(FT, n_up) =
    (; turbconv = (; nh_pressure = FT(0), nh_pressure_adv = FT(0), nh_pressure_drag = FT(0), nh_pressure_b = FT(0)))

##### Prognostic fields

# Center only
cent_prognostic_vars_up(FT) = (; ρarea = FT(0), ρaθ_liq_ice = FT(0), ρaq_tot = FT(0))
cent_prognostic_vars_en(FT) = (; ρatke = FT(0), ρaHvar = FT(0), ρaQTvar = FT(0), ρaHQTcov = FT(0))
cent_prognostic_vars_edmf(FT, n_up) = (;
    turbconv = (;
        en = cent_prognostic_vars_en(FT),
        up = ntuple(i -> cent_prognostic_vars_up(FT), n_up),
        pr = (; q_rai = FT(0), q_sno = FT(0)),
    ),
)
# cent_prognostic_vars_edmf(FT, n_up) = (;) # could also use this for empty model

# Face only
face_prognostic_vars_up(FT) = (; ρaw = FT(0))
face_prognostic_vars_edmf(FT, n_up) = (; turbconv = (; up = ntuple(i -> face_prognostic_vars_up(FT), n_up)))


# this_dir=string(dictGlobalSettings["Julia Code Folder"],"\\src");cd(this_dir);push!(LOAD_PATH,this_dir)
this_dir = "C:\\Users\\bernhard.konig\\Documents\\ASync\\irobot\\TariffWatch New Repo\\algorithms\\Julia\\Code\\dev\\src";cd(this_dir);push!(LOAD_PATH, this_dir)
# include(string(this_dir,"\\DTM.jl"))
using DTM
using DataFrames

#=
reffile="A:\\JuliaTestData\\bagging_small.csv"
reffile="C:\\Users\\bernhard.konig\\Documents\\ASync\\irobot\\data_tmp\\bagging_small.csv"
eltypev=DTM.define_eltypevector(DataFrames.readtable(reffile,nrows=100),DTM.global_const_shift_cols,35)
reference_db=readtable(reffile,eltypes=eltypev,nrows=-1,normalizenames=true) =#

tmpf = "A:\\CH\\03Testing October 2017\\motordata_10k.csv"
tmpf = "C:\\Users\\bernhard.konig\\Documents\\ASync\\irobot\\data_tmp\\motordata_200k.csv"
df_tmp = readtable(tmpf);

expl = ["AFF_MM" "FLG_BERSANI" "f_anz_cli_Rca" "f_citt" "anno" "f_cod_fraz" "f_flg_rinnovo_new" "f_num_gar" "F_rin_cons" "f_num_pol_att" "anno_pat" "fonte" "FLG_CNT_INT" "f_can_pol" "f_profes" "f_flg_cla_tpgu" "f_sex" "f_eta" "f_anz_cli_Zuco" "f_etavei" "f_alim" "F_stat_civ_gran" "f_pagamento_new" "f_att_na_old" "f_att_pulito" "f_cod_marc" "f_ant_rit" "f_reg" "COND_ABI" "UT_AUTO" "KM_AN" "SG_PR" "numpol_att_motor" "Ass" "Rit" "inc_fur" "tut" "Nat" "Soc" "ktot" "Crist" "Inf" "bluk" "eta_conseg_patente" "anni_di_patente"][:]

# prep df
@warn("add option forced_categorical_variables to specify columns which are interpreted as categorical variables even if they are numerical (e.g. [1 2 4 9 1][:] ...)")
@time (df_prepped, this_sett) = prepare_dataframe_for_dtm(df_tmp, numcol="n_claims_artificial", denomcol="exposure", independent_vars=expl);

# remove all rows which have some NA entries
# an alternative is of course to replace the NAs with some specific value
origrows = size(df_prepped, 1)
for i = 1:size(df_prepped, 2)
    df_prepped = df_prepped[.!isna.(df_prepped[i]),:]
end
@show droppedrows = origrows - size(df_prepped, 1)
@show size(df_prepped)

@time updateSettingsMod!(this_sett, model_type="boosted_tree", minWeight=-.15, randomw=.1, learningRate=.1, subsampling_features_prop=1, iterations=50, subsampling_prop=1.00)

outfileStringOnly = "boosti"
datafolder = "C:\\temp\\"
outfilename = string("c:\\temp\\", outfileStringOnly)
dataFilename = "C:\\temp\\where_to_save_jld_file"
key, trn, numeratortrn, denominatortrn, weighttrn, trn_numfeatures, trn_charfeatures_PDA, keyval, val, val_numfeatures, val_charfeatures_PDA, numeratorval, denominatorval, weightval, mappings, sett, num_levels, char_levels, all_levels, all_levels_as_string_vector, names_and_levels, candMatWOMaxValues = prep_data_from_df_modernized(df_prepped, this_sett, datafolder, outfilename, dataFilename, outfileStringOnly)

result_of_runmodel, resulting_model = run_model_actual(key, trn, numeratortrn, denominatortrn, weighttrn, trn_numfeatures, trn_charfeatures_PDA, keyval, val, val_numfeatures, val_charfeatures_PDA, numeratorval, denominatorval, weightval, mappings, datafolder, outfilename, outfileStringOnly, DTM.global_const_shift_cols, sett, num_levels, char_levels, all_levels, all_levels_as_string_vector, names_and_levels, candMatWOMaxValues, dataFilename)

# a


#=
julia --sysimage sys_plots.so
=#

# Trying out GR, StatsPlots, Plots
# Need:
# - Heatmaps
# - KDE
# - Histograms
# - Grouped line plots
# - Grouped scatter plots

import Pkg; Pkg.activate(".")
@time begin
  using Plots; gr()
end

@time begin
  x, y = randn(100), randn(100)
  p1 = plot(x, y, label="p1");
  p2 = plot(x, y, seriestype=:scatter, label="p2");
  p3 = plot(x, seriestype=:hist, label="p3");
  p4 = plot(y, seriestype=:hist, label="p4", color="red");
  p = plot(p1, p2, p3, p4, layout=(2, 2));
  savefig(p, "test.pdf")
end

@time begin
  a = plot(randn(6, 6), seriestype=:heatmap);
  b = plot(randn(6, 6), seriestype=:heatmap);
  pp = plot(a, b, layout=(2, 1));
  pp
end


#-------------------------------------------------------------------
#* EMSO Model Library (EML) Copyright (C) 2004 - 2007 ALSOC.
#*
#* This LIBRARY is free software; you can distribute it and/or modify
#* it under the therms of the ALSOC FREE LICENSE as available at
#* http://www.enq.ufrgs.br/alsoc.
#*
#* EMSO Copyright (C) 2004 - 2007 ALSOC, original code
#* from http://www.rps.eng.br Copyright (C) 2002-2004.
#* All rights reserved.
#*
#* EMSO is distributed under the therms of the ALSOC LICENSE as
#* available at http://www.enq.ufrgs.br/alsoc.
#*
#*--------------------------------------------------------------------
#* Models to simulate a power plant.
#*--------------------------------------------------------------------
#* Author: Argimiro R. Secchi
#* $Id: power_plant.mso 195 2007-03-07 20:30:12Z arge $
#*-------------------------------------------------------------------
# Declaracao de tipos
export Temperatura
typealias DanaTemperatura DanaRealParametric
type _Temperatura
	function _Temperatura(_::Dict{Symbol,Any})
		fields::Dict{Symbol,Any}=Dict{Symbol,Any}()
		fields[:Default]=600
		fields[:Lower]=273.16
		fields[:Upper]=900
		fields[:Unit]="K"
		drive!(fields,_)
		new(fields)
	end
	value::Dict{Symbol,Any}
end
typealias Temperatura DanaRealParametric{_Temperatura}


# diffusion variance

abstract type AbstractDiffusion end

mutable struct UnitDiffusion <: AbstractDiffusion end


# Sorting Direct Method, implementation following McCollum et al,
# "The sorting direct method for stochastic simulation of biochemical systems with varying reaction execution behavior"
# Comp. Bio. and Chem., 30, pg. 39-49 (2006).

mutable struct SortingDirectJumpAggregation{T,S,F1,F2,RNG,DEPGR} <: AbstractSSAJumpAggregator
    next_jump::Int
    next_jump_time::T
    end_time::T
    cur_rates::Vector{T}
    sum_rate::T
    ma_jumps::S
    rates::F1
    affects!::F2
    save_positions::Tuple{Bool,Bool}
    rng::RNG
    dep_gr::DEPGR
    jump_search_order::Vector{Int}
    jump_search_idx::Int
  end

function SortingDirectJumpAggregation(nj::Int, njt::T, et::T, crs::Vector{T}, sr::T,
                                      maj::S, rs::F1, affs!::F2, sps::Tuple{Bool,Bool},
                                      rng::RNG; num_specs, dep_graph=nothing, kwargs...) where {T,S,F1,F2,RNG}

    # a dependency graph is needed and must be provided if there are constant rate jumps
    if dep_graph === nothing
        if (get_num_majumps(maj) == 0) || !isempty(rs)
            error("To use ConstantRateJumps with the SortingDirect algorithm a dependency graph must be supplied.")
        else
            dg = make_dependency_graph(num_specs, maj)
        end
    else
        dg = dep_graph
    end

    # make sure each jump depends on itself
    add_self_dependencies!(dg)

    # map jump idx to idx in cur_rates
    jtoidx = collect(1:length(crs))
    SortingDirectJumpAggregation{T,S,F1,F2,RNG,typeof(dg)}(nj, njt, et, crs, sr,
                                maj, rs, affs!, sps, rng, dg, jtoidx, zero(Int))
end

########### The following routines should be templates for all SSAs ###########

# condition for jump to occur
@inline function (p::SortingDirectJumpAggregation)(u, t, integrator)
    p.next_jump_time == t
end

# executing jump at the next jump time
function (p::SortingDirectJumpAggregation)(integrator)
    execute_jumps!(p, integrator, integrator.u, integrator.p, integrator.t)
    generate_jumps!(p, integrator, integrator.u, integrator.p, integrator.t)
    register_next_jump_time!(integrator, p, integrator.t)
    nothing
end

# setting up a new simulation
function (p::SortingDirectJumpAggregation)(dj, u, t, integrator) # initialize
    initialize!(p, integrator, u, integrator.p, t)
    register_next_jump_time!(integrator, p, t)
    nothing
end

############################# Required Functions ##############################

# creating the JumpAggregation structure (function wrapper-based constant jumps)
function aggregate(aggregator::SortingDirect, u, p, t, end_time, constant_jumps,
                   ma_jumps, save_positions, rng; kwargs...)

    # handle constant jumps using function wrappers
    rates, affects! = get_jump_info_fwrappers(u, p, t, constant_jumps)

    build_jump_aggregation(SortingDirectJumpAggregation, u, p, t, end_time, ma_jumps,
                           rates, affects!, save_positions, rng; num_specs=length(u), kwargs...)
end

# set up a new simulation and calculate the first jump / jump time
function initialize!(p::SortingDirectJumpAggregation, integrator, u, params, t)
    fill_rates_and_sum!(p, u, params, t)
    generate_jumps!(p, integrator, u, params, t)
    nothing
end

# execute one jump, changing the system state
function execute_jumps!(p::SortingDirectJumpAggregation, integrator, u, params, t)
    # execute jump
    u = update_state!(p, integrator, u)

    # update search order
    jso   = p.jump_search_order
    jsidx = p.jump_search_idx
    if jsidx != 1
        @inbounds tmp          = jso[jsidx]
        @inbounds jso[jsidx]   = jso[jsidx-1]
        @inbounds jso[jsidx-1] = tmp
    end

    # update current jump rates
    update_dependent_rates!(p, u, params, t)
    nothing
end

# calculate the next jump / jump time
function generate_jumps!(p::SortingDirectJumpAggregation, integrator, u, params, t)
    @fastmath p.next_jump_time = t + calc_next_jump!(p, u, params, t)
    nothing
end

######################## SSA specific helper routines #########################

# searches down the rate list for the next reaction
@fastmath function calc_next_jump!(p, u, params, t)

    # next jump type
    cur_rates = p.cur_rates
    numjumps  = length(cur_rates)
    jso       = p.jump_search_order
    rn        = p.sum_rate * rand(p.rng)
    @inbounds for idx = 1:numjumps
        rn -= cur_rates[jso[idx]]
        if rn < zero(rn)
            p.jump_search_idx = idx
            break
        end
    end
    @inbounds p.next_jump = jso[p.jump_search_idx]

    # return time to next jump
    randexp(p.rng) / p.sum_rate
end


using Knet
libknet8handle = Libdl.dlopen(Knet.libknet8)

SIZE1 = 1000
SIZE2 = 100
ITER = 100000
mat32 = KnetArray(rand(Float32,SIZE1,SIZE2))
mat32b = KnetArray(rand(Float32,SIZE1,SIZE2))
col32 = KnetArray(rand(Float32,SIZE1))
row32 = KnetArray(rand(Float32,1,SIZE2))
out32 = similar(mat32)
mat64 = KnetArray(rand(Float64,SIZE1,SIZE2))
mat64b = KnetArray(rand(Float64,SIZE1,SIZE2))
col64 = KnetArray(rand(Float64,SIZE1))
row64 = KnetArray(rand(Float64,1,SIZE2))
out64 = similar(mat64)
f32 = f64 = nothing

function cuda12test(fname, jname=fname, o...)
    println(fname)
    fcpu = eval(parse(jname))
    global f32 = Libdl.dlsym(libknet8handle, fname*"_32_12")
    @time cuda12rep(f32,mat32,mat32b,out32)
    isapprox(Array(out32),fcpu(Array(mat32),Array(mat32b))) || warn("$fname 32 mat mat")
    @time cuda12rep(f32,mat32,col32,out32)
    isapprox(Array(out32),fcpu(Array(mat32),Array(col32))) || warn("$fname 32 mat col")
    @time cuda12rep(f32,mat32,row32,out32)
    isapprox(Array(out32),fcpu(Array(mat32),Array(row32))) || warn("$fname 32 mat row")
    @time cuda12rep(f32,row32,col32,out32)
    isapprox(Array(out32),fcpu(Array(row32),Array(col32))) || warn("$fname 32 row col")
    global f64 = Libdl.dlsym(libknet8handle, fname*"_64_12")
    @time cuda12rep(f64,mat64,mat64b,out64)
    isapprox(Array(out64),fcpu(Array(mat64),Array(mat64b))) || warn("$fname 64 mat mat")
    @time cuda12rep(f64,mat64,col64,out64)
    isapprox(Array(out64),fcpu(Array(mat64),Array(col64))) || warn("$fname 64 mat col")
    @time cuda12rep(f64,mat64,row64,out64)
    isapprox(Array(out64),fcpu(Array(mat64),Array(row64))) || warn("$fname 64 mat row")
    @time cuda12rep(f64,row64,col64,out64)
    isapprox(Array(out64),fcpu(Array(row64),Array(col64))) || warn("$fname 64 row col")
end

function cuda12rep{T}(f,x::KnetArray{T},y::KnetArray{T},z::KnetArray{T})
    n = Cint(length(z))
    if size(x,1)==1; sx=size(z,1); else; sx=1; end
    if size(y,1)==1; sy=size(z,1); else; sy=1; end
    nx = length(x); ny = length(y)
    for i=1:ITER
        ccall(f,Void,(Cint,Ptr{T},Cint,Cint,Ptr{T},Cint,Cint,Ptr{T}),n,x,sx,nx,y,sy,ny,z)
    end
    Knet.@cuda(cudart,cudaDeviceSynchronize,())
    Knet.@cuda(cudart,cudaGetLastError,())
end

for f in Knet.cuda12
    isa(f,Tuple) || (f=(f,))
    cuda12test(f...)
end


## LEARNING NETWORK MACHINES

# ***
surrogate(::Type{<:Deterministic})  = Deterministic()
surrogate(::Type{<:Probabilistic})  = Probabilistic()
surrogate(::Type{<:Unsupervised}) = Unsupervised()
surrogate(::Type{<:Static}) = Static()

"""
    model_supertype(signature)

Return, if this can be deduced, which of `Deterministic`,
`Probabilistic` and `Unsupervised` is the appropriate supertype for a
composite model obtained by exporting a learning network with the
specified `signature`.

A learning network *signature* is a named tuple, such as
`(predict=yhat, transfrom=W)`, specifying what nodes of the network
are called to produce output of each operation represented by the
keys, in an exported version of the network.

If a supertype cannot be deduced, `nothing` is returned.

If the network with given `signature` is not exportable, this method
will not error but it will not a give meaningful return value either.

"""
function model_supertype(signature)

    operations = keys(signature)

    length(intersect(operations, (:predict_mean, :predict_median))) == 1 &&
        return Deterministic

    if :predict in operations
        node = signature.predict
        if node isa Source
            return Deterministic
        end
        if node.machine !== nothing
            model = node.machine.model
            model isa Deterministic && return Deterministic
            model isa Probabilistic && return Probabilistic
        end
    end

    return nothing

end

function machine(model::Surrogate, _sources::Source...; pair_itr...)

    # named tuple, such as `(predict=yhat, transform=W)`:
    signature = (; pair_itr...)
    for op in keys(signature)
        op in OPERATIONS || throw(ArgumentError(
            "`$op` is not an admissible operation. "))
    end
    if isempty(signature)
        model isa Supervised &&
            throw(ArgumentError(
                "You must specify at least `predict=<some node>`. "))
        model isa Unsupervised &&
            throw(ArgumentError(
                "You must specify at least `transform=<some node>`. "))
    end

    if model isa Supervised
        length(_sources) in [2, 3] ||
            error("Incorrect number of source nodes specified.\n"*
                  "Use  `machine(model, X, y; ...)` or "*
                  "`machine(model, X, y, w; ...)` when "*
                  "`model isa Supervised`. ")
    elseif model isa Unsupervised
        length(_sources) == 1 ||
            error("Incorrect number of source nodes specified.\n"*
                  "Use `machine(model, X; ...)` when "*
                  "`model isa Unsupervised. ` (even if `Static`). ")
    else
        throw(DomainError)
    end

    mach = Machine(model, _sources...)

    mach.fitresult = signature

    return mach

end

function machine(sources::Source...; pair_itr...)

    signature = (; pair_itr...)

    isempty(signature) &&
        throw(ArgumentError(
            "You must specify at least `predict=<some node>` or "*
            "or `transform=<some node>`. "))

    T = model_supertype(signature)
    if T == nothing
        @warn "Unable to infer surrogate model type. \n"*
        "Using Deterministic(). To override use:\n "*
        "`machine(Probabilistic(), ...)` or `machine(Interval(), ...)`"
        model = Deterministic()
    else
        model = surrogate(T)
    end

    return machine(model, sources...; pair_itr...)

end

"""
    anonymize!(sources)

Returns a named tuple `(sources=..., data=....)` whose values are the
provided source nodes and their contents respectively, and clears the
contents of those source nodes.

"""
function anonymize!(sources)
    data = Tuple(s.data for s in sources)
    [MLJBase.rebind!(s, nothing) for s in sources]
    return (sources=sources, data=data)
end

"""
    fit!(mach::Machine{<:Surrogate};
         rows=nothing,
         acceleration=CPU1(),
         verbosity=1,
         force=false))

Train the complete learning network wrapped by the machine
`mach`.

More precisely, if `s` is the learning network signature used to
construct `mach`, then call `fit!(N)`, where `N = glb(values(s)...)`
is a greatest lower bound on the nodes appearing in the signature. For
example, if `s = (predict=yhat, transform=W)`, then call
`fit!(glb(yhat, W))`. Here `glb` is `tuple` overloaded for nodes.

See also [`machine`](@ref)

"""
function fit!(mach::Machine{<:Surrogate}; kwargs...)

    signature = mach.fitresult
    glb_node = glb(values(signature)...) # greatest lower bound node
    fit!(glb_node; kwargs...)

    # mach.cache = anonymize!(mach.args)
    mach.state += 1
    mach.report = report(glb_node)
    return mach

end

# make learning network machines callable for use in manual export of
# learning networks:
function (mach::Machine{<:Surrogate})()
    # anonymize sources:
    mach.cache = anonymize!(mach.args)
    return mach.fitresult, mach.cache, mach.report
end

MLJModelInterface.fitted_params(mach::Machine{<:Surrogate}) =
    fitted_params(glb(values(mach.fitresult)...))

## DUPLICATING AND REPLACING PARTS OF A LEARNING NETWORK MACHINE

"""
    replace(mach, a1=>b1, a2=>b2, ...; empty_unspecified_sources=false)

Create a deep copy of a learning network machine `mach` but replacing
any specified sources and models `a1, a2, ...` of the original
underlying network with `b1, b2, ...`.

If `empty_unspecified_sources=true` then any source nodes not
specified are replaced with empty source nodes.

"""
function Base.replace(mach::Machine{<:Surrogate},
                      pairs::Pair...; empty_unspecified_sources=false)

    signature = mach.fitresult
    interface_nodes = values(signature)

    W = glb(interface_nodes...)

    # Note: We construct nodes of the new network as values of a
    # dictionary keyed on the nodes of the old network. Additionally,
    # there are dictionaries of models keyed on old models and
    # machines keyed on old machines. The node and machine
    # dictionaries must be built simultaneously.

    # build model dict:
    model_pairs = filter(collect(pairs)) do pair
        first(pair) isa Model
    end
    models_ = models(W)
    models_to_copy = setdiff(models_, first.(model_pairs))
    model_copy_pairs = [model=>deepcopy(model) for model in models_to_copy]
    newmodel_given_old = IdDict(vcat(model_pairs, model_copy_pairs))

    # build complete source replacement pairs:
    sources_ = mach.args # sources(W)
    specified_source_pairs = filter(collect(pairs)) do pair
        first(pair) isa Source
    end
    unspecified_sources = setdiff(sources_, first.(specified_source_pairs))
    unspecified_sources_wrapping_something =
        filter(s -> !isempty(s), unspecified_sources)
    if !isempty(unspecified_sources_wrapping_something) &&
        !empty_unspecified_sources
        @warn "No replacement specified for one or more non-empty source "*
        "nodes. Contents will be duplicated. "
    end
    if empty_unspecified_sources
        unspecified_source_pairs = [s => source() for
                                    s in unspecified_sources]
    else
        unspecified_source_pairs = [s => deepcopy(s) for
                                    s in unspecified_sources]
    end

    all_source_pairs = vcat(specified_source_pairs, unspecified_source_pairs)

    # drop source nodes from all nodes of network terminating at W:
    nodes_ = filter(nodes(W)) do N
        !(N isa Source)
    end
    isempty(nodes_) && error("All nodes in network are source nodes. ")
    # instantiate node and machine dictionaries:
    newnode_given_old =
        IdDict{AbstractNode,AbstractNode}(all_source_pairs)
    newsources = [newnode_given_old[s] for s in sources_]
    newinterface_node_given_old =
        IdDict{AbstractNode,AbstractNode}()
    newmach_given_old = IdDict{Machine,Machine}()

    # build the new network:
    for N in nodes_
       args = [newnode_given_old[arg] for arg in N.args]
         if N.machine === nothing
             newnode_given_old[N] = node(N.operation, args...)
         else
             if N.machine in keys(newmach_given_old)
                 m = newmach_given_old[N.machine]
             else
                 train_args = [newnode_given_old[arg] for arg in N.machine.args]
                 m = Machine(newmodel_given_old[N.machine.model],
                                train_args...)
                 newmach_given_old[N.machine] = m
             end
             newnode_given_old[N] = N.operation(m, args...)
         end
        if N in interface_nodes
            newinterface_node_given_old[N] = newnode_given_old[N]
        end
    end

    newinterface_nodes = Tuple(newinterface_node_given_old[N] for N in
                          interface_nodes)
    newsignature = NamedTuple{keys(signature)}(newinterface_nodes)

    return machine(mach.model, newsources...; newsignature...)

end


module Forcings

export Forcing, ContinuousForcing, DiscreteForcing, Relaxation, GaussianMask, LinearTarget, AdvectiveForcing

using Oceananigans.Fields

include("continuous_forcing.jl")
include("discrete_forcing.jl")
include("relaxation.jl")
include("advective_forcing.jl")
include("forcing.jl")
include("model_forcing.jl")
include("multiple_forcings.jl")

end # module