module TestLearningCompositesInspection

using Test
using MLJBase
using ..Models

@load KNNRegressor

@constant X = source()
@constant y = source()

hot = OneHotEncoder()
hotM = machine(hot, X)
@constant W = transform(hotM, X)
knn = KNNRegressor()
knnM = machine(knn, W, y)
@constant yhat = predict(knnM, W)
@constant K =  2*X
@constant all = glb(yhat, K)

@test MLJBase.tree(yhat) == (operation = predict,
                             model = knn,
                             arg1 = (operation = transform,
                                     model = hot,
                                     arg1 = (source = X, ),
                                     train_arg1 = (source = X, )),
                             train_arg1 = (operation = transform,
                                           model = hot,
                                           arg1 = (source = X, ),
                                           train_arg1 = (source = X, )),
                             train_arg2 = (source = y,))

@test Set(models(yhat)) == Set([hot, knn])
@test Set(sources(yhat)) == Set([X, y])
@test Set(origins(yhat)) == Set([X,])
@test Set(machines(yhat)) == Set([knnM, hotM])
@test Set(MLJBase.args(yhat)) == Set([W, ])
@test Set(MLJBase.train_args(yhat)) == Set([W, y])
@test Set(MLJBase.children(X, all)) == Set([W, K])

@constant Q = 2X
@constant R = 3X
@constant S = glb(X, Q, R)
@test Set(MLJBase.children(X, S)) == Set([Q, R, S])
@test MLJBase.lower_bound([Int, Float64]) == Union{}
@test MLJBase.lower_bound([Int, Integer]) == Int
@test MLJBase.lower_bound([Int, Integer]) == Int
@test MLJBase.lower_bound([]) == Any

@test input_scitype(2X) == Unknown
@test input_scitype(yhat) == input_scitype(KNNRegressor())
W2 = transform(machine(UnivariateStandardizer(), X), X)
# @test input_scitype(X, glb(W, W2)) == Union{}
# @test input_scitype(X, glb(Q, W)) == Unknown

y1 = predict(machine(@load(DecisionTreeRegressor), X, y), X)
@test input_scitype(y1) == Table(Continuous, OrderedFactor, Count)
y2 = predict(machine(@load(KNNRegressor), X, y), X)
@test input_scitype(y2) == Table(Continuous)
# @test input_scitype(X, glb(y1, y2)) == Table(Continuous)
# @test input_scitype(X, glb(y1, y2, Q)) == Unknown

end

true


# This file creates the Manpower module, holding all the types, definitions, and
#   methods for the manpower planning project.

module ManpowerPlanning
    using SimJulia
    using ResumableFunctions
    using Plots
    using LightGraphs
    using MetaGraphs
    using GraphIO
    using EzXML
    using GraphPlot
    using Gadfly
    using Distributions
    using Polynomials
    using FileIO
    using SQLite
    using DataFrames
    using StatsBase
    using XLSX
    # using ExcelWrapper

    types = [
#        "historyEntry",
#        "history",
        "attrition",
        "personnelAttribute",
        "state",
        "compoundState",
        "condition",
        "transition",
#        "personnel",
#        "personnelDatabase",
#        "prerequisite",
#        "prerequisiteGroup",
        "recruitment",
        "retirement",
        "simulationReport",
        "manpowerSimulation"
    ]

    rootPath = Base.source_path()
    rootPath = rootPath isa Void ? "" : dirname( rootPath )
    funcPath = joinpath( rootPath, "Functions" )
    typePath = joinpath( rootPath, "Types" )

    # The types
    map( mpType -> include( joinpath( typePath, mpType * ".jl" ) ), types )

    # Some union type aliases
    include( joinpath( typePath, "typeAliases.jl" ) )

    # The functions
    map( mpType -> include( joinpath( funcPath, mpType * ".jl" ) ), types )
end  # module ManpowerPlanning


using LoopVectorization, Test, ArrayInterface
using LoopVectorization: check_inputs!

# macros for generate loops whose body is not a block
macro gen_loop_issue395(ex)
  sym, ind = ex.args
  loop_body = :(ret[$ind] = $sym[$ind])
  loop = Expr(:for, :($ind = axes($sym, 1)), loop_body)
  return esc(:(@turbo $loop))
end
macro gen_single_loop(B, A)
  loop_body = :($B[i] = $A[i])
  loop = Expr(:for, :(i = indices(($B, $A), 1)), loop_body)
  return esc(:(@turbo $loop))
end
macro gen_nest_loop(C, A, B)
  loop_body = :($C[i, j] = $A[i] * $B[j])
  loop_head = Expr(:block, :(j = indices(($C, $B), (2, 1))), :(i = indices(($C, $A), 1)))
  loop = Expr(:for, loop_head, loop_body)
  return esc(:(@turbo $loop))
end
macro gen_A_mul_B(C, A, B)
  inner_body = :(Cji += $A[j, k] * $B[k, i])
  inner_loop = Expr(:for, :(k = indices(($A, $B), (2, 1))), inner_body)
  loop = :(
    for i in indices(($C, $B), 2), j in indices(($C, $A), 1)
      Cji = zero(eltype($C))
      $inner_loop
      $C[j, i] = Cji
    end
  )
  return esc(:(@turbo $loop))
end

@testset "check_block, #395" begin
  A = rand(4)
  B = rand(4)
  C = rand(4, 4)
  D = zeros(4)
  E = zeros(4, 4)
  F = zeros(4, 4)
  ret = zeros(4)
  @gen_single_loop D A
  @gen_loop_issue395 B[i]
  @gen_nest_loop E A B
  @gen_A_mul_B F C E
  @test D == A
  @test ret == B
  @test E == A * B'
  @test F == C * E
end

@testset "enumerate, #393" begin
  A = zeros(4)
  B = zeros(4)
  C = zeros(4, 4)
  D = zeros(4, 4)
  @turbo for (i, x) in enumerate(1:4)
    A[i] = x
  end
  @turbo for (i,) in enumerate(B)
    B[i] += 1
  end
  @turbo for (j, Aj) in enumerate(A), (i, Bi) in enumerate(B)
    C[i, j] = Aj * Bi
  end
  @turbo for (j, Bj) in enumerate(B)
    for (i, Ai) in enumerate(A)
      D[i, j] = Ai * Bj
    end
  end
  @test A == 1:4
  @test B == ones(4)
  @test A .* B' == C' == D
  @test_throws ArgumentError check_inputs!(:(
    for ix in enumerate(A)
      A[ix[1]] = ix[1] + ix[2]
    end
  ), Any[])
  @test_throws ArgumentError check_inputs!(:(
    for () in enumerate(A)
    end
  ), Any[])
end


#!/bin/bash
# -*- mode: julia -*-
#=
exec julia --project=@. --color=yes --startup-file=no -e "include(popfirst!(ARGS))" "${BASH_SOURCE[0]}" "$@"
=#

import CPLEX, Gurobi, GLPK
using JuMP

function test_duals(solver_sym, relaxed_mip)
	solver_lib = getfield(Main, solver_sym)
	m = direct_model(solver_lib.Optimizer())
	set_silent(m)
	if relaxed_mip
		@variable(m, x[1:10] >= 0, Int)
	else
		@variable(m, x[1:10] >= 0)
	end
	@objective(m, Min, sum(x))
	relaxed_mip && unset_integer.(x)
	optimize!(m)
	@show solver_sym
	@show relaxed_mip
	@show has_duals(m)
end

test_duals(:GLPK, false)
test_duals(:GLPK, true)
test_duals(:CPLEX, false)
test_duals(:CPLEX, true)
test_duals(:Gurobi, false)
test_duals(:Gurobi, true)


module DimensionalData

# Use the README as the module docs
@doc let
    path = joinpath(dirname(@__DIR__), "README.md")
    include_dependency(path)
    read(path, String)
end DimensionalData

# Standard lib
using Dates,
      LinearAlgebra,
      Random,
      Statistics,
      SparseArrays

using Base.Broadcast: Broadcasted, BroadcastStyle, DefaultArrayStyle, AbstractArrayStyle,
      Unknown

using Base: tail, OneTo, @propagate_inbounds

# Ecosystem
import Adapt,
       ArrayInterface,
       ConstructionBase,
       RecipesBase,
       Tables

using RecipesBase: @recipe

export Dimension, IndependentDim, DependentDim, XDim, YDim, ZDim, TimeDim,
       X, Y, Z, Ti, ParametricDimension, Dim, AnonDim, Coord

export Selector, At, Between, Contains, Near, Where

export Locus, Center, Start, End, AutoLocus

export Order, Ordered, Unordered, UnknownOrder, AutoOrder

export IndexOrder, ArrayOrder, Relation,
       ForwardIndex, ReverseIndex, UnorderedIndex,
       ForwardArray, ReverseArray, ForwardRelation, ReverseRelation

export Sampling, Points, Intervals

export Span, Regular, Irregular, Explicit, AutoSpan

export Mode, IndexMode, Auto, AutoMode, NoIndex

export Aligned, AbstractSampled, Sampled, AbstractCategorical, Categorical

export Unaligned, Transformed

export AbstractDimArray, DimArray, AbstractDimensionalArray, DimensionalArray

export AbstractDimTable, DimTable

export AbstractDimStack, DimStack

export DimIndices, DimKeys

export AbstractMetadata, Metadata, NoMetadata

export AbstractName, Name, NoName

export data, dims, refdims, mode, metadata, name, label, units,
       val, index, order, sampling, span, bounds, locus, <|

export dimnum, hasdim, otherdims, commondims, setdims, swapdims, sortdims,
       set, rebuild, reorder, modify, dimwise, dimwise!

export order, indexorder, arrayorder, relation

export @dim

const DD = DimensionalData

include("interface.jl")
include("mode.jl")
include("metadata.jl")
include("name.jl")
include("identify.jl")
include("dimension.jl")
include("array.jl")
include("stack.jl")
include("tables.jl")
include("selector.jl")
include("dimindices.jl")
include("indexing.jl")
include("primitives.jl")
include("broadcast.jl")
include("methods.jl")
include("matmul.jl")
include("coord.jl")
include("set.jl")
include("utils.jl")
include("plotrecipes.jl")
include("show.jl")

# For compat with old versions
const AbstractDimensionalArray = AbstractDimArray
const DimensionalArray = DimArray

const AbstractDimDataset = AbstractDimStack
const DimDataset = DimStack

function _precompile()
    precompile(DimArray, (Array{Int,1}, typeof(X())))
    precompile(DimArray, (Array{Int,2}, Tuple{typeof(X()), typeof(Y())}))
    precompile(DimArray, (Array{Float64,1}, typeof(X())))
    precompile(DimArray, (Array{Float64,2}, Tuple{typeof(X()), typeof(Y())}))
    precompile(DimArray, (Array{Float64,3}, Tuple{typeof(X()), typeof(Y()), typeof(Z())}))
end

_precompile()

end


"""
`module  JAC.AlphaVariation`
    ... a submodel of JAC that contains all methods for computing alpha-variation parameters for some levels.
"""
module AlphaVariation

    using Printf, JAC, ..Basics, ..Defaults, ..ManyElectron, ..Radial, ..TableStrings
    global JAC_counter = 0

    """
    `struct  AlphaVariation.Settings  <:  AbstractPropertySettings`  ... defines a type for the details and parameters of computing alpha-variation parameters.

        + calcK                    ::Bool             ... True if the enhancement factor need to be calculated, and false otherwise.
        + printBefore              ::Bool             ... True if a list of selected levels is printed before the actual computations start.
        + levelSelection           ::LevelSelection   ... Specifies the selected levels, if any.
    """
    struct Settings  <:  AbstractPropertySettings
        calcK                      ::Bool
        printBefore                ::Bool
        levelSelection             ::LevelSelection
    end

    """
    `AlphaVariation.Settings()`  ... constructor for an `empty` instance of AlphaVariation.Settings for the computation of alpha variation parameters.
    """
    function Settings()
        Settings(false, false, LevelSelection() )
    end

    # `Base.show(io::IO, settings::AlphaVariation.Settings)`  ... prepares a proper printout of the variable settings::AlphaVariation.Settings.
    function Base.show(io::IO, settings::AlphaVariation.Settings)
        println(io, "calcK:                    $(settings.calcK)  ")
        println(io, "printBefore:              $(settings.printBefore)  ")
        println(io, "levelSelection:           $(settings.levelSelection)  ")
    end

    """
    `struct  AlphaVariation.Outcome`
        ... defines a type to keep the outcome of a alpha-variation computation, such as the K enhancement factor as well other results.

        + level                     ::Level              ... Atomic level to which the outcome refers to.
        + K                         ::Float64            ... K enhancement parameter
    """
    struct Outcome
        level                       ::Level
        K                           ::Float64
    end

    """
    `AlphaVariation.Outcome()`  ... constructor for an `empty` instance of AlphaVariation..Outcome for the computation of alpha-variation properties.
    """
    function Outcome()
        Outcome(Level(), 0.)
    end

    # `Base.show(io::IO, outcome::AlphaVariation.Outcome)`  ... prepares a proper printout of the variable outcome::AlphaVariation.Outcome.
    function Base.show(io::IO, outcome::AlphaVariation.Outcome)
        println(io, "level:                   $(outcome.level)  ")
        println(io, "K:                       $(outcome.K)  ")
    end

    """
    `AlphaVariation.computeOutcomes(multiplet::Multiplet, nm::Nuclear.Nuclear.Model, grid::Radial.Grid, settings::AlphaVariation.Settings; output=true)`
        ... to compute (as selected) the alpha-variation parameters for the levels of the given multiplet and as specified by the given settings.
            The results are printed in neat tables to screen but nothing is returned otherwise.
    """
    function computeOutcomes(multiplet::Multiplet, nm::Nuclear.Nuclear.Model, grid::Radial.Grid, settings::AlphaVariation.Settings; output=true)
        println("")
        printstyled("AlphaVariation.computeOutcomes(): The computation of the alpha-variation parameters starts now ... \n", color=:light_green)
        printstyled("-------------------------------------------------------------------------------------------------- \n", color=:light_green)
        #
        outcomes = AlphaVariation.determineOutcomes(multiplet, settings)
        # Display all selected levels before the computations start
        if  settings.printBefore    AlphaVariation.displayOutcomes(outcomes)    end
        # Calculate all amplitudes and requested properties
        newOutcomes = AlphaVariation.Outcome[]
        for  outcome in outcomes
            ## newOutcome = AlphaVariation.computeAmplitudesProperties(outcome, nm, grid, settings)
            newOutcome = AlphaVariation.Outcome(outcome.level, 2.0)
            push!( newOutcomes, newOutcome)
        end
        # Print all results to screen
        AlphaVariation.displayResults(stdout, newOutcomes)
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        if  printSummary    AlphaVariation.displayResults(iostream, newOutcomes)   end
        #
        if    output    return( newOutcomes )
        else            return( nothing )
        end
    end

    """
    `AlphaVariation.determineOutcomes(multiplet::Multiplet, settings::AlphaVariation.Settings)`
        ... to determine a list of Outcomes's for the computation of the alpha-variation parameters for the given multiplet.
            It takes into account the particular selections and settings. An Array{AlphaVariation.Outcome,1} is returned.
            Apart from the level specification, all physical properties are set to zero during the initialization process.
    """
    function  determineOutcomes(multiplet::Multiplet, settings::AlphaVariation.Settings)
        outcomes = AlphaVariation.Outcome[]
        for  level  in  multiplet.levels
            if  Basics.selectLevel(level, settings.levelSelection)
                push!( outcomes, AlphaVariation.Outcome(level, 0.) )
            end
        end
        return( outcomes )
    end

    """
    `AlphaVariation.displayOutcomes(outcomes::Array{AlphaVariation.Outcome,1})`  ... to display a list of levels that have been selected
         for the computations. A small neat table of all selected levels and their energies is printed but nothing is returned otherwise.
    """
    function  displayOutcomes(outcomes::Array{AlphaVariation.Outcome,1})
        nx = 43
        println(" ")
        println("  Selected AlphaVariation levels:")
        println(" ")
        println("  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(10, "Level"; na=2);                             sb = sb * TableStrings.hBlank(12)
        sa = sa * TableStrings.center(10, "J^P";   na=4);                             sb = sb * TableStrings.hBlank(14)
        sa = sa * TableStrings.center(14, "Energy"; na=4);
        sb = sb * TableStrings.center(14, TableStrings.inUnits("energy"); na=4)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(nx))
        #
        for  outcome in outcomes
            sa  = "  ";    sym = LevelSymmetry( outcome.level.J, outcome.level.parity)
            sa = sa * TableStrings.center(10, TableStrings.level(outcome.level.index); na=2)
            sa = sa * TableStrings.center(10, string(sym); na=4)
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", outcome.level.energy)) * "    "
            println( sa )
        end
        println("  ", TableStrings.hLine(nx))
        #
        return( nothing )
    end

    """
    `AlphaVariation.displayResults(stream::IO, outcomes::Array{AlphaVariation.Outcome,1})`
        ... to display the energies, M_ms and F-parameters, etc. for the selected levels. A neat table is printed but nothing
            is returned otherwise.
    """
    function  displayResults(stream::IO, outcomes::Array{AlphaVariation.Outcome,1})
        nx = 64
        println(stream, " ")
        println(stream, "  Alpha variation parameters:")
        println(stream, " ")
        println(stream, "  ", TableStrings.hLine(nx))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(10, "Level"; na=2);                             sb = sb * TableStrings.hBlank(12)
        sa = sa * TableStrings.center(10, "J^P";   na=4);                             sb = sb * TableStrings.hBlank(14)
        sa = sa * TableStrings.center(14, "Energy"; na=4)
        sb = sb * TableStrings.center(14, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.center(14, "K";     na=4)
        sb = sb * TableStrings.center(14, "    " ; na=4)
        println(stream, sa);    println(stream, sb);    println(stream, "  ", TableStrings.hLine(nx))
        #
        for  outcome in outcomes
            sa  = "  ";    sym = LevelSymmetry( outcome.level.J, outcome.level.parity)
            sa = sa * TableStrings.center(10, TableStrings.level(outcome.level.index); na=2)
            sa = sa * TableStrings.center(10, string(sym); na=4)
            energy = 1.0
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", energy))              * "    "
            sa = sa * @sprintf("%.8e", outcome.K)                                               * "    "
            println(stream, sa )
        end
        println(stream, "  ", TableStrings.hLine(nx), "\n\n")
        #
        return( nothing )
    end

end # module


module GPMLj

using PyCall

export
    gpflow,
    instantiate!,
    minimize!,
    predict_f,
    predict_f_samples,
    GPFlowObject

abstract type GPFlowObject end

function instantiate!(o) end
function instantiate!(o::Nothing) return nothing end
function minimize!(opt, m) end
function predict_f(m, Xnew) end
function predict_f_samples(m, Xnew, num_samples) end

include("gpflow.jl")

end # module


# MadNLP.jl
# Created by Sungho Shin (sungho.shin@wisc.edu)

module Mc68

import ..MadNLPHSL: @kwdef, libhsl

@kwdef mutable struct Control
    f_array_in::Cint = 0
    f_array_out::Cint = 0
    min_l_workspace::Clong = 0
    lp::Cint = 0
    wp::Cint = 0
    mp::Cint = 0
    nemin::Cint = 0
    print_level::Cint = 0
    row_full_thresh::Cint = 0
    row_search::Cint = 0
end

@kwdef mutable struct Info
    flag::Cint = 0
    iostat::Cint = 0
    stat::Cint = 0
    out_range::Cint = 0
    duplicate::Cint = 0
    n_compressions::Cint = 0
    n_zero_eigs::Cint = 0
    l_workspace::Clong = 0
    zb01_info::Cint = 0
    n_dense_rows::Cint = 0
end

function get_mc68_default_control()
    control = Control(0,0,0,0,0,0,0,0,0,0)
    mc68_default_control_i(control)
    return control
end

mc68_default_control_i(control::Control) = ccall((:mc68_default_control_i,libhsl),
                                                 Nothing,
                                                 (Ref{Control},),
                                                 control)

mc68_order_i(ord::Int32,n::Int32,ptr::Array{Int32,1},row::Array{Int32,1},
             perm::Array{Int32,1},control::Control,info::Info) = ccall(
                 (:mc68_order_i,libhsl),
                 Nothing,
                 (Cint,Cint,Ptr{Cint},Ptr{Cint},Ptr{Cint},Ref{Control},Ref{Info}),
                 ord,n,ptr,row,perm,control,info)

end #module


@testset "Starting Area" begin

    # Test Starting Area
    x_nom = VehicleState(1.0, 1.0, 1.0, 1.0)
    x_min = VehicleState(0.0, 0.0, 0.0, 0.0)
    x_max = VehicleState(1.0, 2.0, 3.0, 4.0)
    start1 = StartingArea(x_nom, x_min, x_max)
    start2 = StartingArea(x_nom)
    start3 = StartingArea(x_min, x_max)

    @test start1.x_nom == x_nom
    @test start1.x_min == x_min
    @test start1.x_max == x_max

    @test start2.x_nom == x_nom
    @test start2.x_min == x_nom
    @test start2.x_max == x_nom

    @test start3.x_nom == VehicleState(0.5, 1.0, 1.5, 2.0)
    @test start3.x_min == x_min
    @test start3.x_max == x_max

    xr = randstate(start1)
    @test 0.0 <= xr.x <= 1.0
    @test 0.0 <= xr.y <= 2.0
    @test 0.0 <= xr.θ <= 3.0
    @test 0.0 <= xr.v <= 4.0

end


# compute PCA using proPCA software
global propca_exe = "/scratch/users/bbchu/ProPCA/build/propca"
seed = 1

for chr in 1:22
    println("computing PCA for chr $chr")
    plinkname = "/scratch/users/bbchu/ukb_SHAPEIT/subset/ukb.10k.chr$chr"
    outfile = "/scratch/users/bbchu/ukb_SHAPEIT/subset_pca/ukb.10k.chr$chr."
    run(`$propca_exe -g $plinkname -k 10 -o $outfile -nt $(Threads.nthreads()) -seed $seed`)
end


using Base.Test
import Ogre

@testset "Value sets" begin
    vs_notemp = Ogre.ValueSet(1,2,3)
    vs_full = Ogre.ValueSet(1,2,3,4)
    @test_throws MethodError Ogre.ValueSet(1,2)

    @testset "Type hierarchy" begin
        tempdep = Ogre.ValueSet{Ogre.WithTemp}
        notemp = Ogre.ValueSet{Ogre.NoTemp}
        @test !isa(vs_notemp, tempdep)
        @test isa(vs_notemp, notemp)
        @test isa(vs_notemp, Ogre.ValueSet)
        @test isa(vs_full, tempdep)
        @test !isa(vs_full, notemp)
        @test isa(vs_full, Ogre.ValueSet)
    end

    @testset "Get values from them" begin
        @test Ogre.mass(vs_notemp) == 1
        @test Ogre.nonmass(vs_notemp) == [2,3]
        @test Ogre.mass(vs_full) == 1
        @test Ogre.nonmass(vs_full) == [2,3,4]
        @test Ogre.radius(vs_notemp) == 2
        @test Ogre.pressure(vs_full) == 3
        @test_throws MethodError Ogre.temperature(vs_notemp)
        @test Ogre.temperature(vs_full) == 4
    end

    @testset "Zero values or less are considered 'unphysical'" begin
        @test !Ogre.isphysical(Ogre.zero(Ogre.ValueSet{Ogre.NoTemp}))
        @test !Ogre.isphysical(Ogre.zero(Ogre.ValueSet{Ogre.WithTemp}))
        @test !Ogre.isphysical(Ogre.ValueSet(0, 0, -1))
        @test !Ogre.isphysical(Ogre.ValueSet(0, 0, 0, -1))
    end
end


""" Compile or evaluate morphisms as Julia programs.
"""
module GenerateJuliaPrograms
export Block, CompileState, compile, compile_expr, compile_block,
  evaluate, evaluate_hom

using GeneralizedGenerated: mk_function

using ...Catlab
import ...Meta: Expr0, concat_expr
using ...Theories: ObExpr, HomExpr, dom, codom

# Compilation
#############

""" A block of Julia code with input and output variables.
"""
struct Block
  code::Expr
  inputs::Vector{<:Expr0}
  outputs::Vector{<:Expr0}
end

""" Internal state for compilation of morphism into Julia code.
"""
abstract type CompileState end

mutable struct SimpleCompileState <: CompileState
  nvars::Int
  SimpleCompileState(; nvars::Int=0) = new(nvars)
end

""" Compile a morphism expression into a Julia function.
"""
function compile(mod::Module, f::HomExpr; kw...)
  mk_function(mod, compile_expr(f; kw...))
end
compile(f::HomExpr; kw...) = compile(Main, f)

""" Compile a morphism expression into a Julia function expression.
"""
function compile_expr(f::HomExpr; name::Symbol=Symbol(),
                      args::Vector{Symbol}=Symbol[],
                      arg_types::Vector{<:Expr0}=Symbol[])
  inputs = isempty(args) ? input_exprs(ndims(dom(f)), kind=:variables) : args
  block = compile_block(f, inputs)
  to_function_expr(block; name=name, arg_types=arg_types)
end

""" Compile a morphism expression into a block of Julia code.
"""
function compile_block(f::HomExpr, inputs::Vector)
  compile_block(f, inputs, SimpleCompileState())
end

function compile_block(f::HomExpr{:generator}, inputs::Vector,
                       state::CompileState)::Block
  nin, nout = ndims(dom(f)), ndims(codom(f))
  @assert length(inputs) == nin
  outputs = genvars(state, nout)

  lhs = nout == 1 ? first(outputs) : Expr(:tuple, outputs...)
  rhs = generator_expr(f, inputs, state)
  Block(Expr(:(=), lhs, rhs), inputs, outputs)
end

function compile_block(f::HomExpr{:compose}, inputs::Vector,
                       state::CompileState)::Block
  code = Expr(:block)
  vars = inputs
  for g in args(f)
    block = compile_block(g, vars, state)
    code = concat_expr(code, block.code)
    vars = block.outputs
  end
  outputs = vars
  Block(code, inputs, outputs)
end

function compile_block(f::HomExpr{:id}, inputs::Vector,
                       state::CompileState)::Block
  Block(Expr(:block), inputs, inputs)
end

function compile_block(f::HomExpr{:otimes}, inputs::Vector,
                       state::CompileState)::Block
  code = Expr(:block)
  outputs = empty(inputs)
  i = 1
  for g in args(f)
    nin = ndims(dom(g))
    block = compile_block(g, inputs[i:i+nin-1], state)
    code = concat_expr(code, block.code)
    append!(outputs, block.outputs)
    i += nin
  end
  Block(code, inputs, outputs)
end

function compile_block(f::HomExpr{:braid}, inputs::Vector,
                       state::CompileState)::Block
  m = ndims(first(f))
  outputs = [inputs[m+1:end]; inputs[1:m]]
  Block(Expr(:block), inputs, outputs)
end

function compile_block(f::HomExpr{:mcopy}, inputs::Vector,
                       state::CompileState)::Block
  reps = div(ndims(codom(f)), ndims(dom(f)))
  outputs = reduce(vcat, fill(inputs, reps))
  Block(Expr(:block), inputs, outputs)
end

function compile_block(f::HomExpr{:delete}, inputs::Vector,
                       state::CompileState)::Block
  Block(Expr(:block), inputs, empty(inputs))
end

""" Convert a block of Julia code into a Julia function expression.
"""
function to_function_expr(block::Block; name::Symbol=Symbol(),
                          arg_types::Vector{<:Expr0}=Symbol[],
                          kwargs::Vector{<:Expr0}=Symbol[])
  # Create list of call arguments.
  args = block.inputs
  if !isempty(arg_types)
    @assert length(args) == length(arg_types)
    args = [ Expr(:(::), arg, type) for (arg, type) in zip(args, arg_types) ]
  end
  if !isempty(kwargs)
    kwargs = [ (kw isa Expr ? kw : Expr(:kw, kw, nothing)) for kw in kwargs ]
    args = [ Expr(:parameters, kwargs...); args ]
  end

  # Create call expression (function header).
  call_expr = if name == Symbol() # Anonymous function
    Expr(:tuple, args...)
  else # Named function
    Expr(:call, name, args...)
  end

  # Create function body.
  return_expr = Expr(:return, if length(block.outputs) == 1
    block.outputs[1]
  else
    Expr(:tuple, block.outputs...)
  end)
  body_expr = concat_expr(block.code, return_expr)

  Expr(:function, call_expr, body_expr)
end

""" Generate Julia expression for evaluation of morphism generator.
"""
function generator_expr(f::HomExpr{:generator}, inputs::Vector,
                        state::CompileState)
  # By default, treat even nullary generators as functions, not constants.
  value = first(f)
  Expr(:call, value::Symbol, inputs...)
end

""" Generate expressions for inputs to Julia code.
"""
function input_exprs(n::Int; kind::Symbol=:variables, prefix::Symbol=:x)
  if kind == :variables
    [ Symbol(string(prefix, i)) for i in 1:n ]
  elseif kind == :array
    [ :($prefix[$i]) for i in 1:n ]
  else
    error("Unknown input kind: $kind")
  end
end

""" Generate a fresh variable (symbol).

This is basically `gensym` with local, not global, symbol counting.
"""
function genvar(state::CompileState; prefix::Symbol=:v)::Symbol
  Symbol(string(prefix, state.nvars += 1))
end
function genvars(state::CompileState, n::Int; prefix::Symbol=:v)::Vector{Symbol}
  Symbol[ genvar(state; prefix=prefix) for i in 1:n ]
end

# Evaluation
############

""" Evaluate a morphism as a function.

If the morphism will be evaluated only once (possibly with vectorized inputs),
then direct evaluation will be much faster than compiling (via `compile`) and
evaluating a standard Julia function.

Compare with [`functor`](@ref).
"""
function evaluate(f::HomExpr, xs...; kw...)
  make_return_value(evaluate_hom(f, collect(xs); kw...))
end

function evaluate_hom(f::HomExpr{:generator}, xs::Vector;
                      generators::AbstractDict=Dict(), broadcast::Bool=false)
  fun = generators[first(f)]
  y = broadcast ? fun.(xs...) : fun(xs...)
  y isa Tuple ? collect(y) : [y]
end

function evaluate_hom(f::HomExpr{:compose}, xs::Vector; kw...)
  foldl((ys, g) -> evaluate_hom(g, ys; kw...), args(f); init=xs)
end

function evaluate_hom(f::Union{HomExpr{:otimes},HomExpr{:oplus}}, xs::Vector; kw...)
  i = 1
  mapreduce(vcat, args(f); init=[]) do g
    m = ndims(dom(g))
    ys = evaluate_hom(g, xs[i:i+m-1]; kw...)
    i += m
    ys
  end
end

evaluate_hom(f::HomExpr{:id}, xs::Vector; kw...) = xs
evaluate_hom(f::HomExpr{:braid}, xs::Vector; kw...) = [xs[2], xs[1]]

evaluate_hom(f::HomExpr{:mcopy}, xs::Vector; kw...) = begin
  reduce(vcat, fill(xs, ndims(codom(f)) ÷ ndims(dom(f))); init=[])
end
evaluate_hom(f::HomExpr{:delete}, xs::Vector; kw...) = []

""" Return a zero, one, or more values, following Julia conventions.
"""
function make_return_value(values)
  if isempty(values)          # Nullary case.
    nothing
  elseif length(values) == 1  # Unary case.
    first(values)
  else                        # General case.
    Tuple(values)
  end
end

end


module Solutions

    using HDF5
    using OffsetArrays
    using SharedArrays

    using GeometricBase
    using GeometricBase.Config
    using GeometricBase.Utils
    using GeometricEquations

    using GeometricBase: fromarray

    export DEFAULT_NSAVE, DEFAULT_NWRITE

    const DEFAULT_NSAVE = 1
    const DEFAULT_NWRITE = 0

    export DataSeries, TimeSeries, Solution, DeterministicSolution
    export get_solution, get_solution!, set_solution!

    include("solutions/solution.jl")

    export AtomicSolution,
           AtomicSolutionODE, AtomicSolutionPODE,
           AtomicSolutionDAE, AtomicSolutionPDAE
    export update!, cut_periodic_solution!

    include("solutions/atomic_solution.jl")
    include("solutions/atomic_solution_ode.jl")
    include("solutions/atomic_solution_pode.jl")
    include("solutions/atomic_solution_dae.jl")
    include("solutions/atomic_solution_pdae.jl")

    export SolutionODE, SSolutionODE, PSolutionODE, SolutionPODE, SSolutionPODE, PSolutionPODE
    export SolutionDAE, SSolutionDAE, PSolutionDAE, SolutionPDAE, SSolutionPDAE, PSolutionPDAE
    export get_initial_conditions, get_initial_conditions!, set_initial_conditions!
    export create_hdf5, create_hdf5!, write_to_hdf5, hdf5

    include("solutions/solution_ode.jl")
    include("solutions/solution_pode.jl")
    include("solutions/solution_dae.jl")
    include("solutions/solution_pdae.jl")

    include("solutions/solution_hdf5.jl")

    include("solutions/atomic_solution_constructors.jl")

end


"""
    SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer

The `SingleBridgeOptimizer` bridges any constraint supported by the bridge `BT`.
This is in contrast with the [`LazyBridgeOptimizer`](@ref) which only bridges the constraints that are unsupported by the internal model, even if they are supported by one of its bridges.
"""
mutable struct SingleBridgeOptimizer{BT<:AbstractBridge, OT<:MOI.ModelLike} <: AbstractBridgeOptimizer
    model::OT
    con_to_name::Dict{CI, String}
    name_to_con::Union{Dict{String, MOI.ConstraintIndex}, Nothing}
    # Constraint Index of bridged constraint -> Bridge.
    # It is set to `nothing` when the constraint is deleted.
    bridges::Vector{Union{Nothing, AbstractBridge}}
    # Constraint Index of bridged constraint -> Constraint type.
    constraint_types::Vector{Tuple{DataType, DataType}}
    # For `SingleVariable` constraints: (variable, set type) -> bridge
    single_variable_constraints::Dict{Tuple{Int64, DataType}, AbstractBridge}
end
function SingleBridgeOptimizer{BT}(model::OT) where {BT, OT <: MOI.ModelLike}
    SingleBridgeOptimizer{BT, OT}(
        model, Dict{CI, String}(), nothing,
        Union{Nothing, AbstractBridge}[], Tuple{DataType, DataType}[],
        Dict{Tuple{Int64, DataType}, AbstractBridge}())
end

function supports_bridging_constraint(b::SingleBridgeOptimizer{BT},
                                      F::Type{<:MOI.AbstractFunction},
                                      S::Type{<:MOI.AbstractSet}) where BT
    return MOI.supports_constraint(BT, F, S)
end
function is_bridged(b::SingleBridgeOptimizer, F::Type{<:MOI.AbstractFunction},
                    S::Type{<:MOI.AbstractSet})
    return supports_bridging_constraint(b, F, S)
end
bridge_type(b::SingleBridgeOptimizer{BT}, ::Type{<:MOI.AbstractFunction}, ::Type{<:MOI.AbstractSet}) where BT = BT


using Test
using StaticArrays
using Proj
import PROJ_jll

@testset "Error handling" begin
    @test Proj.proj_errno_string(0) === nothing
    @test Proj.proj_errno_string(1) == "Unknown error (code 1)"
    @test Proj.proj_errno_string(Proj.PROJ_ERR_INVALID_OP_WRONG_SYNTAX) ==
          "Invalid PROJ string syntax"

    pj = Proj.proj_create("EPSG:4326")
    Proj.proj_errno_set(pj, 14)
    @test Proj.proj_errno(pj) == 14
    Proj.proj_errno_reset(pj)
    @test Proj.proj_errno(pj) == 0
    pj = Proj.proj_destroy(pj)

    # this throws an internal error that is handled by our log_func
    @test_throws Proj.PROJError Proj.proj_create("+proj=bobbyjoe")
    # ensure we have reset the error state
    @test Proj.proj_context_errno() == 0

end

@testset "Database" begin
    crs = Proj.proj_create_from_database("EPSG", "4326", Proj.PJ_CATEGORY_CRS, false)
    Proj.proj_errno(crs)
    Proj.proj_get_id_code(crs, 0)
end

@testset "Create" begin
    pj_latlon = Proj.proj_create("EPSG:4326")

    esriwkt = """GEOGCS["GCS_WGS_1984",DATUM["D_WGS_1984",SPHEROID["WGS_1984",6378137.0,298.257223563]],PRIMEM["Greenwich",0.0],UNIT["Degree",0.0174532925199433]]"""
    pj2 = Proj.proj_create(esriwkt)

    @test startswith(Proj.proj_as_wkt(pj2, Proj.PJ_WKT1_ESRI, C_NULL), "GEOGCS")
    @test startswith(Proj.proj_as_wkt(pj2, Proj.PJ_WKT2_2018, C_NULL), "GEOGCRS")
end

@testset "Transformation between CRS" begin
    # taken from http://osgeo-org.1560.x6.nabble.com/PROJ-PROJ-6-0-0-proj-create-operation-factory-context-behavior-td5403470.html
    src = Proj.proj_create("IGNF:REUN47GAUSSL")   # area : 55.17,-21.42,55.92,-20.76
    tgt = Proj.proj_create("IGNF:RGAF09UTM20")    # area : -63.2,14.25,-60.73,18.2
    factory = Proj.proj_create_operation_factory_context(C_NULL)
    @test factory != C_NULL
    @test Proj.proj_context_errno() == 0
    results = Proj.proj_create_operations(src, tgt, factory)
    @test results != C_NULL
    n = Proj.proj_list_get_count(results)
    @test n == 1

    operation = Proj.proj_list_get(results, 0)
    @test Bool(Proj.proj_coordoperation_has_ballpark_transformation(operation))
    @test Bool(Proj.proj_coordoperation_has_ballpark_transformation(operation))
    @test startswith(Proj.proj_get_name(operation), "Inverse of GAUSS LABORDE REUNION")

    info = Proj.proj_pj_info(operation)
    @test unsafe_string(info.id) == "pipeline"
    @test startswith(unsafe_string(info.description), "Inverse of GAUSS LABORDE REUNION")
    @test startswith(unsafe_string(info.definition), "proj=pipeline step inv proj=gstmerc")
    @test Bool(info.has_inverse)
    @test info.accuracy === -1.0
    Proj.proj_destroy(operation)

    Proj.proj_list_destroy(results)

    Proj.proj_operation_factory_context_destroy(factory)
    Proj.proj_destroy(src)
    Proj.proj_destroy(tgt)
end

@testset "Quickstart" begin
    # https://proj.org/development/quickstart.html

    pj = Proj.proj_create_crs_to_crs(
        "EPSG:4326",  # source
        "+proj=utm +zone=32 +datum=WGS84",  # target, also EPSG:32632
    )

    # This will ensure that the order of coordinates for the input CRS
    # will be longitude, latitude, whereas EPSG:4326 mandates latitude,
    # longitude
    pj_for_gis = Proj.proj_normalize_for_visualization(pj)
    Proj.proj_destroy(pj)
    pj = pj_for_gis

    # a coordinate union representing Copenhagen: 55d N, 12d E
    # Given that we have used proj_normalize_for_visualization(), the order of
    # coordinates is longitude, latitude, and values are expressed in degrees.
    a = Proj.proj_coord(12, 55)
    @test a isa Proj.Coord
    @test a isa AbstractVector
    @test eltype(a) == Float64
    @test length(a) == 4
    @test sum(a) == Inf
    @test isbits(a)
    @test a[1] === 12.0
    @test a[2] === 55.0
    @test a[3] === 0.0
    @test a[4] === Inf

    # transform to UTM zone 32
    b = Proj.proj_trans(pj, Proj.PJ_FWD, a)
    @test is_approx(b, (691875.632, 6098907.825, 0.0, Inf))

    # inverse transform, back to geographical
    b = Proj.proj_trans(pj, Proj.PJ_INV, b)
    @test is_approx(b, (12.0, 55.0, 0.0, Inf))

    # Clean up
    pj = Proj.proj_destroy(pj)
    pj = Proj.proj_destroy(pj)
    # Julia crashes if proj_destroy is called twice on the same non nullptr,
    # since the contents at that address are already wiped. To prevent this,
    # change the input binding directly after, e.g. pj = proj_destroy(pj)  # -> C_NULL
end

@testset "inverse transformation" begin
    trans = Proj.Transformation(
        "EPSG:4326",
        "+proj=utm +zone=32 +datum=WGS84",
        always_xy = true,
    )

    # for custom / proj strings, or modified axis order, no description can be looked up in
    # the database
    @test repr(trans) == """
    Transformation
        source: unknown
        target: unknown
        direction: forward
    """

    trans⁻¹ = inv(trans, always_xy = true)
    trans¹ = inv(trans⁻¹, always_xy = true)

    # inv does not flip source and target, so the WKT stays the same
    wkt_type = Proj.PJ_WKT2_2019
    @test Proj.proj_as_wkt(trans⁻¹.pj, wkt_type) == Proj.proj_as_wkt(trans.pj, wkt_type)
    @test Proj.proj_as_wkt(trans¹.pj, wkt_type) == Proj.proj_as_wkt(trans.pj, wkt_type)

    a = (12.0, 55.0)
    b = (691875.632, 6098907.825)
    @test is_approx(trans⁻¹(b), a)
    @test is_approx(trans¹(a), b)
    @test trans⁻¹.direction == PJ_INV
    @test trans¹.direction == PJ_FWD

    # we don't check if source and target are equal when constructing a identity transform
    # since Transformation is mutable, it can always be changed after construction
    trans = Proj.Transformation(
        "EPSG:4326",
        "+proj=utm +zone=32 +datum=WGS84",
        direction = PJ_IDENT,
    )
    @test trans(a) === a
    trans⁻¹ = inv(trans, always_xy = true)
    @test trans⁻¹(a) === a
    @test trans⁻¹.direction == PJ_IDENT
    trans⁻¹.direction = PJ_FWD
    @test is_approx(trans⁻¹(a), b)

    @test inv(PJ_FWD) == PJ_INV
    @test inv(PJ_IDENT) == PJ_IDENT
    @test inv(PJ_INV) == PJ_FWD
end

@testset "single transformation" begin
    source_crs = Proj.proj_create("EPSG:4326")
    target_crs = Proj.proj_create("EPSG:28992")
    @test source_crs isa Ptr{Nothing}
    @test target_crs isa Ptr{Nothing}
    trans = Proj.Transformation(source_crs, target_crs)

    # Check that altitude and time inputs are correctly forwarded from the transformation
    p = trans(SA_F64[52.16, 5.39, 5, 2020])
    @test is_approx(p, (155191.3538124342, 463537.1362732911, 5.0, 2020.0))

    a = (52.16, 5.39)
    b = Proj.proj_trans(trans.pj, Proj.PJ_FWD, a)
    @test is_approx(b, (155191.3538124342, 463537.1362732911))

    # with always_xy = true, we need to use lon/lat, and still get x/y out
    trans = Proj.Transformation(source_crs, target_crs, always_xy = true)
    a = (5.39, 52.16)
    b = Proj.proj_trans(trans.pj, Proj.PJ_FWD, a)
    @test is_approx(b, (155191.3538124342, 463537.1362732911))
end

@testset "dense 4D coord vector transformation" begin
    source_crs = Proj.proj_create("EPSG:4326")
    target_crs = Proj.proj_create("EPSG:28992")
    trans = Proj.Transformation(source_crs, target_crs, always_xy = true)
    # This array is mutated in place. Note that this array needs to have 4D elements,
    # with 2D elements it will only do every other one
    A = [Proj.Coord(5.39, 52.16) for _ = 1:5]
    err = Proj.proj_trans_array(trans.pj, Proj.PJ_FWD, length(A), A)
    @test err == 0
    B = [Proj.Coord(155191.3538124342, 463537.1362732911) for _ = 1:5]
    @test all(is_approx(a, b) for (a, b) in zip(A, B))

    # since A is not in target_crs, we will get an error on a second call
    err = Proj.PROJError("push: Invalid latitude")
    @test_throws err Proj.proj_trans_array(trans.pj, Proj.PJ_FWD, length(A), A)
    # error number is reset during PROJError construction
    @test Proj.proj_errno(trans.pj) == 0
    # test triggering finalizer manually
    finalize(trans)
    @test trans.pj === C_NULL
end

@testset "generic array transformation" begin
    source_crs = Proj.proj_create("EPSG:4326")
    target_crs = Proj.proj_create("EPSG:28992")
    trans = Proj.Transformation(source_crs, target_crs, always_xy = true)

    # inplace transformation of vector of 2D coordinates
    # using https://proj.org/development/reference/functions.html#c.proj_trans_generic
    A = [SA[5.39, 52.16] for _ = 1:5]
    st = sizeof(first(A))
    ptr = pointer(A)
    n = length(A)
    # 8 is sizeof(Float64), so ptr + 8 points to first latitude element
    n_done = Proj.proj_trans_generic(
        trans.pj,
        Proj.PJ_FWD,
        ptr,
        st,
        n,
        ptr + 8,
        st,
        n,
        C_NULL,
        C_NULL,
        C_NULL,
        C_NULL,
        C_NULL,
        C_NULL,
    )
    @test n_done == n
    B = [SA[155191.3538124342, 463537.1362732911] for _ = 1:5]
    @test A ≈ B
end

@testset "compose" begin
    trans1 = Proj.Transformation("EPSG:4326", "EPSG:28992", always_xy = true)
    trans2 = Proj.Transformation("EPSG:32632", "EPSG:2027", always_xy = true)
    trans = trans1 ∘ trans2  # same as compose(trans1, trans2)
    source_crs = Proj.proj_get_source_crs(trans.pj)
    target_crs = Proj.proj_get_target_crs(trans.pj)

    # trans1 source is the new source
    source_crs1 = Proj.proj_get_source_crs(trans1.pj)
    source_wkt = Proj.proj_as_wkt(source_crs, Proj.PJ_WKT2_2019, C_NULL)
    source_wkt1 = Proj.proj_as_wkt(source_crs1, Proj.PJ_WKT2_2019, C_NULL)
    @test source_wkt == source_wkt1

    # trans2 source is the new target
    target_crs1 = Proj.proj_get_target_crs(trans2.pj)
    target_wkt = Proj.proj_as_wkt(target_crs, Proj.PJ_WKT2_2019, C_NULL)
    target_wkt1 = Proj.proj_as_wkt(target_crs1, Proj.PJ_WKT2_2019, C_NULL)
    @test target_wkt == target_wkt1

    # which we can also see from show
    @test repr(trans) == """
    Transformation
        source: WGS 84 (with axis order normalized for visualization)
        target: NAD27(76) / UTM zone 15N
        direction: forward
    """
end

@testset "Coord constructor" begin
    coord = Proj.Coord(1.0, 2.0, 3.0, Inf)
    @test_throws MethodError Proj.Coord()
    @test_throws ErrorException Proj.Coord(0.0)
    @test Proj.Coord(1.0, 2.0) == Proj.Coord(1.0, 2.0, 0.0, Inf)
    @test Proj.Coord(1.0, 2.0, 3.0) == coord
    @test Proj.Coord(1.0, 2.0, 3.0, 4.0) == Proj.Coord(1.0, 2.0, 3.0, 4.0)

    @test Proj.Coord((1.0, 2.0, 3.0)) == coord
    @test Proj.Coord([1.0, 2.0, 3.0]) == coord
    @test Proj.Coord(1, 2, 3) == coord
    @test Proj.Coord([1, 2, 3]) == coord
    @test Proj.Coord(UInt8[1, 2, 3]) == coord
    @test Proj.Coord(1:3) == coord
    @test Proj.Coord(1.0f0:3) == coord
    @test Proj.Coord((i for i = 1:3)) == coord
    @test Proj.Coord(coord) === coord
    @test convert(Proj.Coord, coord) === coord
    @test convert(Proj.Coord, (1.0, 2.0, 3.0)) == coord

    @inferred Proj.Coord(1, 2)
    @inferred Proj.Coord((1, 2))
    @inferred Proj.Coord([1, 2])
    @inferred Proj.Coord(SA[1, 2])
end

@testset "in and output types" begin
    trans = Proj.Transformation("EPSG:4326", "EPSG:28992", always_xy = true)
    trans(Proj.proj_coord(5.39, 52.16))
    b = trans(SA[5.39, 52.16, 0.0, 0.0])

    # StaticVector, Vector or Tuple
    x, y, z, t = 5.39, 52.16, 0.0, Inf

    @inferred trans(x, y)
    @inferred trans(x, y, z)
    @inferred trans(x, y, z, t)
    @inferred trans(Proj.Coord(x, y))
    @inferred trans((x, y))
    # for Vector it cannot be precisely inferred due to unknown length
    @inferred Proj.NTuple234 trans([x, y])
    @inferred trans(SA[x, y])

    # SVector{2/3/4, Float64}
    p = trans(SA[5, 52])
    @test is_approx(p, (128410.08537081012, 445806.50883314764))
    p = trans(SA[5.39, 52.16, 2.0])
    @test is_approx(p, (155191.35381147722, 463537.13624246384, 2.0))
    p = trans(SA[5.39, 52.16, 0.0, Inf])
    @test is_approx(p, (155191.3538124342, 463537.1362732911, 0.0, Inf))
end

@testset "network" begin
    as_before = Proj.network_enabled()

    # turn off network, no z transformation
    @test !Proj.enable_network!(false)
    @test !Proj.network_enabled()
    trans_z = Proj.Transformation("EPSG:4326+5773", "EPSG:7856+5711", always_xy = true)
    @test trans_z((151, -33, 5))[3] == 5
    # turn on network, z transformation
    @test Proj.enable_network!(true)
    @test Proj.network_enabled()
    trans_z = Proj.Transformation("EPSG:4326+5773", "EPSG:7856+5711", always_xy = true)
    z = trans_z((151, -33, 5))[3]
    @test z ≈ 5.280603319143665

    # 0 args turns it on as well
    Proj.enable_network!(false)
    Proj.enable_network!()
    @test Proj.network_enabled()

    # restore setting as outside the testset
    Proj.enable_network!(as_before)
end


"""
    AbstractLayout

Supertype for specifying the layout of new instances of arrays.
"""
abstract type AbstractLayout{T} end

""" MemoryLayout """
abstract type MemoryLayout{T} <: AbstractLayout{T} end

struct DynamicMemory{T} <: MemoryLayout{T}
    length::Int
end

struct FixedMemory{T} <: MemoryLayout{T}
    length::Int
end

struct StaticMemory{T,N} <: MemoryLayout{T} end

MemoryLayout{T}(length::Int) where {T} = FixedMemory{T}(length)
MemoryLayout{T}(::StaticInt{N}) where {T,N} = StaticMemory{T,N}()

""" AbstractArrayLayout """
abstract type AbstractArrayLayout{T,N,L} <: AbstractLayout{T} end

struct StrideLayout{T,N,L<:MemoryLayout{T},B,C,S,I<:Tuple{Vararg{Any,N}}} <: AbstractArrayLayout{T,N,L}
    layout::L
    contiguous_batch_size::B
    contiguous_axis::C
    strides::S
    indices::I

    function StrideLayout(x::AbstractArray{T,N}) where {T,N}
        inds = map(ArrayInterface.indices, axes(x))
        m = MemoryLayout{T}(prod(map(static_length, inds)))
        b = ArrayInterface.contiguous_batch_size(x)
        c = ArrayInterface.contiguous_axis(x)
        s = ArrayInterface.strides(x)
        return new{T,N,typeof(m),typeof(b),typeof(c),typeof(s),typeof(inds)}(
            m, b, c, s, inds
        )
    end
end

struct ArrayLayout{T,N,L<:AbstractLayout{T},I<:Tuple{Vararg{Any,N}}} <: AbstractArrayLayout{T,N,L}
    layout::L
    indices::I
end

abstract type SparseLayout{T,N,L} <: AbstractArrayLayout{T,N,L} end

struct DiagonalLayout{T,L<:ArrayLayout{T,1}} <: SparseLayout{T,2,L}
    layout::L
end

""" layout """
function layout(x::AbstractArray{T,N}) where {T,N}
    if ArrayInterface.defines_strides(x)
        return StrideLayout(x)
    else
        return ArrayLayout(x)
    end
    inds = map(ArrayInterface.indices, axes(x))
    return ArrayLayout(MemoryLayout{T}(prod(map(static_length, inds))), inds)
end
function layout(x::AbstractVector{T}) where {T}
    if ArrayInterface.can_change_size(x)
        return DynamicMemory(length(x))
    else
        return MemoryLayout{T}(length(x))
    end
end
layout(x::Diagonal) = DiagonalLayout(layout(x.diag))

""" layout """
layout(x::DynamicMemory, ::Type{T}) where {T} = DynamicMemory{T}(x.length)
layout(x::FixedMemory, ::Type{T}) where {T} = FixedMemory{T}(x.length)
layout(x::StaticMemory{T1,N}, ::Type{T2}) where {T1,T2,N} = StaticMemory{T2,N}()
layout(x::ArrayLayout, ::Type{T}) where {T} = ArrayLayout(layout(x.layout, T), x.axes)
layout(x::DiagonalLayout, ::Type{T}) where {T} = DiagonalLayout(layout(x.layout, T))
layout(x::AbstractArray, ::Type{T}) where {T} = layout(layout(x), T)

""" combine_layouts """
@inline function combine_layouts(bc::AbstractBroadcasted)
    typed_layout = Fix2(layout, Base.Broadcast.combine_eltypes(bc.f, bc.args))
    return broadcast_layout(map(typed_layout, bc.args)...)
end

# This is the where new subtypes of `AbstractLayout` need to specify how they combine with
# eachother
""" broadcast_layout(x::AbstractLayout, y::AbstractLayout) """
broadcast_layout(x) = x
broadcast_layout(x, y, zs...) = broadcast_layout(broadcast_layout(x, y), zs...)
function broadcast_layout(x::DiagonalLayout, y::DiagonalLayout)
    return DiagonalLayout(broadcast_layout(x.layout, y.layout))
end
function broadcast_layout(x::AbstractArrayLayout, y::AbstractArrayLayout)
    return ArrayLayout(
        combine_layout(memory_layout(x), memory_layout(y)),
        _bcs(axes(x), axes(y))
    )
end

_bcs(::Tuple{}, ::Tuple{}) = ()
_bcs(::Tuple{}, y::Tuple) = (first(y), _bcs((), tail(y))...)
_bcs(shape::Tuple, ::Tuple{}) = (shape[1], _bcs(tail(shape), ())...)
function _bcs(x::Tuple, y::Tuple)
    return (ArrayInterface.broadcast_axis(first(x), first(y)), _bcs(tail(x), tail(y))...)
end

# TODO
""" vcat_layouts """

""" hcat_layouts """

""" cat_layouts """


import CSV
import DataFrames

symbols = [:SOVEREIGNT, :GDP_MD_EST, :GDP_YEAR]
path = "data/natural_earth_vector"
attributesname = "temp2-attributes.csv"
nodesname = "temp1-nodes.csv"
attributes = DataFrames.DataFrame(CSV.File(joinpath(path, attributesname)))
nodes = DataFrames.DataFrame(CSV.File(joinpath(path, nodesname)))
dict = Dict()
for symbol in symbols
    column = DataFrames.groupby(attributes, symbol)
    dict[symbol] = column
end
gdp_year = DataFrames.groupby(attributes, :GDP_YEAR)
subdataframe = groupdataframe[(SOVEREIGNT="Iran",)]
gdp = subdataframe.GDP_MD_EST


# functions related to mesh warping/coordinate updating

"""
  This function updates the coordinates of the vertices of a given element.
  In parallel, it is the responsibility of the caller to update the coordinates
  consistenly for shared elements.  Note that the user must not change
  the topology of the mesh (ie. the adjacency of mesh entities), only
  their positions.

  **Inputs**

   * elnum: the element number to update
   * coords_new: the `mesh.dim` x `coord_numNodesPerElement` array of new
            coordinates.
   * snap: if true, snap the coordinates to the geometry (if the geometric
           model supports snapping).  Setting this to false is strongly
           discouraged and can cause inaccurate coordinate and derivative
           evaluations in other functions.  If true, coords_new will be
           overwritten with the snapped coordinates

  For `coords_new`, each column contains the coordinate of a coordinate node,
  starting
  with the vertices, followed by the edges (if quadratic).
  The entities must be ordered the same as the downward adjacencies of
  the elements.  The mesh.topo field combined with mesh.element_vertnums
  can be used to figure this out.

  Note that even if `coords_new` is complex valued, this function only uses
  the real part.  If you need to propigate complex number through the
  metric calculation, see [`recalcCoordinatesAndMetrics`](@ref).

  After completeing all calls to update_coords, users *must* call
  commit_coords().

  This function is supported for backwards compatability, see the other
  methods of `update_coords` which work on the entire mesh at once, which may
  be more efficient.
"""
function update_coords(mesh::PumiMesh, elnum::Integer,  coords_new::AbstractMatrix, snap::Bool=true)

#  @assert size(coords_new, 1) == mesh.dim
#  @assert (size(coords_new, 2) == mesh.dim + 1 || size(coords_new, 2) == mesh.dim+1 + mesh.numTypePerElement[2]) # number of verts or number of verts + edges

  verts = Array{Ptr{Void}}(12)
  coords_j = zeros(Float64, 3)
#  newx_j = zeros(Float64, 3)
#  xi_j = zeros(Float64, 2)
#  g = apf.getModel(mesh.m_ptr)
#  can_eval = gmi.can_eval(g)
#  snap = snap && can_eval

  el_i = mesh.elements[elnum]
  apf.getDownward(mesh.m_ptr, el_i, 0, verts)

  if mesh.coord_order == 1
    @assert (size(coords_new, 2) >= mesh.numTypePerElement[1])
  elseif mesh.coord_order == 2
    @assert (size(coords_new, 2) == (mesh.numTypePerElement[1] + mesh.numTypePerElement[2]))
  else
    throw(ErrorException("Unsupported coordinate order"))
  end

  #TODO: make this a proper master loop construct
  for j=1:(mesh.dim + 1)  # loop over verts
    for k=1:mesh.dim
      coords_j[k] = real(coords_new[k, j])
    end
    setCoords(mesh, verts[j], 0, coords_j, snap)
    for k=1:mesh.dim
      coords_new[k, j] = coords_j[k]
    end
  end

  if apf.hasNodesIn(mesh.coordshape_ptr, 1)
    offset = mesh.dim + 1
    nedges = apf.getDownward(mesh.m_ptr, el_i, 1, verts)
    for j=1:nedges
      for k=1:mesh.dim
        coords_j[k] = real(coords_new[k, j + offset])
      end

      setCoords(mesh, verts[j], 0, coords_j, snap)
      for k=1:mesh.dim
        coords_new[k, j + offset] = coords_j[k]
      end
    end
  end

  return nothing
end

#------------------------------------------------------------------------------
# Internal API for updating xyz and xi coordinates of individual nodes

"""
  Set the parametric coordinates for a node.  Users should *not* call
  apf.setParam directly, use this function instead.  This function
  updates both the parametric and xyz coordinates.

  Note that this function stores the parametric coordinates as defined by
  the CAD engine, not the hybrid xyz-xi format used by `interface_geo.jl`.
  In particular, for 2D meshes, the xi coordinates for a mesh entity classified
  on a model region should be the CAD parametric coordinates, not the xyz.

  **Inputs**

   * mesh: mesh object
   * entity: apf::MeshEntity*
   * node: local node number (0 based)
   * xi: vector of length 3 containing the parametric coordinates.

  **Inputs/Outputs**

   * coords: if provided, will be overwritten with the xyz coordinates
             of the point
"""
function setCoordsXi(mesh::PumiMesh, entity::Ptr{Void}, node::Integer,
                     xi::AbstractVector{Float64},
                     coords::AbstractArray{Float64}=NullArray{Float64}(0))

  g = apf.getModel(mesh.m_ptr)
  me = apf.toModel(mesh.m_ptr, entity)
  me_dim = apf.getModelType(mesh.m_ptr, me)

  @assert me_dim < 3  # there are no parametric coordinates for regions
  @assert me_dim > 0  # vertex parametric coordinates cannot be modified
  @assert mesh.geoNums.can_eval

  # check xi range
  xi_range = Array{Float64}(2)
  for i=1:me_dim
    gmi.range(g, me, i-1, xi_range)
    @assert xi[i] >= xi_range[1]
    @assert xi[i] <= xi_range[2]
  end

  e_dim = apf.getDimension(mesh.m_ptr, entity)
  if e_dim == 0
    apf.setParam(mesh.m_ptr, entity, xi)
  else  # higher dimension xi coordinates are stored in a field
    apf.setComponents(mesh.geoNums.xi_fptr, entity, node, xi)
  end

  coords2 = Array{Float64}(3)  #TODO: cache this
  gmi.geval(g, me, xi, coords2)
  apf.setPoint(mesh.m_ptr, entity, node, coords2)

  if length(coords) > 0
    for i=1:3
      coords[i] = coords2[i]
    end
  end

  return nothing
end

"""
  Retrieve the parametric coordinates for a mesh node, and optionally the
  xyz coordinates as well.  This works for *any* MeshEntity, not only
  vertices like `apf.getParam`.  This function should be preferred in
  all cases over `apf.getParam`.

  **Inputs**

   * mesh: mesh object
   * entity: apf::MeshEntity*
   * node: local node number (0 based)

  **Inputs/Outputs**

   * xi: vector of length 3 to be overwritten with the parametric coordinates.
   * coords: if provided, will be overwritten with the xyz coordinates
             of the point
"""
function getCoordsXi(mesh::PumiMesh, entity::Ptr{Void}, node::Integer,
                     xi::AbstractVector{Float64},
                     coords::AbstractArray{Float64}=NullArray{Float64}(0))

  g = apf.getModel(mesh.m_ptr)
  me = apf.toModel(mesh.m_ptr, entity)
  me_dim = apf.getModelType(mesh.m_ptr, me)

  @assert me_dim < 3  # there are no parametric coordinates for regions
  #@assert me_dim > 0  # vertex parametric coordinates cannot be modified
  @assert mesh.geoNums.can_eval

  e_dim = apf.getDimension(mesh.m_ptr, entity)
  if e_dim == 0
    apf.getParam(mesh.m_ptr, entity, xi)
  else  # higher dimension xi coordinates are stored in a field
    apf.getComponents(mesh.geoNums.xi_fptr, entity, node, xi)
  end

  if length(coords) > 0
    apf.getPoint(mesh.m_ptr, entity, node, coords)
  end

  return nothing
end

"""
  This function updates the xyz coordinates for a given node, and also
  the xi coordinates, which are computed from the xyz coordinates.

  **Inputs**

   * mesh
   * node: local node number (0 based)
   * coords: vector of length 3 containing the xyz coordinates
   * snap: Bool, if true, the coordinates will be snapped onto the geometry
           and the `coords` overwritten with the new coordinates, default
           true.  Setting this to false can result in inaccurate derivative
           calculations.  If the geometric model does not support evaluation
           points, this option has no effect.
"""
function setCoords(mesh::PumiMesh, entity::Ptr{Void}, node::Integer,
                   coords::AbstractArray{Float64}, snap::Bool=true)

  geonums = mesh.geoNums
  newx = Array{Float64}(3)
  xi = Array{Float64}(3)

  me = apf.toModel(mesh.m_ptr, entity)
  me_dim = apf.getModelType(mesh.m_ptr, me)

  if snap && geonums.can_eval
    # update xyz and xi coordinates consistently
    has_xi = getSnappedCoords(mesh, entity, true, coords, newx, xi)
    if has_xi
      if me_dim > 0
        setCoordsXi(mesh, entity, node, xi, newx)
      end
      for i=1:3
        coords[i] = newx[i]
      end
    else
      apf.setPoint(mesh.m_ptr, entity, node, coords)
    end

  elseif !snap && geonums.can_eval
    # update the xyz and xi coordinates, but don't make them consistent
    has_xi = getSnappedCoords(mesh, entity, false, coords, newx, xi)
    if has_xi && me_dim > 0
      setCoordsXi(mesh, entity, node, xi)
    end
    apf.setPoint(mesh.m_ptr, entity, node, coords)  # overwrite coordinates from
                                               # setCoordsXi
  else  # can't eval
    apf.setPoint(mesh.m_ptr, entity, node, coords)
  end

  return nothing
end

"""
  Retrieve the xyz coordinates of a node

  **Inputs**

   * mesh
   * entity: the MeshEntity
   * node: the local node number (0 based)

  **Inputs/Outputs**

   * coords: vector of length 3 to be overwritten with coordinates
"""
function getCoords(mesh::PumiMesh, entity::Ptr{Void}, node::Integer,
                   coords::AbstractArray{Float64})

  apf.getPoint(mesh.m_ptr, entity, node, coords)

  return nothing
end

"""
  Internal function to snap coordinates to the geometry.  Only call this
  function if the geometric model supports evaluating coordinates.

  **Inputs**

   * mesh
   * entity: the MeshEntity
   * snap: if the xyz coordinates should be snapped
   * coords: vector of length 3 containing xyz coordinates

  **Inputs/Outputs**

   * newx: new xyz coordinates, snapped to the geometry if `snap` is true,
           same as `coords` if false
   * xi: the CAD parametric coordinates

  **Outputs**

   * has_xi: Bool, if true, the model entity that `entity` is classified on
             has parametric coordinates.  If false, the value of `xi` on exit
             is undefined.
"""
function getSnappedCoords(mesh::PumiMesh, entity::Ptr{Void}, snap::Bool,
                          coords::AbstractVector, newx::AbstractVector,
                          xi::AbstractVector)

  g = apf.getModel(mesh.m_ptr)
  me = apf.toModel(mesh.m_ptr, entity)
  me_dim = apf.getModelType(mesh.m_ptr, me)
  has_xi = me_dim != 3  # only snap if the parametric coordinates exist

  if has_xi
    if me_dim == 0  # Simmetrix doesn't have closest_point for vertices
      fill!(xi, 0)
      gmi.geval(g, me, xi, newx)
    else
      gmi.closest_point(g, me, coords, newx, xi)
    end
  end

  if !snap || !has_xi
    for k=1:mesh.dim
      newx[k] = coords[k]
    end
  end

  return has_xi
end

"""
  This must be called after all calls to update_coords are complete. It
  update all fields of the mesh that are derived from the coordinates
  (dxidx, jac, node coordinate etc.), given that update_coords does not
  change the mesh topology.

  The verify keyword argument determines whether or not to run the Pumi
  verifier on the updated mesh.  It can be expensive, so it is recommended
  to only run it on small meshes.

  The `write_vis` keyword controls whether a vtk file, named `pre_commit` is
  written just before the metrics are recalculated (if an element is turned
  inside out, the metric recalculation is where the error gets thrown).
"""
function commit_coords(mesh::PumiMesh, sbp, opts; verify=true, write_vis=false)
# users must call this function when they have finished updating the coordinates
# the verify kwarg determines if the Pumi verifier on the new mesh
# that should check for negative volumes

  apf.acceptChanges(mesh.m_ptr)
  if verify
    apf.Verify(mesh.m_ptr)
    # TODO; call our verification code too
  end

  if write_vis
    writeVisFiles(mesh, "pre_commit")
  end

  getAllCoordinatesAndMetrics(mesh, sbp, opts, verify=verify)

end


module Nodes
import Base.&, Base.isequal, Base.>=, Base.>, Base.<=, Base.<, Base.show
import ..Patterns.depsof, ..Patterns.subs, ..Patterns.intension
using ..Meta, ..Intern, ..Patterns
export argsym, argnode, refnode, lengthnode, egalpred, typepred
export julia_signature_of

# ---- nodes ------------------------------------------------------------------

type Arg <: Node{Any}; end
const argnode = Arg()
const argsym  = gensym("arg")

@interned type Ref    <: Node{Any};  arg::Node; index::Int;  end
@interned type Length <: Node{Any};  arg::Node;              end
@interned type Egal   <: Predicate;  arg::Node; eq::Node;    end
@interned type Isa    <: Predicate;  arg::Node; typ;         end

refnode(   arg::Node, index::Int) = Ref(arg, index)
lengthnode(arg::Node)             = Length(arg)
egalpred(  arg::Node, value)      = Egal(arg, Atom(value))

typepred(arg::Node, ::Type{Any})     = always
typepred(arg::Node, ::Type{Union{}}) = never
typepred(arg::Node, typ)             = Isa(arg, typ)

indof(node::Ref) = node.index
eqof(node::Egal) = node.eq
Tof(node::Isa)   = node.typ

subs(d::Dict, node::Union{Arg, Atom}) = node
subs(d::Dict, node::Ref)    = Ref(   d[node.arg], node.index)
subs(d::Dict, node::Length) = Length(d[node.arg])
subs(d::Dict, node::Egal)   = Egal(  d[node.arg], d[node.eq])
subs(d::Dict, node::Isa)    = Isa(   d[node.arg], node.typ)

encode(v::Arg)    = argsym
encode(v::Atom)   = quot(v.value)
encode(v::Ref)    = :( $(resultof(v.arg))[$(v.index)] )
encode(v::Length) = :(length($(resultof(v.arg))))
encode(g::Egal)   = :(is( $(resultof(g.arg)), $(resultof(g.eq))))
encode(g::Isa)    = :(isa($(resultof(g.arg)), $(quot(g.typ))))

(&)(node1::Atom{Bool}, node2::Atom{Bool}) = Atom(node1.value & node2.value)
(&)(node1::Predicate,  node2::Atom{Bool}) = node2.value ? node1 : never
(&)(node1::Atom{Bool}, node2::Predicate) = node2 & node1

# to work around that type equivalence is weaker than isequal
# works together with >=(::Intension, ::Intension)
mytintersect(S,T) = (T <: S) ? T : typeintersect(S,T)

samearg(n::Node, m::Node) = @assert n.arg===m.arg
(&)(e::Egal, f::Egal) = (samearg(e, f); e.eq === f.eq ?          e : never)
(&)(e::Egal, t::Isa)  = (samearg(e, t); isa(e.eq.value, t.typ) ? e : never)
(&)(t::Isa,  e::Egal) = e & t
(&)(s::Isa, t::Isa) = (samearg(s,t); typepred(s.arg,mytintersect(s.typ,t.typ)))

depsof(node::Arg) = []
depsof(node::Egal) = Node[node.arg, node.eq]
depsof(node::Union{Ref, Length, Isa}) = [node.arg]

function depsof(i::Intension, n::Ref)
    Node[n.arg, Guard(i.factors[n.arg]), Guard(i.factors[Length(n.arg)])]
end
depsof(i::Intension, node::Length) = Node[node.arg, Guard(i.factors[node.arg])]

function intension(Ts::Tuple)
    intension(typepred(argnode, Tuple),
              egalpred(lengthnode(argnode), length(Ts)),
              Any[typepred(refnode(argnode, k),T) for (k,T) in enumerate(Ts)]...)
end

get_type(g::Isa)  = Tof(g)
get_type(g::Egal) = typeof(eqof(g).value)
function get_type(intent::Intension, node::Node)
    haskey(intent.factors, node) ? get_type(intent.factors[node]) : Any
end
function julia_signature_of(intent::Intension)
    if !haskey(intent.factors, argnode);  return nothing;  end
    garg::Isa = intent.factors[argnode]
    @assert Tof(garg) <: Tuple
    glen::Egal = intent.factors[Length(argnode)]
    nargs = eqof(glen).value
    tuple(Any[get_type(intent, refnode(argnode, k)) for k=1:nargs]...)
end
julia_signature_of(p::Pattern) = julia_signature_of(p.intent)

function show(io::IO, p::Pattern)
    if p.intent === naught; print(io, "::Union{}"); return; end

    users = Dict{Node,Set}()
    for (name,arg) in p.bindings; adduser(users, name, arg); end
    for g in predsof(p.intent);  adduser(users, g);         end

    showpat(io, users, argnode)
end

adduser(users::Dict, u::Node) = for d in depsof(u); adduser(users, u, d); end
function adduser(users::Dict, user, dep::Node)
    if !haskey(users, dep); adduser(users, dep); users[dep] = Set() end
    push!(users[dep], user)
end

const typeorder = Dict(Symbol=>1, Length=>2, Egal=>3, Isa=>3, Ref=>4)
cmp(x,y) = typeorder[typeof(x)] < typeorder[typeof(y)]
cmp(x::Symbol, y::Symbol) = string(x) < string(y)
cmp(x::Ref,    y::Ref)    = x.index   < y.index

function showpat(io::IO, users::Dict, node::Node)
    if !haskey(users, node); print(io, "::Any"); return end

    # printing order: Symbol, Ref, Egal, Isa
    us = sort(collect(users[node]), lt=cmp)
    k, n = 1, length(us)
    printed = false
    typ = Any
    while k <= n
        u = us[k]
        if printed && !(isa(u, Isa) || isa(u, Length)); print(io, '~'); end
        if isa(u, Symbol); print(io, u); printed = true
        elseif isa(u, Egal); print(io, eqof(u).value); printed = true
        elseif isa(u, Isa)
            typ = Tof(u)
            if k+1 <= n && isa(us[k+1], Ref)
                if (Tof(u) == Tuple) || (Tof(u) == Vector)
                    k += 1
                    continue
                end
            end
            print(io, "::", Tof(u)); printed = true
        elseif isa(u, Length) # todo: do something
        elseif isa(u, Ref)
            print(io, typ <: Vector ? '[' : '(')
            i = 0
            while k <= n && isa(us[k], Ref)
                if i==0; i=1; end
                u = us[k]
                @assert i <= indof(u)
                while i < indof(u); print(io, ", "); i += 1; end
                i = indof(u)
                showpat(io, users, u)
                k += 1
            end
            if i == 1; print(io, ','); end
            print(io, typ <: Vector ? ']' : ')')
            printed = true
        else
            error("unknown node type")
        end
        k += 1
    end
end

end # module


using BPPLib
using Test

@testset "BPPLoadSymbol" begin
    data = loadBPP(:PM_u010_01)
    @test length(data.weights) == 10
    @test data.capacity == 150
    @test data.lb == 0
    @test data.ub == typemax(Int64)
    @test data.name == "PM_u010_01"
end

@testset "BPPErrorSymbol" begin
    data = loadBPP(:PM_u010_0)
    @test data === nothing
end

@testset "BPPLoadString" begin
    data = loadBPP(joinpath(pkgdir(BPPLib), "test/data/bpp/Falkenauer_u120_00.txt"))
    @test length(data.weights) == 120
    @test data.capacity == 150
    @test data.lb == 48
    @test data.ub == 48
    @test data.name == "Falkenauer_u120_00"
end

@testset "BPPErrorString" begin
    data = loadBPP(joinpath(pkgdir(BPPLib), "not a file"))
    @test data === nothing
end

@testset "BPPShow" begin
    data = loadBPP(:PM_u010_01)
    @test_nowarn println(data)
    data = loadBPP(joinpath(pkgdir(BPPLib), "test/data/bpp/Falkenauer_u120_00.txt"))
    @test_nowarn println(data)
end

@testset "CSPLoadSymbol" begin
    data = loadCSP(:PM_u010_01)
    @test length(data.weights) == 10
    @test length(data.demands) == 10
    @test data.capacity == 150
    @test data.lb == 0
    @test data.ub == typemax(Int64)
    @test data.name == "PM_u010_01"
end

@testset "CSPErrorSymbol" begin
    data = loadCSP(:PM_u010_0)
    @test data === nothing
end

@testset "CSPLoadString" begin
    data = loadCSP(joinpath(pkgdir(BPPLib), "test/data/csp/Falkenauer_u120_00.txt"))
    @test length(data.weights) == 58
    @test length(data.demands) == 58
    @test data.capacity == 150
    @test data.lb == 48
    @test data.ub == 48
    @test data.name == "Falkenauer_u120_00"
end

@testset "CSPErrorString" begin
    data = loadCSP(joinpath(pkgdir(BPPLib), "not a file"))
    @test data === nothing
end

@testset "CSPShow" begin
    data = loadCSP(:PM_u010_01)
    @test_nowarn println(data)
    data = loadCSP(joinpath(pkgdir(BPPLib), "test/data/csp/Falkenauer_u120_00.txt"))
    @test_nowarn println(data)
end

@testset "toBPP" begin
    data = loadCSP(:PM_u010_01)
    data2 = toBPP(data)
    @test length(data2.weights) == 10
    @test data2.capacity == 150
    @test data2.name == "PM_u010_01"
end

@testset "toCSP" begin
    data = loadBPP(:PM_u010_01)
    data2 = toCSP(data)
    @test length(data2.weights) == 10
    @test length(data2.demands) == 10
    @test data2.capacity == 150
    @test data2.name == "PM_u010_01"
end


__precompile__()

module SteadyStateDiffEq

using Reexport
@reexport using DiffEqBase

using NLsolve, DiffEqCallbacks
using LinearAlgebra
using Compat

include("algorithms.jl")
include("solve.jl")

export SSRootfind, DynamicSS

end # module


module Glimmer

using Blink

using LinearAlgebra
import MeshCat
using CoordinateTransformations
using Rotations
import GeometryBasics
using Colors: RGBA, RGB
import Colors
import ColorSchemes
import FileIO
import MeshIO

import Tables
import JSON, JSONTables
import Dates
using OrderedCollections

using StaticArrays
import UUIDs
import Base64

using Reexport

include("Misc.jl")
include("GridUtils.jl")
include("Material.jl")
include("SceneObject.jl")
include("Scene.jl")

include("JSInterface/JuliaJSBridge.jl")
include("UI/UIVariables.jl")
include("UI/UIControls.jl")
include("UI/WGL.jl")

include("UI/FlexUI.jl")

# reexport all the public stuff from UIControls and UIVariables

@reexport using ..UIControls
@reexport using ..UIVariables
@reexport using ..WGL
@reexport using ..GridUtils

#------------------------------------------------------------------------------
# Exports
#------------------------------------------------------------------------------
export  Scene,
        set_Y_up!,
        set_Z_up!,
        grid!,
        axes!,
        # root, root!,
        parent, parent!,
        material, material!,
        Material,
        transform, lookAt,
        rotation,
        local_tr, local_tr!,
        cameraTransform!, cameraPlanes!,
        EmptySceneObject,
        Box,
        Mesh,
        Arrow,
        Axes,
        LineSegments,
        PointCloud,
        dataPath,
        unitX3,
        unitY3,
        unitZ3,
        zero3,
        cameraTransform!,
        url,
        clear,
        send!,
        root,
        root_transform,
        win,
        vis

export  BasicValidation, on, Variable

export  Container,
        Slider,
        Button,
        MeshCatViewer,
        Label,
        Image,
        PanZoom,
        Field,
        ButtonToggle,
        RadioGroup,
        CheckBox,
        ExpansionPanel,
        Accordion,
        Tabs,
        Tab,
        Divider,
        Card,
        Markdown,

        VContainer,
        HContainer,
        HContainerSpace,
        H1Label,
        H2Label,
        H3Label,
        H4Label

# export the common functions of a UI implementation
export addVariable!, prop, prop!, controls, controls!, renderFunction, renderFunction!

end # module Glimmer


using Delaunay
using Test

@testset "Delaunay.jl" begin
    # Write your tests here.
end


#=
  Package: FishEBM.jl
  File: utilities.jl
  Devin Rose
  Utilities for use of FishEBM, plots, memory allocation, etc. during runtime.
  Created: March 2016
=#

"""
  Description: Allocates enough memory for a population density matrix and
    sets all elements in the array to zero.

  Returns: Array(Int64, size of habitat)

  Last update: May 2016
"""
function initPopulationDensity(enviro_a::EnvironmentAssumptions)
  popDensity = Array(Int64, size(enviro_a.habitat)[1], size(enviro_a.habitat)[2])

  for i = 1:size(enviro_a.habitat)[1]
    for j = 1:size(enviro_a.habitat)[2]
      popDensity[i, j] = 0
    end
  end

  return popDensity
end

"""
  Description: Updates a population density matrix in the simulate function.
    The population density is used for visualizing the movement of fish during
    the simulation to monitor the behaviour.

  Precondition: A population density matrix is already allocated prior to
    calling this function.

  Returns: operates directly on pop_density

  Last update: June 2016
"""
function updatePopulationDensity!(agent_db::Vector, pop_density::Array, week_total::Int64)
  totalPop = 0

  # get adult age vector
  ageVec = fill(0, length(agent_db[1].alive))
  for j = 1:length(ageVec)
    ageVec[j] = getAge(week_total, agent_db[1].weekNum[j])
  end

  for k = 1:length(agent_db)
    pop_density[agent_db[k].locationID] = 1

    if isEmpty(agent_db[k]) == false
      for m = 1:length(ageVec)
        if ageVec[m] != 0
          pop_density[agent_db[k].locationID] += agent_db[k].alive[m]
        end
        totalPop += agent_db[k].alive[m]
      end
    end
  end

  return totalPop
end


# Load data to use for tests
path = dirname(@__FILE__)
fred = CSV.read("$path/../reference/fred_160812.csv")

# Specify vintage and dates
custom_settings = Dict{Symbol, Setting}(
    :data_vintage             => Setting(:data_vintage, "160812"),
    :cond_vintage             => Setting(:cond_vintage, "160812"),
    :cond_id                  => Setting(:cond_id, 0),
    :use_population_forecast  => Setting(:use_population_forecast, true),
    :date_forecast_start      => Setting(:date_forecast_start, DSGE.quartertodate("2016-Q3")),
    :date_conditional_end     => Setting(:date_conditional_end, DSGE.quartertodate("2016-Q3")),
    :n_mon_anticipated_shocks => Setting(:n_mon_anticipated_shocks, 6))
m = Model990(testing = true, custom_settings = custom_settings)
m <= Setting(:rate_expectations_source, :ois)

# Test full, semi, and none conditional data
exp_data, exp_cond_data, exp_semicond_data =
    JLD2.jldopen("$path/../reference/load_data_out.jld2", "r") do file
        read(file, "data"), read(file, "cond_data"), read(file, "semi_cond_data")
    end
levels, semi_levels, full_levels =
    JLD2.jldopen("$path/../reference/transform_data_inputs.jld2", "r") do file
        read(file, "none"), read(file, "semi"), read(file, "full")
end
exp_data_rev_transforms =
    JLD2.jldopen("$path/../reference/transform_data_out.jld2", "r") do file
        read(file, "rev_transform")
    end
exp_hp_pop_hist, exp_hp_pop_forecast =
    JLD2.jldopen("$path/../reference/hp_data_out.jld2", "r") do file
        read(file, "exp_hp_pop_hist"), read(file, "exp_hp_pop_forecast")
    end
pop_forecast = DataFrame(CNP16OV = cumsum(fred[!,:CNP16OV][end] .* (ones(8) .* 1.03)))
pop_forecast[!,:date] = DSGE.get_quarter_ends(DSGE.next_quarter(fred[!,:date][end]), fred[!,:date][end] + Year(2))

@testset "Check helper functions for transform_data" begin
    @test !isempty(collect_data_transforms(m))
    @test collect_data_transforms(m, direction = :rev) == exp_data_rev_transforms
    hp_hist, hp_forecast = DSGE.transform_population_data(fred, pop_forecast, :CNP16OV,
                                                     pad_forecast_start = true)
    @test hp_hist[!,:filtered_population_recorded] == exp_hp_pop_hist
    @test hp_forecast[!,:filtered_population_forecast] == exp_hp_pop_forecast
    hp_hist, hp_forecast = DSGE.transform_population_data(fred, pop_forecast, :CNP16OV)
    @test hp_hist[!,:filtered_population_recorded] == exp_hp_pop_hist
    @test hp_forecast[!,:filtered_population_forecast] == exp_hp_pop_forecast

    hist, forecast = DSGE.transform_population_data(fred, pop_forecast, :CNP16OV,
                                    pad_forecast_start = true, use_hpfilter = false)
    @test @test_matrix_approx_eq hist[!,:dlpopulation_recorded] difflog(Float64.(fred[!,:CNP16OV]))
    @test @test_matrix_approx_eq forecast[!,:dlpopulation_forecast] difflog(Float64.(pop_forecast[!,:CNP16OV]))
    hist, forecast = DSGE.transform_population_data(fred, pop_forecast, :CNP16OV,
                                    use_hpfilter = false)
    @test @test_matrix_approx_eq hist[!,:dlpopulation_recorded] difflog(Float64.(fred[!,:CNP16OV]))
    @test @test_matrix_approx_eq forecast[!,:dlpopulation_forecast] difflog(Float64.(pop_forecast[!,:CNP16OV]))
end

@testset "Test transform_data for different conditional data" begin
    @info "The following warnings are expected."
    df = transform_data(m, levels; cond_type = :none, verbose = :none)
    start_date = date_presample_start(m)
    end_date = date_mainsample_end(m)
    df = df[start_date .<= df[!,:date] .<= end_date, :]
    DSGE.missing_cond_vars!(m, df, cond_type = :none, check_empty_columns = false)
    @test @test_matrix_approx_eq df_to_matrix(m,df) exp_data
    df = transform_data(m, semi_levels; cond_type = :semi, verbose = :none)
    end_date = date_conditional_end(m)
    df = df[start_date .<= df[!,:date] .<= end_date, :]
    DSGE.missing_cond_vars!(m, df, cond_type = :semi, check_empty_columns = false)
    @test @test_matrix_approx_eq df_to_matrix(m,df, cond_type = :semi) exp_semicond_data
    df = transform_data(m, full_levels; cond_type = :full, verbose = :none)
    end_date = date_conditional_end(m)
    df = df[start_date .<= df[!,:date] .<= end_date, :]
    DSGE.missing_cond_vars!(m, df, cond_type = :full, check_empty_columns = false)
    @test @test_matrix_approx_eq df_to_matrix(m,df, cond_type = :full) exp_cond_data
end

nothing


export Diag

# Diag can have either Vector storage, in which case
# it is a general Diag tensor, or scalar storage,
# in which case the diagonal has a uniform value
struct Diag{T} <: TensorStorage
  data::T
  Diag{T}(data) where {T} = new{T}(data)
end

const NonuniformDiag{T} = Diag{T} where {T<:AbstractVector}
const UniformDiag{T} = Diag{T} where {T<:Number}

Base.@propagate_inbounds Base.getindex(D::NonuniformDiag,i::Int)= data(D)[i]
Base.getindex(D::UniformDiag,i::Int) = data(D)

Base.@propagate_inbounds Base.setindex!(D::Diag,val,i::Int)= (data(D)[i] = val)
Base.setindex!(D::UniformDiag,val,i::Int)= error("Cannot set elements of a uniform Diag storage")

Base.fill!(D::Diag,v) = fill!(data(D),v)

# convert to complex
# TODO: this could be a generic TensorStorage function
Base.complex(D::Diag{T}) where {T} = Diag{complex(T)}(complex(data(D)))

Base.copy(D::Diag{T}) where {T} = Diag{T}(copy(data(D)))

Base.eltype(::Diag{T}) where {T} = eltype(T)
Base.eltype(::Type{<:Diag{T}}) where {T} = eltype(T)

# Deal with uniform Diag conversion
Base.convert(::Type{<:Diag{T}},D::Diag) where {T} = Diag{T}(data(D))

Base.similar(D::Diag{T}) where {T} = Diag{T}(similar(data(D)))

# TODO: write in terms of ::Int, not inds
Base.similar(D::Diag{T},inds) where {T} = Diag{T}(similar(data(D),minimum(dims(inds))))
Base.similar(D::Type{<:NonuniformDiag{T}},inds) where {T} = Diag{T}(similar(T,length(inds)==0 ? 1 : minimum(dims(inds))))

Base.similar(D::UniformDiag{T}) where {T<:Number} = Diag{T}(zero(T))
Base.similar(::Type{<:UniformDiag{T}},inds) where {T<:Number} = Diag{T}(zero(T))

# TODO: make this work for other storage besides Vector
Base.zeros(::Type{Diag{T}},dim::Int64) where
  {T<:AbstractVector} = Diag{T}(zeros(eltype(T),dim))
Base.zeros(::Type{Diag{T}},dim::Int64) where
  {T<:Number} = Diag{T}(zero(T))

function Base.:*(D::Diag{<:Vector{El}},x::S) where {El<:Number,S<:Number}
  return Diag{Vector{promote_type(El,S)}}(x*data(D))
end

function Base.:*(D::UniformDiag{<:El},x::S) where {El<:Number,S<:Number}
  return Diag{promote_type(El,S)}(x*data(D))
end

Base.:*(x::Number,D::Diag) = D*x

#
# Type promotions involving Diag
# Useful for knowing how conversions should work when adding and contracting
#

Base.promote_rule(::Type{<:UniformDiag{T1}},::Type{<:UniformDiag{T2}}) where
  {T1<:Number,T2<:Number} = Diag{promote_type(T1,T2)}

Base.promote_rule(::Type{<:NonuniformDiag{T1}},::Type{<:NonuniformDiag{T2}}) where
  {T1<:AbstractVector,T2<:AbstractVector} = Diag{promote_type(T1,T2)}

# TODO: how do we make this work more generally for T2<:AbstractVector{S2}?
# Make a similar_type(AbstractVector{S2},T1) -> AbstractVector{T1} function?
Base.promote_rule(::Type{<:UniformDiag{T1}},::Type{<:NonuniformDiag{T2}}) where
  {T1<:Number,T2<:Vector{S2}} where S2 = Diag{Vector{promote_type(T1,S2)}}

Base.promote_rule(::Type{<:Dense{T1}},::Type{<:Diag{T2}}) where
  {T1,T2} = Dense{promote_type(T1,eltype(T2))}

# Convert a Diag storage type to the closest Dense storage type
dense(::Type{<:Diag{T}}) where {T} = Dense{eltype(T)}

const DiagTensor{ElT,N,StoreT,IndsT} = Tensor{ElT,N,StoreT,IndsT} where {StoreT<:Diag}
const NonuniformDiagTensor{ElT,N,StoreT,IndsT} = Tensor{ElT,N,StoreT,IndsT} where
                                               {StoreT<:NonuniformDiag}
const UniformDiagTensor{ElT,N,StoreT,IndsT} = Tensor{ElT,N,StoreT,IndsT} where
                                               {StoreT<:UniformDiag}

Base.IndexStyle(::Type{<:DiagTensor}) = IndexCartesian()

# TODO: this needs to be better (promote element type, check order compatibility,
# etc.
function Base.convert(::Type{<:Tensor{ElT,N,<:Dense}}, T::DiagTensor{ElT,N}) where {ElT,N}
  return dense(T)
end

# These are rules for determining the output of a pairwise contraction of Tensors
# (given the indices of the output tensors)
function contraction_output_type(TensorT1::Type{<:DiagTensor},
                                 TensorT2::Type{<:DenseTensor},
                                 indsR)
  return similar_type(promote_type(TensorT1,TensorT2),indsR)
end
contraction_output_type(TensorT1::Type{<:DenseTensor},
                        TensorT2::Type{<:DiagTensor},
                        indsR) = contraction_output_type(TensorT2,TensorT1,indsR)

# This performs the logic that DiagTensor*DiagTensor -> DiagTensor if it is not an outer
# product but -> DenseTensor if it is
# TODO: if the tensors are both order 2 (or less), or if there is an Index replacement,
# then they remain diagonal. Should we limit DiagTensor*DiagTensor to cases that
# result in a DiagTensor, for efficiency and type stability? What about a general
# SparseTensor result?
function contraction_output_type(TensorT1::Type{<:DiagTensor{<:Number,N1}},
                                 TensorT2::Type{<:DiagTensor{<:Number,N2}},
                                 indsR) where {N1,N2}
  if ValLength(indsR)===Val(N1+N2)
    # Turn into is_outer(inds1,inds2,indsR) function?
    # How does type inference work with arithmatic of compile time values?
    return similar_type(dense(promote_type(TensorT1,TensorT2)),indsR)
  end
  return similar_type(promote_type(TensorT1,TensorT2),indsR)
end

# TODO: move to tensor.jl?
zero_contraction_output(TensorT1::Type{<:Tensor},TensorT2::Type{<:Tensor},indsR) = zeros(contraction_output_type(TensorT1,TensorT2,indsR),indsR)

function contraction_output(TensorT1::Type{<:DiagTensor},
                            TensorT2::Type{<:Tensor},
                            indsR)
  return zero_contraction_output(TensorT1,TensorT2,indsR)
end
contraction_output(TensorT1::Type{<:Tensor},TensorT2::Type{<:DiagTensor},indsR) = contraction_output(TensorT2,TensorT1,indsR)

function contraction_output(TensorT1::Type{<:DiagTensor},
                            TensorT2::Type{<:DiagTensor},
                            indsR)
  return zero_contraction_output(TensorT1,TensorT2,indsR)
end

function array(T::DiagTensor{ElT,N}) where {ElT,N}
  return array(dense(T))
end
matrix(T::DiagTensor{<:Number,2}) = array(T)
vector(T::DiagTensor{<:Number,1}) = array(T)

diag_length(T::DiagTensor) = minimum(dims(T))
diag_length(T::DiagTensor{<:Number,0}) = 1

getdiag(T::DiagTensor,ind::Int) = store(T)[ind]

setdiag!(T::DiagTensor,val,ind::Int) = (store(T)[ind] = val)

setdiag(T::DiagTensor,val::ElR,ind::Int) where {ElR} = Tensor(Diag{ElR}(val),inds(T))

Base.@propagate_inbounds function Base.getindex(T::DiagTensor{ElT,N},inds::Vararg{Int,N}) where {ElT,N}
  if all(==(inds[1]),inds)
    return store(T)[inds[1]]
  else
    return zero(eltype(ElT))
  end
end
Base.@propagate_inbounds Base.getindex(T::DiagTensor{<:Number,1},ind::Int) = store(T)[ind]
Base.@propagate_inbounds Base.getindex(T::DiagTensor{<:Number,0}) = store(T)[1]

# Set diagonal elements
# Throw error for off-diagonal
Base.@propagate_inbounds function Base.setindex!(T::DiagTensor{<:Number,N},
                                            val,inds::Vararg{Int,N}) where {N}
  all(==(inds[1]),inds) || error("Cannot set off-diagonal element of Diag storage")
  return store(T)[inds[1]] = val
end
Base.@propagate_inbounds Base.setindex!(T::DiagTensor{<:Number,1},val,ind::Int) = ( store(T)[ind] = val )
Base.@propagate_inbounds Base.setindex!(T::DiagTensor{<:Number,0},val) = ( store(T)[1] = val )

function Base.setindex!(T::UniformDiagTensor{<:Number,N},val,inds::Vararg{Int,N}) where {N}
  error("Cannot set elements of a uniform Diag storage")
end

# TODO: make a fill!! that works for uniform and non-uniform
Base.fill!(T::DiagTensor,v) = fill!(store(T),v)

function dense(::Type{<:Tensor{ElT,N,StoreT,IndsT}}) where {ElT,N,
                                                            StoreT<:Diag,IndsT}
  return Tensor{ElT,N,dense(StoreT),IndsT}
end

# convert to Dense
function dense(T::TensorT) where {TensorT<:DiagTensor}
  R = zeros(dense(TensorT),inds(T))
  for i = 1:diag_length(T)
    setdiag!(R,getdiag(T,i),i)
  end
  return R
end

function outer!(R::DenseTensor{<:Number,NR},
                T1::DiagTensor{<:Number,N1},
                T2::DiagTensor{<:Number,N2}) where {NR,N1,N2}
  for i1 = 1:diag_length(T1), i2 = 1:diag_length(T2)
    indsR = CartesianIndex{NR}(ntuple(r -> r ≤ N1 ? i1 : i2, Val(NR)))
    R[indsR] = getdiag(T1,i1)*getdiag(T2,i2)
  end
  return R
end

# TODO: write an optimized version of this?
function outer!(R::DenseTensor{ElR},
                T1::DenseTensor,
                T2::DiagTensor) where {ElR}
  R .= zero(ElR)
  outer!(R,T1,dense(T2))
  return R
end

function outer!(R::DenseTensor{ElR},
                T1::DiagTensor,
                T2::DenseTensor) where {ElR}
  R .= zero(ElR)
  outer!(R,dense(T1),T2)
  return R
end

# Right an in-place version
function outer(T1::DiagTensor{ElT1,N1},
               T2::DiagTensor{ElT2,N2}) where {ElT1,ElT2,N1,N2}
  indsR = unioninds(inds(T1),inds(T2))
  R = Tensor(Dense{promote_type(ElT1,ElT2)}(zeros(dim(indsR))),indsR)
  outer!(R,T1,T2)
  return R
end

function Base.permutedims!(R::DiagTensor{<:Number,N},
                           T::DiagTensor{<:Number,N},
                           perm::NTuple{N,Int},f::Function=(r,t)->t) where {N}
  # TODO: check that inds(R)==permute(inds(T),perm)?
  for i=1:diag_length(R)
    @inbounds setdiag!(R,f(getdiag(R,i),getdiag(T,i)),i)
  end
  return R
end

function Base.permutedims(T::DiagTensor{<:Number,N},
                          perm::NTuple{N,Int},f::Function=identity) where {N}
  R = similar(T,permute(inds(T),perm))
  permutedims!(R,T,perm,f)
  return R
end

function Base.permutedims(T::UniformDiagTensor{ElT,N},
                          perm::NTuple{N,Int},
                          f::Function=identity) where {ElR,ElT,N}
  R = Tensor(Diag{promote_type(ElR,ElT)}(f(getdiag(T,1))),
                                         permute(inds(T),perm))
  return R
end

# Version that may overwrite in-place or may return the result
function permutedims!!(R::NonuniformDiagTensor{<:Number,N},
                       T::NonuniformDiagTensor{<:Number,N},
                       perm::NTuple{N,Int},
                       f::Function=(r,t)->t) where {N}
  permutedims!(R,T,perm,f)
  return R
end

function permutedims!!(R::UniformDiagTensor{ElR,N},
                       T::UniformDiagTensor{ElT,N},
                       perm::NTuple{N,Int},
                       f::Function=(r,t)->t) where {ElR,ElT,N}
  R = Tensor(Diag{promote_type(ElR,ElT)}(f(getdiag(R,1),getdiag(T,1))),inds(R))
  return R
end

function Base.permutedims!(R::DenseTensor{ElR,N},
                           T::DiagTensor{ElT,N},
                           perm::NTuple{N,Int},
                           f::Function = (r,t)->t) where {ElR,ElT,N}
  for i = 1:diag_length(T)
    @inbounds setdiag!(R,f(getdiag(R,i),getdiag(T,i)),i)
  end
  return R
end

function permutedims!!(R::DenseTensor{ElR,N},
                       T::DiagTensor{ElT,N},
                       perm::NTuple{N,Int},f::Function=(r,t)->t) where {ElR,ElT,N}
  permutedims!(R,T,perm,f)
  return R
end

function _contract!!(R::UniformDiagTensor{ElR,NR},labelsR,
                     T1::UniformDiagTensor{<:Number,N1},labelsT1,
                     T2::UniformDiagTensor{<:Number,N2},labelsT2) where {ElR,NR,N1,N2}
  if NR==0  # If all indices of A and B are contracted
    # all indices are summed over, just add the product of the diagonal
    # elements of A and B
    R = setdiag(R,diag_length(T1)*getdiag(T1,1)*getdiag(T2,1),1)
  else
    # not all indices are summed over, set the diagonals of the result
    # to the product of the diagonals of A and B
    R = setdiag(R,getdiag(T1,1)*getdiag(T2,1),1)
  end
  return R
end

function _contract!(R::DiagTensor{ElR,NR},labelsR,
                    T1::DiagTensor{<:Number,N1},labelsT1,
                    T2::DiagTensor{<:Number,N2},labelsT2) where {ElR,NR,N1,N2}
  if NR==0  # If all indices of A and B are contracted
    # all indices are summed over, just add the product of the diagonal
    # elements of A and B
    Rdiag = zero(ElR)
    for i = 1:diag_length(T1)
      Rdiag += getdiag(T1,i)*getdiag(T2,i)
    end
    setdiag!(R,Rdiag,1)
  else
    min_dim = min(diag_length(T1),diag_length(T2))
    # not all indices are summed over, set the diagonals of the result
    # to the product of the diagonals of A and B
    for i = 1:min_dim
      setdiag!(R,getdiag(T1,i)*getdiag(T2,i),i)
    end
  end
  return R
end

function _contract!(C::DenseTensor{ElC,NC},Clabels,
                    A::DiagTensor{ElA,NA},Alabels,
                    B::DenseTensor{ElB,NB},Blabels) where {ElA,NA,
                                                           ElB,NB,
                                                           ElC,NC}
  if all(i -> i < 0, Blabels)
    # If all of B is contracted
    # TODO: can also check NC+NB==NA
    min_dim = minimum(dims(B))
    if length(Clabels) == 0
      # all indices are summed over, just add the product of the diagonal
      # elements of A and B
      for i = 1:min_dim
        setdiag!(C,getdiag(C,1)+getdiag(A,i)*getdiag(B,i),1)
      end
    else
      # not all indices are summed over, set the diagonals of the result
      # to the product of the diagonals of A and B
      # TODO: should we make this return a Diag storage?
      for i = 1:min_dim
        setdiag!(C,getdiag(A,i)*getdiag(B,i),i)
      end
    end
  else
    astarts = zeros(Int,length(Alabels))
    bstart = 0
    cstart = 0
    b_cstride = 0
    nbu = 0
    for ib = 1:length(Blabels)
      ia = findfirst(==(Blabels[ib]),Alabels)
      if !isnothing(ia)
        b_cstride += stride(B,ib)
        bstart += astarts[ia]*stride(B,ib)
      else
        nbu += 1
      end
    end

    c_cstride = 0
    for ic = 1:length(Clabels)
      ia = findfirst(==(Clabels[ic]),Alabels)
      if !isnothing(ia)
        c_cstride += stride(C,ic)
        cstart += astarts[ia]*stride(C,ic)
      end
    end

    # strides of the uncontracted dimensions of
    # B
    bustride = zeros(Int,nbu)
    custride = zeros(Int,nbu)
    # size of the uncontracted dimensions of
    # B, to be used in CartesianIndices
    busize = zeros(Int,nbu)
    n = 1
    for ib = 1:length(Blabels)
      if Blabels[ib] > 0
        bustride[n] = stride(B,ib)
        busize[n] = size(B,ib)
        ic = findfirst(==(Blabels[ib]),Clabels)
        custride[n] = stride(C,ic)
        n += 1
      end
    end

    boffset_orig = 1-sum(strides(B))
    coffset_orig = 1-sum(strides(C))
    cartesian_inds = CartesianIndices(Tuple(busize))
    for inds in cartesian_inds
      boffset = boffset_orig
      coffset = coffset_orig
      for i in 1:nbu
        ii = inds[i]
        boffset += ii*bustride[i]
        coffset += ii*custride[i]
      end
      for j in 1:diag_length(A)
        C[cstart+j*c_cstride+coffset] += getdiag(A,j)*
                                         B[bstart+j*b_cstride+boffset]
      end
    end
  end
end
_contract!(C::DenseTensor,Clabels,
           A::DenseTensor,Alabels,
           B::DiagTensor,Blabels) = _contract!(C,Clabels,
                                               B,Blabels,
                                               A,Alabels)


using DataDeps

register(DataDep(
  "statlog-project-german-credit",
  "http://archive.ics.uci.edu/ml/datasets/Statlog+Project",
  "http://archive.ics.uci.edu/ml/machine-learning-databases/statlog/german/german.data",
  "b21f3d81db8071257d5ff1deaeba1fd4303b62712e6fcc9715c7a86202cb5871",
  post_fetch_method=(path -> begin
    UCIData.process_dataset(path,
        target_index=21,
        feature_indices=1:20,
        categoric_indices=[1; 3:4; 6:7; 9:10; 12; 14:15; 17; 19:20],
        delim=' ',
    )
  end),
))


struct MultiFieldCellField{DS<:DomainStyle} <: CellField
  single_fields::Vector{<:CellField}
  trian::Triangulation
  domain_style::DS

  function MultiFieldCellField(single_fields::Vector{<:CellField})
    @assert length(single_fields) > 0
    f1 = first(single_fields)

    # Find a suitable domain style
    if any( map(i->DomainStyle(i)==ReferenceDomain(),single_fields) )
      domain_style = ReferenceDomain()
    else
      domain_style = PhysicalDomain()
    end

    trian = get_triangulation(f1)
    #@check all(map(i-> get_triangulation(i) === trian,single_fields))

    new{typeof(domain_style)}(single_fields,trian,domain_style)
  end
end

function CellData.get_data(f::MultiFieldCellField)
  s = """
  Function get_data is not implemented for MultiFieldCellField at this moment.
  You need to extract the individual fields and then evaluate them separatelly.

  If ever implement this, evaluating a `MultiFieldCellField` directly would provide,
  at each evaluation point, a tuple with the value of the different fields.
  """
  @notimplemented s
end

CellData.get_triangulation(f::MultiFieldCellField) = f.trian
CellData.DomainStyle(::Type{MultiFieldCellField{DS}}) where DS = DS()
num_fields(a::MultiFieldCellField) = length(a.single_fields)
Base.getindex(a::MultiFieldCellField,i::Integer) = a.single_fields[i]
Base.iterate(a::MultiFieldCellField)  = iterate(a.single_fields)
Base.iterate(a::MultiFieldCellField,state)  = iterate(a.single_fields,state)