module MultiAgentPathFinding

## Dependencies

using DataFrames
using DataGraphs
using DataStructures
using FillArrays
using Graphs
using LinearAlgebra
using OffsetArrays
using ProgressMeter
using PythonCall
using Random
using Requires
using Statistics
using StatsBase
using SparseArrays
using UnicodePlots

## Includes

include("mapf.jl")

include("utils/eval_sol.jl")
include("utils/conflicts.jl")
include("utils/reservation.jl")
include("utils/vectorize.jl")

include("paths/dijkstra.jl")
include("paths/temporal_astar.jl")
include("paths/cooperative_astar.jl")
include("paths/independent_shortest_paths.jl")

include("exact_methods/conflict_based_search.jl")

include("local_search/neighborhood.jl")
include("local_search/large_neighborhood_search.jl")
include("local_search/feasibility_search.jl")
include("local_search/permutation_search.jl")

include("learning/features_agents.jl")
include("learning/features_edges.jl")

include("datasets/flatland/constants.jl")
include("datasets/flatland/agent.jl")
include("datasets/flatland/graph.jl")
include("datasets/flatland/utils.jl")
include("datasets/flatland/mapf.jl")

include("datasets/benchmarks/read.jl")
include("datasets/benchmarks/graph.jl")
include("datasets/benchmarks/mapf.jl")

## Exports

export Path, Solution
export MAPF, nb_agents

export flowtime, max_time
export VectorPriorityQueue
export find_conflict, conflict_exists, count_conflicts
export is_feasible
export path_to_vec, solution_to_mat, solution_to_mat2

export my_dijkstra_shortest_paths
export temporal_astar
export independent_astar, independent_dijkstra, independent_topological_sort
export compute_reservation
export cooperative_astar!, cooperative_astar

export conflict_based_search

export local_search_permutations
export feasibility_search!, feasibility_search
export large_neighborhood_search, large_neighborhood_search!

export agents_embedding
export edges_embedding

export flatland_mapf

export read_map, read_scenario
export GridGraph, shortest_path_grid
export benchmark_mapf

## Conditional dependencies

function __init__()
    @require GLMakie = "e9467ef8-e4e7-5192-8a1a-b1aee30e663a" begin
        using .GLMakie
        include("flatland/plot.jl")
        export plot_flatland_graph, flatland_agent_coords
    end
    @require Images = "916415d5-f1e6-5110-898d-aaa5f9f070e0" begin
        using .Images
        include("benchmarks/plot.jl")
        export display_map
    end
    @require JuMP = "4076af6c-e467-56ae-b986-b466b2749572" begin
        @require SCIP = "82193955-e24f-5292-bf16-6f2c5261a85f" begin
            using .JuMP, .SCIP
            include("exact_methods/linear_program.jl")
            export solve_lp
        end
    end
end

end # module


using FactCheck

facts("MoralVector") do
    context("Create an Vector") do
        @fact MoralVector() --> not(nothing)
        @fact MoralVector(2) --> not(nothing)
        @fact MoralVector([1.,2,3]) --> not(nothing)
    end
end

facts("BasicSociety") do
    context("Create a BasicSociety") do
        @fact BasicSociety() --> not(nothing)
        # @fact BasicSociety(3) --> not(nothing)
        # @fact BasicSociety(rand(3,3)) --> not(nothing)
        # @fact BasicSociety(rand(3,2)) --> nothing
    end
end


using DataFrames, DataFramesMeta, CSV
using PGFPlots
using Statistics

# Function for results of Fig. A.6
function getfittedparamvalues_multipleruns(;
        filepath =  joinpath(dirname(pathof(SinergiafMRI_datafit)),"mcmc_rl_fit/projects/fmri/someresults/fits_multipleruns/"),
        filestr = "",
        )

    savepath = makehomesavepath("getfittedparamvalues")
    mkdir(savepath)

    prefix = split(filepath, "/")[end-1]
    runs = getsubfolders(filepath)
    algorithms = getsubfolders(joinpath(filepath, runs[1]))

    for i in algorithms
        dfresults = DataFrame()

        for irun in 1:length(runs)
            if isfile(joinpath(filepath, runs[irun], i, "maxLL_fit/MAP_params.csv"))
                dfalgo = CSV.read(joinpath(filepath, runs[irun], i, "maxLL_fit/MAP_params.csv"), header=false)
                dfresults[:, Symbol("run"*string(irun))] = dfalgo[:, :Column1]
            else
                println("File: $(joinpath(filepath, runs[irun], i, "maxLL_fit/MAP_params.csv")) does not exist!")
            end
        end
        CSV.write(joinpath(savepath, prefix*"_paramvalues_acrossruns_"*i*filestr*".csv"), dfresults,
                delim = " ");
    end
end


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

using Test, Printf

@testset "Printf" begin

@testset "%p" begin

    # pointers
    if Sys.WORD_SIZE == 64
        @test (Printf.@sprintf "%20p" 0) == "  0x0000000000000000"
        @test (Printf.@sprintf "%-20p" 0) == "0x0000000000000000  "
        @test (Printf.@sprintf "%20p" C_NULL) == "  0x0000000000000000"
        @test (@sprintf "%-20p" C_NULL) == "0x0000000000000000  "
    elseif Sys.WORD_SIZE == 32
        @test (Printf.@sprintf "%20p" 0) == "          0x00000000"
        @test (Printf.@sprintf "%-20p" 0) == "0x00000000          "
        @test (@sprintf "%20p" C_NULL) == "          0x00000000"
        @test (@sprintf "%-20p" C_NULL) == "0x00000000          "
    end

end

@testset "%a" begin

    # hex float
    @test (Printf.@sprintf "%a" 1.5) == "0x1.8p+0"
    @test (Printf.@sprintf "%a" 1.5f0) == "0x1.8p+0"
    @test (Printf.@sprintf "%a" big"1.5") == "0x1.8p+0"
    @test (Printf.@sprintf "%#.0a" 1.5) == "0x2.p+0"
    @test (Printf.@sprintf "%+30a" 1/3) == "         +0x1.5555555555555p-2"

    @test Printf.@sprintf("%a", 1.5) == "0x1.8p+0"
    @test Printf.@sprintf("%a", 3.14) == "0x1.91eb851eb851fp+1"
    @test Printf.@sprintf("%.0a", 3.14) == "0x2p+1"
    @test Printf.@sprintf("%.1a", 3.14) == "0x1.9p+1"
    @test Printf.@sprintf("%.2a", 3.14) == "0x1.92p+1"
    @test Printf.@sprintf("%#a", 3.14) == "0x1.91eb851eb851fp+1"
    @test Printf.@sprintf("%#.0a", 3.14) == "0x2.p+1"
    @test Printf.@sprintf("%#.1a", 3.14) == "0x1.9p+1"
    @test Printf.@sprintf("%#.2a", 3.14) == "0x1.92p+1"
    @test Printf.@sprintf("%.6a", 1.5) == "0x1.800000p+0"

end

@testset "%g" begin

    # %g
    for (val, res) in ((12345678., "1.23457e+07"),
                    (1234567.8, "1.23457e+06"),
                    (123456.78, "123457"),
                    (12345.678, "12345.7"),
                    (12340000.0, "1.234e+07"))
        @test (Printf.@sprintf("%.6g", val) == res)
    end
    for (val, res) in ((big"12345678.", "1.23457e+07"),
                    (big"1234567.8", "1.23457e+06"),
                    (big"123456.78", "123457"),
                    (big"12345.678", "12345.7"))
        @test (Printf.@sprintf("%.6g", val) == res)
    end
    for (fmt, val) in (("%10.5g", "     123.4"),
                    ("%+10.5g", "    +123.4"),
                    ("% 10.5g","     123.4"),
                    ("%#10.5g", "    123.40"),
                    ("%-10.5g", "123.4     "),
                    ("%-+10.5g", "+123.4    "),
                    ("%010.5g", "00000123.4")),
        num in (123.4, big"123.4")
        @test Printf.format(Printf.Format(fmt), num) == val
    end
    @test( Printf.@sprintf( "%10.5g", -123.4 ) == "    -123.4")
    @test( Printf.@sprintf( "%010.5g", -123.4 ) == "-0000123.4")
    @test( Printf.@sprintf( "%.6g", 12340000.0 ) == "1.234e+07")
    @test( Printf.@sprintf( "%#.6g", 12340000.0 ) == "1.23400e+07")
    @test( Printf.@sprintf( "%10.5g", big"-123.4" ) == "    -123.4")
    @test( Printf.@sprintf( "%010.5g", big"-123.4" ) == "-0000123.4")
    @test( Printf.@sprintf( "%.6g", big"12340000.0" ) == "1.234e+07")
    @test( Printf.@sprintf( "%#.6g", big"12340000.0") == "1.23400e+07")

    # %g regression gh #14331
    @test( Printf.@sprintf( "%.5g", 42) == "42")
    @test( Printf.@sprintf( "%#.2g", 42) == "42.")
    @test( Printf.@sprintf( "%#.5g", 42) == "42.000")

    @test Printf.@sprintf("%g", 0.00012) == "0.00012"
    @test Printf.@sprintf("%g", 0.000012) == "1.2e-05"
    @test Printf.@sprintf("%g", 123456.7) == "123457"
    @test Printf.@sprintf("%g", 1234567.8) == "1.23457e+06"

    # zeros
    @test Printf.@sprintf("%.15g", 0) == "0"
    @test Printf.@sprintf("%#.15g", 0) == "0.00000000000000"

end

@testset "%f" begin

    # Inf / NaN handling
    @test (Printf.@sprintf "%f" Inf) == "Inf"
    @test (Printf.@sprintf "%+f" Inf) == "+Inf"
    @test (Printf.@sprintf "% f" Inf) == " Inf"
    @test (Printf.@sprintf "% #f" Inf) == " Inf"
    @test (Printf.@sprintf "%f" -Inf) == "-Inf"
    @test (Printf.@sprintf "%+f" -Inf) == "-Inf"
    @test (Printf.@sprintf "%f" NaN) == "NaN"
    @test (Printf.@sprintf "%+f" NaN) == "NaN"
    @test (Printf.@sprintf "% f" NaN) == "NaN"
    @test (Printf.@sprintf "% #f" NaN) == "NaN"
    @test (Printf.@sprintf "%e" big"Inf") == "Inf"
    @test (Printf.@sprintf "%e" big"NaN") == "NaN"

    @test (Printf.@sprintf "%.0f" 3e142) == "29999999999999997463140672961703247153805615792184250659629251954072073858354858644285983761764971823910371920726635399393477049701891710124032"

    @test Printf.@sprintf("%f", 1.234) == "1.234000"
    @test Printf.@sprintf("%F", 1.234) == "1.234000"
    @test Printf.@sprintf("%+f", 1.234) == "+1.234000"
    @test Printf.@sprintf("% f", 1.234) == " 1.234000"
    @test Printf.@sprintf("%f", -1.234) == "-1.234000"
    @test Printf.@sprintf("%+f", -1.234) == "-1.234000"
    @test Printf.@sprintf("% f", -1.234) == "-1.234000"
    @test Printf.@sprintf("%#f", 1.234) == "1.234000"
    @test Printf.@sprintf("%.2f", 1.234) == "1.23"
    @test Printf.@sprintf("%.2f", 1.235) == "1.24"
    @test Printf.@sprintf("%.2f", 0.235) == "0.23"
    @test Printf.@sprintf("%4.1f", 1.234) == " 1.2"
    @test Printf.@sprintf("%8.1f", 1.234) == "     1.2"
    @test Printf.@sprintf("%+8.1f", 1.234) == "    +1.2"
    @test Printf.@sprintf("% 8.1f", 1.234) == "     1.2"
    @test Printf.@sprintf("% 7.1f", 1.234) == "    1.2"
    @test Printf.@sprintf("% 08.1f", 1.234) == " 00001.2"
    @test Printf.@sprintf("%08.1f", 1.234) == "000001.2"
    @test Printf.@sprintf("%-08.1f", 1.234) == "1.2     "
    @test Printf.@sprintf("%-8.1f", 1.234) == "1.2     "
    @test Printf.@sprintf("%08.1f", -1.234) == "-00001.2"
    @test Printf.@sprintf("%09.1f", -1.234) == "-000001.2"
    @test Printf.@sprintf("%09.1f", 1.234) == "0000001.2"
    @test Printf.@sprintf("%+09.1f", 1.234) == "+000001.2"
    @test Printf.@sprintf("% 09.1f", 1.234) == " 000001.2"
    @test Printf.@sprintf("%+ 09.1f", 1.234) == "+000001.2"
    @test Printf.@sprintf("%+ 09.1f", 1.234) == "+000001.2"
    @test Printf.@sprintf("%+ 09.0f", 1.234) == "+00000001"
    @test Printf.@sprintf("%+ #09.0f", 1.234) == "+0000001."
end

@testset "%e" begin

    # Inf / NaN handling
    @test (Printf.@sprintf "%e" Inf) == "Inf"
    @test (Printf.@sprintf "%+e" Inf) == "+Inf"
    @test (Printf.@sprintf "% e" Inf) == " Inf"
    @test (Printf.@sprintf "% #e" Inf) == " Inf"
    @test (Printf.@sprintf "%e" -Inf) == "-Inf"
    @test (Printf.@sprintf "%+e" -Inf) == "-Inf"
    @test (Printf.@sprintf "%e" NaN) == "NaN"
    @test (Printf.@sprintf "%+e" NaN) == "NaN"
    @test (Printf.@sprintf "% e" NaN) == "NaN"
    @test (Printf.@sprintf "% #e" NaN) == "NaN"
    @test (Printf.@sprintf "%e" big"Inf") == "Inf"
    @test (Printf.@sprintf "%e" big"NaN") == "NaN"

    # scientific notation
    @test (Printf.@sprintf "%.0e" 3e142) == "3e+142"
    @test (Printf.@sprintf "%#.0e" 3e142) == "3.e+142"
    @test (Printf.@sprintf "%.0e" big"3e142") == "3e+142"
    @test (Printf.@sprintf "%#.0e" big"3e142") == "3.e+142"

    @test (Printf.@sprintf "%.0e" big"3e1042") == "3e+1042"

    @test (Printf.@sprintf "%e" 3e42) == "3.000000e+42"
    @test (Printf.@sprintf "%E" 3e42) == "3.000000E+42"
    @test (Printf.@sprintf "%e" 3e-42) == "3.000000e-42"
    @test (Printf.@sprintf "%E" 3e-42) == "3.000000E-42"

    @test Printf.@sprintf("%e", 1.234) == "1.234000e+00"
    @test Printf.@sprintf("%E", 1.234) == "1.234000E+00"
    @test Printf.@sprintf("%+e", 1.234) == "+1.234000e+00"
    @test Printf.@sprintf("% e", 1.234) == " 1.234000e+00"
    @test Printf.@sprintf("%e", -1.234) == "-1.234000e+00"
    @test Printf.@sprintf("%+e", -1.234) == "-1.234000e+00"
    @test Printf.@sprintf("% e", -1.234) == "-1.234000e+00"
    @test Printf.@sprintf("%#e", 1.234) == "1.234000e+00"
    @test Printf.@sprintf("%.2e", 1.234) == "1.23e+00"
    @test Printf.@sprintf("%.2e", 1.235) == "1.24e+00"
    @test Printf.@sprintf("%.2e", 0.235) == "2.35e-01"
    @test Printf.@sprintf("%4.1e", 1.234) == "1.2e+00"
    @test Printf.@sprintf("%8.1e", 1.234) == " 1.2e+00"
    @test Printf.@sprintf("%+8.1e", 1.234) == "+1.2e+00"
    @test Printf.@sprintf("% 8.1e", 1.234) == " 1.2e+00"
    @test Printf.@sprintf("% 7.1e", 1.234) == " 1.2e+00"
    @test Printf.@sprintf("% 08.1e", 1.234) == " 1.2e+00"
    @test Printf.@sprintf("%08.1e", 1.234) == "01.2e+00"
    @test Printf.@sprintf("%-08.1e", 1.234) == "1.2e+00 "
    @test Printf.@sprintf("%-8.1e", 1.234) == "1.2e+00 "
    @test Printf.@sprintf("%-8.1e", 1.234) == "1.2e+00 "
    @test Printf.@sprintf("%08.1e", -1.234) == "-1.2e+00"
    @test Printf.@sprintf("%09.1e", -1.234) == "-01.2e+00"
    @test Printf.@sprintf("%09.1e", 1.234) == "001.2e+00"
    @test Printf.@sprintf("%+09.1e", 1.234) == "+01.2e+00"
    @test Printf.@sprintf("% 09.1e", 1.234) == " 01.2e+00"
    @test Printf.@sprintf("%+ 09.1e", 1.234) == "+01.2e+00"
    @test Printf.@sprintf("%+ 09.1e", 1.234) == "+01.2e+00"
    @test Printf.@sprintf("%+ 09.0e", 1.234) == "+0001e+00"
    @test Printf.@sprintf("%+ #09.0e", 1.234) == "+001.e+00"
end

@testset "strings" begin

    @test Printf.@sprintf("Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("+%s+", "hello") == "+hello+"
    @test Printf.@sprintf("%.1s", "foo") == "f"
    @test Printf.@sprintf("%s", "%%%%") == "%%%%"
    @test Printf.@sprintf("%s", "Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("%+s", "Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("% s", "Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("%+ s", "Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("%1s", "Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("%20s", "Hallo") == "               Hallo"
    @test Printf.@sprintf("%-20s", "Hallo") == "Hallo               "
    @test Printf.@sprintf("%0-20s", "Hallo") == "Hallo               "
    @test Printf.@sprintf("%.20s", "Hallo heimur") == "Hallo heimur"
    @test Printf.@sprintf("%20.5s", "Hallo heimur") == "               Hallo"
    @test Printf.@sprintf("%.0s", "Hallo heimur") == ""
    @test Printf.@sprintf("%20.0s", "Hallo heimur") == "                    "
    @test Printf.@sprintf("%.s", "Hallo heimur") == ""
    @test Printf.@sprintf("%20.s", "Hallo heimur") == "                    "
    @test (Printf.@sprintf "%s" "test") == "test"
    @test (Printf.@sprintf "%s" "tést") == "tést"
    @test Printf.@sprintf("ø%sø", "hey") == "øheyø"
    @test Printf.@sprintf("%4sø", "ø") == "   øø"
    @test Printf.@sprintf("%-4sø", "ø") == "ø   ø"

    @test (Printf.@sprintf "%8s" "test") == "    test"
    @test (Printf.@sprintf "%-8s" "test") == "test    "

    @test (Printf.@sprintf "%s" :test) == "test"
    @test (Printf.@sprintf "%#s" :test) == ":test"
    @test (Printf.@sprintf "%#8s" :test) == "   :test"
    @test (Printf.@sprintf "%#-8s" :test) == ":test   "

    @test (Printf.@sprintf "%8.3s" "test") == "     tes"
    @test (Printf.@sprintf "%#8.3s" "test") == "     \"te"
    @test (Printf.@sprintf "%-8.3s" "test") == "tes     "
    @test (Printf.@sprintf "%#-8.3s" "test") == "\"te     "
    @test (Printf.@sprintf "%.3s" "test") == "tes"
    @test (Printf.@sprintf "%#.3s" "test") == "\"te"
    @test (Printf.@sprintf "%-.3s" "test") == "tes"
    @test (Printf.@sprintf "%#-.3s" "test") == "\"te"

end

@testset "chars" begin

    @test Printf.@sprintf("%c", 'a') == "a"
    @test Printf.@sprintf("%c",  32) == " "
    @test Printf.@sprintf("%c",  36) == "\$"
    @test Printf.@sprintf("%3c", 'a') == "  a"
    @test Printf.@sprintf( "%c", 'x') == "x"
    @test Printf.@sprintf("%+c", 'x') == "x"
    @test Printf.@sprintf("% c", 'x') == "x"
    @test Printf.@sprintf("%+ c", 'x') == "x"
    @test Printf.@sprintf("%1c", 'x') == "x"
    @test Printf.@sprintf("%20c"  , 'x') == "                   x"
    @test Printf.@sprintf("%-20c" , 'x') == "x                   "
    @test Printf.@sprintf("%-020c", 'x') == "x                   "
    @test Printf.@sprintf("%c", 65) == "A"
    @test Printf.@sprintf("%c", 'A') == "A"
    @test Printf.@sprintf("%3c", 'A') == "  A"
    @test Printf.@sprintf("%-3c", 'A') == "A  "
    @test Printf.@sprintf("%c", 248) == "ø"
    @test Printf.@sprintf("%c", 'ø') == "ø"
    @test Printf.@sprintf("%c", "ø") == "ø"
    @test Printf.@sprintf("%c", '𐀀') == "𐀀"

end

function _test_flags(val, vflag::AbstractString, fmt::AbstractString, res::AbstractString, prefix::AbstractString)
    vflag = string("%", vflag)
    space_fmt = string(length(res) + length(prefix) + 3, fmt)
    fsign = string((val < 0 ? "-" : "+"), prefix)
    nsign = string((val < 0 ? "-" : " "), prefix)
    osign = val < 0 ? string("-", prefix) : string(prefix, "0")
    esign = string(val < 0 ? "-" : "", prefix)
    esignend = val < 0 ? "" : " "

    for (flag::AbstractString, ans::AbstractString) in (
            ("", string("  ", nsign, res)),
            ("+", string("  ", fsign, res)),
            (" ", string("  ", nsign, res)),
            ("0", string(osign, "00", res)),
            ("-", string(esign, res, "  ", esignend)),
            ("0+", string(fsign, "00", res)),
            ("0 ", string(nsign, "00", res)),
            ("-+", string(fsign, res, "  ")),
            ("- ", string(nsign, res, "  ")),
        )
        fmt_string = string(vflag, flag, space_fmt)
        fmtd = Printf.format(Printf.Format(fmt_string), val)
        @test fmtd == ans
    end
end

@testset "basics" begin

    @test Printf.@sprintf("%%") == "%"
    @test Printf.@sprintf("hey there") == "hey there"
    @test_throws ArgumentError Printf.Format("")
    @test_throws ArgumentError Printf.Format("%+")
    @test_throws ArgumentError Printf.Format("%.")
    @test_throws ArgumentError Printf.Format("%.0")
    @test isempty(Printf.Format("%%").formats)
    @test Printf.@sprintf("%d%d", 1, 2) == "12"
    @test (Printf.@sprintf "%d%d" [1 2]...) == "12"
    @test (Printf.@sprintf("X%d", 2)) == "X2"
    @test (Printf.@sprintf("\u00d0%d", 2)) == "\u00d02"
    @test (Printf.@sprintf("\u0f00%d", 2)) == "\u0f002"
    @test (Printf.@sprintf("\U0001ffff%d", 2)) == "\U0001ffff2"
    @test (Printf.@sprintf("%dX%d", 1, 2)) == "1X2"
    @test (Printf.@sprintf("%d\u00d0%d", 1, 2)) == "1\u00d02"
    @test (Printf.@sprintf("%d\u0f00%d", 1, 2)) == "1\u0f002"
    @test (Printf.@sprintf("%d\U0001ffff%d", 1, 2)) == "1\U0001ffff2"
    @test (Printf.@sprintf("%d\u2203%d\u0203", 1, 2)) == "1\u22032\u0203"
    @test_throws ArgumentError Printf.Format("%y%d")
    @test_throws ArgumentError Printf.Format("%\u00d0%d")
    @test_throws ArgumentError Printf.Format("%\u0f00%d")
    @test_throws ArgumentError Printf.Format("%\U0001ffff%d")
    @test Printf.@sprintf("%10.5d", 4) == "     00004"
    @test (Printf.@sprintf "%d" typemax(Int64)) == "9223372036854775807"

    for (fmt, val) in (("%7.2f", "   1.23"),
                   ("%-7.2f", "1.23   "),
                   ("%07.2f", "0001.23"),
                   ("%.0f", "1"),
                   ("%#.0f", "1."),
                   ("%.4e", "1.2345e+00"),
                   ("%.4E", "1.2345E+00"),
                   ("%.2a", "0x1.3cp+0"),
                   ("%.2A", "0X1.3CP+0")),
        num in (1.2345, big"1.2345")
        @test Printf.format(Printf.Format(fmt), num) == val
    end

    for (fmt, val) in (("%i", "42"),
                   ("%u", "42"),
                   ("Test: %i", "Test: 42"),
                   ("%#x", "0x2a"),
                   ("%x", "2a"),
                   ("%X", "2A"),
                   ("% i", " 42"),
                   ("%+i", "+42"),
                   ("%4i", "  42"),
                   ("%-4i", "42  "),
                   ("%f", "42.000000"),
                   ("%g", "42"),
                   ("%e", "4.200000e+01")),
        num in (UInt16(42), UInt32(42), UInt64(42), UInt128(42),
                Int16(42), Int32(42), Int64(42), Int128(42), big"42")
        @test Printf.format(Printf.Format(fmt), num) == val
    end

    for i in (
            (42, "", "i", "42", ""),
            (42, "", "d", "42", ""),

            (42, "", "u", "42", ""),
            (42, "", "x", "2a", ""),
            (42, "", "X", "2A", ""),
            (42, "", "o", "52", ""),

            (42, "#", "x", "2a", "0x"),
            (42, "#", "X", "2A", "0X"),
            (42, "#", "o", "052", ""),

            (1.2345, "", ".2f", "1.23", ""),
            (1.2345, "", ".2e", "1.23e+00", ""),
            (1.2345, "", ".2E", "1.23E+00", ""),

            (1.2345, "#", ".0f", "1.", ""),
            (1.2345, "#", ".0e", "1.e+00", ""),
            (1.2345, "#", ".0E", "1.E+00", ""),

            (1.2345, "", ".2a", "1.3cp+0", "0x"),
            (1.2345, "", ".2A", "1.3CP+0", "0X"),
        )
        _test_flags(i...)
        _test_flags(-i[1], i[2:5]...)
    end

    # reasonably complex
    @test (Printf.@sprintf "Test: %s%c%C%c%#-.0f." "t" 65 66 67 -42) == "Test: tABC-42.."

    # combo
    @test (Printf.@sprintf "%f %d %d %f" 1.0 [3 4]... 5) == "1.000000 3 4 5.000000"

    # multi
    @test (Printf.@sprintf "%s %f %9.5f %d %d %d %d%d%d%d" [1:6;]... [7,8,9,10]...) == "1 2.000000   3.00000 4 5 6 78910"

    # comprehension
    @test (Printf.@sprintf "%s %s %s %d %d %d %f %f %f" Any[10^x+y for x=1:3,y=1:3 ]...) == "11 101 1001 12 102 1002 13.000000 103.000000 1003.000000"

    # more than 16 formats/args
    @test (Printf.@sprintf "%s %s %s %d %d %d %f %f %f %s %s %s %d %d %d %f %f %f" Any[10*x+(x+1) for x=1:18 ]...) ==
        "12 23 34 45 56 67 78.000000 89.000000 100.000000 111 122 133 144 155 166 177.000000 188.000000 199.000000"

    # Check bug with trailing nul printing BigFloat
    @test (Printf.@sprintf("%.330f", BigFloat(1)))[end] != '\0'

    # issue #29662
    @test (Printf.@sprintf "%12.3e" pi*1e100) == "  3.142e+100"

    @test string(Printf.Format("%a").formats[1]) == "%a"
    @test string(Printf.Format("%a").formats[1]; modifier="R") == "%Ra"

    @test Printf.@sprintf("%d", 3.14) == "3"
    @test Printf.@sprintf("%2d", 3.14) == " 3"
    @test Printf.@sprintf("%2d", big(3.14)) == " 3"
    @test Printf.@sprintf("%s", 1) == "1"
    @test Printf.@sprintf("%f", 1) == "1.000000"
    @test Printf.@sprintf("%e", 1) == "1.000000e+00"
    @test Printf.@sprintf("%g", 1) == "1"

    # escaped '%'
    @test_throws ArgumentError @sprintf("%s%%%s", "a")
    @test @sprintf("%s%%%s", "a", "b") == "a%%b"

    # print float as %d uses round(x)
    @test @sprintf("%d", 25.5) == "26"

    # 37539
    @test @sprintf(" %.1e\n", 0.999) == " 1.0e+00\n"
    @test @sprintf("   %.1f", 9.999) == "   10.0"

    # 37552
    @test @sprintf("%d", 1.0e100) == "10000000000000000159028911097599180468360808563945281389781327557747838772170381060813469985856815104"
    @test @sprintf("%d", 3//1) == "3"
end

@testset "integers" begin

    @test Printf.@sprintf( "% d",  42) == " 42"
    @test Printf.@sprintf( "% d", -42) == "-42"
    @test Printf.@sprintf( "% 5d",  42) == "   42"
    @test Printf.@sprintf( "% 5d", -42) == "  -42"
    @test Printf.@sprintf( "% 15d",  42) == "             42"
    @test Printf.@sprintf( "% 15d", -42) == "            -42"
    @test Printf.@sprintf("%+d",  42) == "+42"
    @test Printf.@sprintf("%+d", -42) == "-42"
    @test Printf.@sprintf("%+5d",  42) == "  +42"
    @test Printf.@sprintf("%+5d", -42) == "  -42"
    @test Printf.@sprintf("%+15d",  42) == "            +42"
    @test Printf.@sprintf("%+15d", -42) == "            -42"
    @test Printf.@sprintf( "%0d",  42) == "42"
    @test Printf.@sprintf( "%0d", -42) == "-42"
    @test Printf.@sprintf( "%05d",  42) == "00042"
    @test Printf.@sprintf( "%05d", -42) == "-0042"
    @test Printf.@sprintf( "%015d",  42) == "000000000000042"
    @test Printf.@sprintf( "%015d", -42) == "-00000000000042"
    @test Printf.@sprintf("%-d",  42) == "42"
    @test Printf.@sprintf("%-d", -42) == "-42"
    @test Printf.@sprintf("%-5d",  42) == "42   "
    @test Printf.@sprintf("%-5d", -42) == "-42  "
    @test Printf.@sprintf("%-15d",  42) == "42             "
    @test Printf.@sprintf("%-15d", -42) == "-42            "
    @test Printf.@sprintf("%-0d",  42) == "42"
    @test Printf.@sprintf("%-0d", -42) == "-42"
    @test Printf.@sprintf("%-05d",  42) == "42   "
    @test Printf.@sprintf("%-05d", -42) == "-42  "
    @test Printf.@sprintf("%-015d",  42) == "42             "
    @test Printf.@sprintf("%-015d", -42) == "-42            "
    @test Printf.@sprintf( "%0-d",  42) == "42"
    @test Printf.@sprintf( "%0-d", -42) == "-42"
    @test Printf.@sprintf( "%0-5d",  42) == "42   "
    @test Printf.@sprintf( "%0-5d", -42) == "-42  "
    @test Printf.@sprintf( "%0-15d",  42) == "42             "
    @test Printf.@sprintf( "%0-15d", -42) == "-42            "
    @test_throws ArgumentError Printf.Format("%d %")

    @test Printf.@sprintf("%lld", 18446744065119617025) == "18446744065119617025"
    @test Printf.@sprintf("%+8lld", 100) == "    +100"
    @test Printf.@sprintf("%+.8lld", 100) == "+00000100"
    @test Printf.@sprintf("%+10.8lld", 100) == " +00000100"
    @test_throws ArgumentError Printf.Format("%_1lld")
    @test Printf.@sprintf("%-1.5lld", -100) == "-00100"
    @test Printf.@sprintf("%5lld", 100) == "  100"
    @test Printf.@sprintf("%5lld", -100) == " -100"
    @test Printf.@sprintf("%-5lld", 100) == "100  "
    @test Printf.@sprintf("%-5lld", -100) == "-100 "
    @test Printf.@sprintf("%-.5lld", 100) == "00100"
    @test Printf.@sprintf("%-.5lld", -100) == "-00100"
    @test Printf.@sprintf("%-8.5lld", 100) == "00100   "
    @test Printf.@sprintf("%-8.5lld", -100) == "-00100  "
    @test Printf.@sprintf("%05lld", 100) == "00100"
    @test Printf.@sprintf("%05lld", -100) == "-0100"
    @test Printf.@sprintf("% lld", 100) == " 100"
    @test Printf.@sprintf("% lld", -100) == "-100"
    @test Printf.@sprintf("% 5lld", 100) == "  100"
    @test Printf.@sprintf("% 5lld", -100) == " -100"
    @test Printf.@sprintf("% .5lld", 100) == " 00100"
    @test Printf.@sprintf("% .5lld", -100) == "-00100"
    @test Printf.@sprintf("% 8.5lld", 100) == "   00100"
    @test Printf.@sprintf("% 8.5lld", -100) == "  -00100"
    @test Printf.@sprintf("%.0lld", 0) == "0"
    @test Printf.@sprintf("%#+21.18llx", -100) == "-0x000000000000000064"
    @test Printf.@sprintf("%#.25llo", -100) == "-00000000000000000000000144"
    @test Printf.@sprintf("%#+24.20llo", -100) == "  -000000000000000000144"
    @test Printf.@sprintf("%#+18.21llX", -100) == "-0X000000000000000000064"
    @test Printf.@sprintf("%#+20.24llo", -100) == "-0000000000000000000000144"
    @test Printf.@sprintf("%#+25.22llu", -1) == "  -0000000000000000000001"
    @test Printf.@sprintf("%#+25.22llu", -1) == "  -0000000000000000000001"
    @test Printf.@sprintf("%#+30.25llu", -1) == "    -0000000000000000000000001"
    @test Printf.@sprintf("%+#25.22lld", -1) == "  -0000000000000000000001"
    @test Printf.@sprintf("%#-8.5llo", 100) == "000144  "
    @test Printf.@sprintf("%#-+ 08.5lld", 100) == "+00100  "
    @test Printf.@sprintf("%#-+ 08.5lld", 100) == "+00100  "
    @test Printf.@sprintf("%.40lld",  1) == "0000000000000000000000000000000000000001"
    @test Printf.@sprintf("% .40lld",  1) == " 0000000000000000000000000000000000000001"
    @test Printf.@sprintf("% .40d",  1) == " 0000000000000000000000000000000000000001"
    @test Printf.@sprintf("%lld",  18446744065119617025) == "18446744065119617025"

    @test Printf.@sprintf("+%d+",  10) == "+10+"
    @test Printf.@sprintf("%#012x",  1) == "0x0000000001"
    @test Printf.@sprintf("%#04.8x",  1) == "0x00000001"

    @test Printf.@sprintf("%#-08.2x",  1) == "0x01    "
    @test Printf.@sprintf("%#08o",  1) == "00000001"
    @test Printf.@sprintf("%d",  1024) == "1024"
    @test Printf.@sprintf("%d", -1024) == "-1024"
    @test Printf.@sprintf("%i",  1024) == "1024"
    @test Printf.@sprintf("%i", -1024) == "-1024"
    @test Printf.@sprintf("%u",  1024) == "1024"
    @test Printf.@sprintf("%u",  UInt(4294966272)) == "4294966272"
    @test Printf.@sprintf("%o",  511) == "777"
    @test Printf.@sprintf("%o",  UInt(4294966785)) == "37777777001"
    @test Printf.@sprintf("%x",  305441741) == "1234abcd"
    @test Printf.@sprintf("%x",  UInt(3989525555)) == "edcb5433"
    @test Printf.@sprintf("%X",  305441741) == "1234ABCD"
    @test Printf.@sprintf("%X",  UInt(3989525555)) == "EDCB5433"
    @test Printf.@sprintf("%+d",  1024) == "+1024"
    @test Printf.@sprintf("%+d", -1024) == "-1024"
    @test Printf.@sprintf("%+i",  1024) == "+1024"
    @test Printf.@sprintf("%+i", -1024) == "-1024"
    @test Printf.@sprintf("%+u",  1024) == "+1024"
    @test Printf.@sprintf("%+u",  UInt(4294966272)) == "+4294966272"
    @test Printf.@sprintf("%+o",  511) == "+777"
    @test Printf.@sprintf("%+o",  UInt(4294966785)) == "+37777777001"
    @test Printf.@sprintf("%+x",  305441741) == "+1234abcd"
    @test Printf.@sprintf("%+x",  UInt(3989525555)) == "+edcb5433"
    @test Printf.@sprintf("%+X",  305441741) == "+1234ABCD"
    @test Printf.@sprintf("%+X",  UInt(3989525555)) == "+EDCB5433"
    @test Printf.@sprintf("% d",  1024) == " 1024"
    @test Printf.@sprintf("% d", -1024) == "-1024"
    @test Printf.@sprintf("% i",  1024) == " 1024"
    @test Printf.@sprintf("% i", -1024) == "-1024"
    @test Printf.@sprintf("% u",  1024) == " 1024"
    @test Printf.@sprintf("% u",  UInt(4294966272)) == " 4294966272"
    @test Printf.@sprintf("% o",  511) == " 777"
    @test Printf.@sprintf("% o",  UInt(4294966785)) == " 37777777001"
    @test Printf.@sprintf("% x",  305441741) == " 1234abcd"
    @test Printf.@sprintf("% x",  UInt(3989525555)) == " edcb5433"
    @test Printf.@sprintf("% X",  305441741) == " 1234ABCD"
    @test Printf.@sprintf("% X",  UInt(3989525555)) == " EDCB5433"
    @test Printf.@sprintf("%+ d",  1024) == "+1024"
    @test Printf.@sprintf("%+ d", -1024) == "-1024"
    @test Printf.@sprintf("%+ i",  1024) == "+1024"
    @test Printf.@sprintf("%+ i", -1024) == "-1024"
    @test Printf.@sprintf("%+ u",  1024) == "+1024"
    @test Printf.@sprintf("%+ u",  UInt(4294966272)) == "+4294966272"
    @test Printf.@sprintf("%+ o",  511) == "+777"
    @test Printf.@sprintf("%+ o",  UInt(4294966785)) == "+37777777001"
    @test Printf.@sprintf("%+ x",  305441741) == "+1234abcd"
    @test Printf.@sprintf("%+ x",  UInt(3989525555)) == "+edcb5433"
    @test Printf.@sprintf("%+ X",  305441741) == "+1234ABCD"
    @test Printf.@sprintf("%+ X",  UInt(3989525555)) == "+EDCB5433"
    @test Printf.@sprintf("%#o",  511) == "0777"
    @test Printf.@sprintf("%#o",  UInt(4294966785)) == "037777777001"
    @test Printf.@sprintf("%#x",  305441741) == "0x1234abcd"
    @test Printf.@sprintf("%#x",  UInt(3989525555)) == "0xedcb5433"
    @test Printf.@sprintf("%#X",  305441741) == "0X1234ABCD"
    @test Printf.@sprintf("%#X",  UInt(3989525555)) == "0XEDCB5433"
    @test Printf.@sprintf("%#o",  UInt(0)) == "00"
    @test Printf.@sprintf("%#x",  UInt(0)) == "0x0"
    @test Printf.@sprintf("%#X",  UInt(0)) == "0X0"
    @test Printf.@sprintf("%1d",  1024) == "1024"
    @test Printf.@sprintf("%1d", -1024) == "-1024"
    @test Printf.@sprintf("%1i",  1024) == "1024"
    @test Printf.@sprintf("%1i", -1024) == "-1024"
    @test Printf.@sprintf("%1u",  1024) == "1024"
    @test Printf.@sprintf("%1u",  UInt(4294966272)) == "4294966272"
    @test Printf.@sprintf("%1o",  511) == "777"
    @test Printf.@sprintf("%1o",  UInt(4294966785)) == "37777777001"
    @test Printf.@sprintf("%1x",  305441741) == "1234abcd"
    @test Printf.@sprintf("%1x",  UInt(3989525555)) == "edcb5433"
    @test Printf.@sprintf("%1X",  305441741) == "1234ABCD"
    @test Printf.@sprintf("%1X",  UInt(3989525555)) == "EDCB5433"
    @test Printf.@sprintf("%20d",  1024) == "                1024"
    @test Printf.@sprintf("%20d", -1024) == "               -1024"
    @test Printf.@sprintf("%20i",  1024) == "                1024"
    @test Printf.@sprintf("%20i", -1024) == "               -1024"
    @test Printf.@sprintf("%20u",  1024) == "                1024"
    @test Printf.@sprintf("%20u",  UInt(4294966272)) == "          4294966272"
    @test Printf.@sprintf("%20o",  511) == "                 777"
    @test Printf.@sprintf("%20o",  UInt(4294966785)) == "         37777777001"
    @test Printf.@sprintf("%20x",  305441741) == "            1234abcd"
    @test Printf.@sprintf("%20x",  UInt(3989525555)) == "            edcb5433"
    @test Printf.@sprintf("%20X",  305441741) == "            1234ABCD"
    @test Printf.@sprintf("%20X",  UInt(3989525555)) == "            EDCB5433"
    @test Printf.@sprintf("%-20d",  1024) == "1024                "
    @test Printf.@sprintf("%-20d", -1024) == "-1024               "
    @test Printf.@sprintf("%-20i",  1024) == "1024                "
    @test Printf.@sprintf("%-20i", -1024) == "-1024               "
    @test Printf.@sprintf("%-20u",  1024) == "1024                "
    @test Printf.@sprintf("%-20u",  UInt(4294966272)) == "4294966272          "
    @test Printf.@sprintf("%-20o",  511) == "777                 "
    @test Printf.@sprintf("%-20o",  UInt(4294966785)) == "37777777001         "
    @test Printf.@sprintf("%-20x",  305441741) == "1234abcd            "
    @test Printf.@sprintf("%-20x",  UInt(3989525555)) == "edcb5433            "
    @test Printf.@sprintf("%-20X",  305441741) == "1234ABCD            "
    @test Printf.@sprintf("%-20X",  UInt(3989525555)) == "EDCB5433            "
    @test Printf.@sprintf("%020d",  1024) == "00000000000000001024"
    @test Printf.@sprintf("%020d", -1024) == "-0000000000000001024"
    @test Printf.@sprintf("%020i",  1024) == "00000000000000001024"
    @test Printf.@sprintf("%020i", -1024) == "-0000000000000001024"
    @test Printf.@sprintf("%020u",  1024) == "00000000000000001024"
    @test Printf.@sprintf("%020u",  UInt(4294966272)) == "00000000004294966272"
    @test Printf.@sprintf("%020o",  511) == "00000000000000000777"
    @test Printf.@sprintf("%020o",  UInt(4294966785)) == "00000000037777777001"
    @test Printf.@sprintf("%020x",  305441741) == "0000000000001234abcd"
    @test Printf.@sprintf("%020x",  UInt(3989525555)) == "000000000000edcb5433"
    @test Printf.@sprintf("%020X",  305441741) == "0000000000001234ABCD"
    @test Printf.@sprintf("%020X",  UInt(3989525555)) == "000000000000EDCB5433"
    @test Printf.@sprintf("%#20o",  511) == "                0777"
    @test Printf.@sprintf("%#20o",  UInt(4294966785)) == "        037777777001"
    @test Printf.@sprintf("%#20x",  305441741) == "          0x1234abcd"
    @test Printf.@sprintf("%#20x",  UInt(3989525555)) == "          0xedcb5433"
    @test Printf.@sprintf("%#20X",  305441741) == "          0X1234ABCD"
    @test Printf.@sprintf("%#20X",  UInt(3989525555)) == "          0XEDCB5433"
    @test Printf.@sprintf("%#020o",  511) == "00000000000000000777"
    @test Printf.@sprintf("%#020o",  UInt(4294966785)) == "00000000037777777001"
    @test Printf.@sprintf("%#020x",  305441741) == "0x00000000001234abcd"
    @test Printf.@sprintf("%#020x",  UInt(3989525555)) == "0x0000000000edcb5433"
    @test Printf.@sprintf("%#020X",  305441741) == "0X00000000001234ABCD"
    @test Printf.@sprintf("%#020X",  UInt(3989525555)) == "0X0000000000EDCB5433"
    @test Printf.@sprintf("%0-20d",  1024) == "1024                "
    @test Printf.@sprintf("%0-20d", -1024) == "-1024               "
    @test Printf.@sprintf("%0-20i",  1024) == "1024                "
    @test Printf.@sprintf("%0-20i", -1024) == "-1024               "
    @test Printf.@sprintf("%0-20u",  1024) == "1024                "
    @test Printf.@sprintf("%0-20u",  UInt(4294966272)) == "4294966272          "
    @test Printf.@sprintf("%-020o",  511) == "777                 "
    @test Printf.@sprintf("%-020o",  UInt(4294966785)) == "37777777001         "
    @test Printf.@sprintf("%-020x",  305441741) == "1234abcd            "
    @test Printf.@sprintf("%-020x",  UInt(3989525555)) == "edcb5433            "
    @test Printf.@sprintf("%-020X",  305441741) == "1234ABCD            "
    @test Printf.@sprintf("%-020X",  UInt(3989525555)) == "EDCB5433            "
    @test Printf.@sprintf("%.20d",  1024) == "00000000000000001024"
    @test Printf.@sprintf("%.20d", -1024) == "-00000000000000001024"
    @test Printf.@sprintf("%.20i",  1024) == "00000000000000001024"
    @test Printf.@sprintf("%.20i", -1024) == "-00000000000000001024"
    @test Printf.@sprintf("%.20u",  1024) == "00000000000000001024"
    @test Printf.@sprintf("%.20u",  UInt(4294966272)) == "00000000004294966272"
    @test Printf.@sprintf("%.20o",  511) == "00000000000000000777"
    @test Printf.@sprintf("%.20o",  UInt(4294966785)) == "00000000037777777001"
    @test Printf.@sprintf("%.20x",  305441741) == "0000000000001234abcd"
    @test Printf.@sprintf("%.20x",  UInt(3989525555)) == "000000000000edcb5433"
    @test Printf.@sprintf("%.20X",  305441741) == "0000000000001234ABCD"
    @test Printf.@sprintf("%.20X",  UInt(3989525555)) == "000000000000EDCB5433"
    @test Printf.@sprintf("%20.5d",  1024) == "               01024"
    @test Printf.@sprintf("%20.5d", -1024) == "              -01024"
    @test Printf.@sprintf("%20.5i",  1024) == "               01024"
    @test Printf.@sprintf("%20.5i", -1024) == "              -01024"
    @test Printf.@sprintf("%20.5u",  1024) == "               01024"
    @test Printf.@sprintf("%20.5u",  UInt(4294966272)) == "          4294966272"
    @test Printf.@sprintf("%20.5o",  511) == "               00777"
    @test Printf.@sprintf("%20.5o",  UInt(4294966785)) == "         37777777001"
    @test Printf.@sprintf("%20.5x",  305441741) == "            1234abcd"
    @test Printf.@sprintf("%20.10x",  UInt(3989525555)) == "          00edcb5433"
    @test Printf.@sprintf("%20.5X",  305441741) == "            1234ABCD"
    @test Printf.@sprintf("%20.10X",  UInt(3989525555)) == "          00EDCB5433"
    @test Printf.@sprintf("%020.5d",  1024) == "               01024"
    @test Printf.@sprintf("%020.5d", -1024) == "              -01024"
    @test Printf.@sprintf("%020.5i",  1024) == "               01024"
    @test Printf.@sprintf("%020.5i", -1024) == "              -01024"
    @test Printf.@sprintf("%020.5u",  1024) == "               01024"
    @test Printf.@sprintf("%020.5u",  UInt(4294966272)) == "          4294966272"
    @test Printf.@sprintf("%020.5o",  511) == "               00777"
    @test Printf.@sprintf("%020.5o",  UInt(4294966785)) == "         37777777001"
    @test Printf.@sprintf("%020.5x",  305441741) == "            1234abcd"
    @test Printf.@sprintf("%020.10x",  UInt(3989525555)) == "          00edcb5433"
    @test Printf.@sprintf("%020.5X",  305441741) == "            1234ABCD"
    @test Printf.@sprintf("%020.10X",  UInt(3989525555)) == "          00EDCB5433"
    @test Printf.@sprintf("%20.0d",  1024) == "                1024"
    @test Printf.@sprintf("%20.d", -1024) == "               -1024"
    @test Printf.@sprintf("%20.d",  0) == "                   0"
    @test Printf.@sprintf("%20.0i",  1024) == "                1024"
    @test Printf.@sprintf("%20.i", -1024) == "               -1024"
    @test Printf.@sprintf("%20.i",  0) == "                   0"
    @test Printf.@sprintf("%20.u",  1024) == "                1024"
    @test Printf.@sprintf("%20.0u",  UInt(4294966272)) == "          4294966272"
    @test Printf.@sprintf("%20.u",  UInt(0)) == "                   0"
    @test Printf.@sprintf("%20.o",  511) == "                 777"
    @test Printf.@sprintf("%20.0o",  UInt(4294966785)) == "         37777777001"
    @test Printf.@sprintf("%20.o",  UInt(0)) == "                   0"
    @test Printf.@sprintf("%20.x",  305441741) == "            1234abcd"
    @test Printf.@sprintf("%20.0x",  UInt(3989525555)) == "            edcb5433"
    @test Printf.@sprintf("%20.x",  UInt(0)) == "                   0"
    @test Printf.@sprintf("%20.X",  305441741) == "            1234ABCD"
    @test Printf.@sprintf("%20.0X",  UInt(3989525555)) == "            EDCB5433"
    @test Printf.@sprintf("%20.X",  UInt(0)) == "                   0"

end

end # @testset "Printf"


using Lux, Random, Test

include("../utils.jl")

rng = Random.default_rng()
Random.seed!(rng, 0)

@testset "Dropout" begin
    layer = Dropout(0.5f0)
    println(layer)
    ps, st = Lux.setup(rng, layer)
    x = randn(Float32, 5, 2)

    x_, st_ = layer(x, ps, st)
    x__, st__ = layer(x, ps, st)
    x___, st___ = layer(x_, ps, st_)

    @test st_.rng != st.rng
    @test st_.rng == st__.rng
    @test x_ == x__
    @test x_ != x___

    run_JET_tests(layer, x, ps, st)
    test_gradient_correctness_fdm(x -> sum(layer(x, ps, st)[1]), x; atol=1.0f-3,
                                  rtol=1.0f-3)

    st = Lux.testmode(st)

    @test first(layer(x, ps, st)) == x
end

@testset "VariationalHiddenDropout" begin
    layer = VariationalHiddenDropout(0.5f0)
    println(layer)
    ps, st = Lux.setup(rng, layer)
    x = randn(Float32, 5, 2)

    x_, st_ = layer(x, ps, st)
    x__, st__ = layer(x, ps, st)
    x___, st___ = layer(x_, ps, st_)

    @test st_.rng != st.rng
    @test st_.rng == st__.rng
    @test st_.mask == st__.mask
    @test x_ == x__
    @test x_ != x___

    run_JET_tests(layer, x, ps, st)
    run_JET_tests(layer, x, ps, st_)
    test_gradient_correctness_fdm(x -> sum(layer(x, ps, st)[1]), x; atol=1.0f-3,
                                  rtol=1.0f-3)
    test_gradient_correctness_fdm(x -> sum(layer(x, ps, st_)[1]), x; atol=1.0f-3,
                                  rtol=1.0f-3)

    st__ = Lux.update_state(st_, :update_mask, Val(true))
    x___, st___ = layer(x, ps, st__)

    @test st___.mask != st__.mask
    @test x___ != x_

    run_JET_tests(layer, x, ps, st__)
    test_gradient_correctness_fdm(x -> sum(layer(x, ps, st__)[1]), x; atol=1.0f-3,
                                  rtol=1.0f-3)
end


struct TanYam7Cache{uType,uArrayType,rateType,uEltypeNoUnits,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  fsalfirst::rateType
  k2::rateType
  k3::rateType
  k4::rateType
  k5::rateType
  k6::rateType
  k7::rateType
  k8::rateType
  k9::rateType
  k10::rateType
  utilde::uArrayType
  tmp::uType
  atmp::uEltypeNoUnits
  k::rateType
  tab::TabType
end

u_cache(c::TanYam7Cache) = (c.utilde,c.atmp)
du_cache(c::TanYam7Cache) = (c.fsalfirst,c.k2,c.k3,c.k4,c.k5,c.k6,c.k7,c.k8,c.k9,c.k10,c.k)

function alg_cache(alg::TanYam7,u,rate_prototype,uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits,uprev,uprev2,f,t,dt,reltol,p,calck,::Type{Val{true}})
  tab = TanYam7ConstantCache(real(uBottomEltypeNoUnits),real(tTypeNoUnits))
  k1 = zero(rate_prototype); k2 = zero(rate_prototype) ; k3 = zero(rate_prototype); k4 = zero(rate_prototype)
  k5 = zero(rate_prototype); k6 = zero(rate_prototype) ; k7 = zero(rate_prototype); k8 = zero(rate_prototype)
  k9 = zero(rate_prototype); k10= zero(rate_prototype) ;
  utilde = similar(u,axes(u)); tmp = similar(u); atmp = similar(u,uEltypeNoUnits,axes(u)); k = zero(rate_prototype)
  TanYam7Cache(u,uprev,k1,k2,k3,k4,k5,k6,k7,k8,k9,k10,utilde,tmp,atmp,k,tab)
end

alg_cache(alg::TanYam7,u,rate_prototype,uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits,uprev,uprev2,f,t,dt,reltol,p,calck,::Type{Val{false}}) = TanYam7ConstantCache(real(uBottomEltypeNoUnits),real(tTypeNoUnits))

struct DP8Cache{uType,uArrayType,rateType,uEltypeNoUnits,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  k1::rateType
  k2::rateType
  k3::rateType
  k4::rateType
  k5::rateType
  k6::rateType
  k7::rateType
  k8::rateType
  k9::rateType
  k10::rateType
  k11::rateType
  k12::rateType
  k13::rateType
  k14::rateType
  k15::rateType
  k16::rateType
  kupdate::rateType
  udiff::rateType
  bspl::rateType
  dense_tmp3::rateType
  dense_tmp4::rateType
  dense_tmp5::rateType
  dense_tmp6::rateType
  dense_tmp7::rateType
  utilde::uArrayType
  tmp::uType
  atmp::uEltypeNoUnits
  tab::TabType
end

u_cache(c::DP8Cache) = (c.utilde,c.atmp)
du_cache(c::DP8Cache) = (c.k1,c.k2,c.k3,c.k4,c.k5,c.k6,c.k7,c.k8,c.k9,c.k10,c.k11,c.k12,c.k13,c.k14,c.k15,c.k16,c.kupdate,c.udiff,c.bspl,c.dense_tmp3,c.dense_tmp4,c.dense_tmp5,c.dense_tmp6,c.dense_tmp7)

function alg_cache(alg::DP8,u,rate_prototype,uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits,uprev,uprev2,f,t,dt,reltol,p,calck,::Type{Val{true}})
  k1 = zero(rate_prototype); k2  = zero(rate_prototype); k3  = zero(rate_prototype);  k4 = zero(rate_prototype)
  k5 = zero(rate_prototype); k6  = zero(rate_prototype); k7  = zero(rate_prototype);  k8 = zero(rate_prototype)
  k9 = zero(rate_prototype); k10 = zero(rate_prototype); k11 = zero(rate_prototype); k12 = zero(rate_prototype)
  kupdate = zero(rate_prototype); utilde = similar(u,axes(u));
  tmp = similar(u); atmp = similar(u,uEltypeNoUnits,axes(u))
  k13 = zero(rate_prototype)
  k14 = zero(rate_prototype)
  k15 = zero(rate_prototype)
  k16 = zero(rate_prototype)
  udiff = zero(rate_prototype)
  bspl = zero(rate_prototype)
  # dense_tmp1 = udiff
  # dense_tmp2 = bspl
  dense_tmp3 = zero(rate_prototype)
  dense_tmp4 = zero(rate_prototype)
  dense_tmp5 = zero(rate_prototype)
  dense_tmp6 = zero(rate_prototype)
  dense_tmp7 = zero(rate_prototype)
  tab = DP8ConstantCache(real(uBottomEltypeNoUnits),real(tTypeNoUnits))
  DP8Cache(u,uprev,k1,k2,k3,k4,k5,k6,k7,k8,k9,k10,k11,k12,k13,k14,k15,k16,kupdate,
           udiff,bspl,dense_tmp3,dense_tmp4,dense_tmp5,dense_tmp6,dense_tmp7,
           utilde,tmp,atmp,tab)
end

alg_cache(alg::DP8,u,rate_prototype,uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits,uprev,uprev2,f,t,dt,reltol,p,calck,::Type{Val{false}}) = DP8ConstantCache(real(uBottomEltypeNoUnits),real(tTypeNoUnits))

struct TsitPap8Cache{uType,uArrayType,rateType,uEltypeNoUnits,TabType} <: OrdinaryDiffEqMutableCache
  u::uType
  uprev::uType
  fsalfirst::rateType
  k2::rateType
  k3::rateType
  k4::rateType
  k5::rateType
  k6::rateType
  k7::rateType
  k8::rateType
  k9::rateType
  k10::rateType
  k11::rateType
  k12::rateType
  k13::rateType
  utilde::uArrayType
  tmp::uType
  atmp::uEltypeNoUnits
  k::rateType
  tab::TabType
end

u_cache(c::TsitPap8Cache) = (c.utilde,c.atmp)
du_cache(c::TsitPap8Cache) = (c.fsalfirst,c.k2,c.k3,c.k4,c.k5,c.k6,c.k7,c.k8,c.k9,c.k10,c.k11,c.k12,c.k13,c.k)

function alg_cache(alg::TsitPap8,u,rate_prototype,uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits,uprev,uprev2,f,t,dt,reltol,p,calck,::Type{Val{true}})
  tab = TsitPap8ConstantCache(real(uBottomEltypeNoUnits),real(tTypeNoUnits))
  k1 = zero(rate_prototype); k2 = zero(rate_prototype); k3 = zero(rate_prototype); k4 = zero(rate_prototype)
  k5 = zero(rate_prototype); k6 = zero(rate_prototype); k7 = zero(rate_prototype); k8 = zero(rate_prototype)
  k9 = zero(rate_prototype); k10 = zero(rate_prototype); k11 = zero(rate_prototype); k12 = zero(rate_prototype)
  k13 = zero(rate_prototype); utilde = similar(u,axes(u)); k = zero(rate_prototype)
  tmp = similar(u); atmp = similar(u,uEltypeNoUnits,axes(u));
  TsitPap8Cache(u,uprev,k1,k2,k3,k4,k5,k6,k7,k8,k9,k10,k11,k12,k13,utilde,tmp,atmp,k,tab)
end

alg_cache(alg::TsitPap8,u,rate_prototype,uEltypeNoUnits,uBottomEltypeNoUnits,tTypeNoUnits,uprev,uprev2,f,t,dt,reltol,p,calck,::Type{Val{false}}) = TsitPap8ConstantCache(real(uBottomEltypeNoUnits),real(tTypeNoUnits))


include("gemv.jl")
include("gbmv.jl")
include("hemv.jl")
include("hbmv.jl")
include("symv.jl")
include("sbmv.jl")
include("trmv.jl")
include("trsv.jl")
include("ger.jl")
include("her.jl")
include("syr.jl")


using Random
using BenchmarkTools

function comprassion()
    array = []
    for i in 1:999999
        push!(array, rand())
    end
    array2 = array
    @btime sort!(array)
    @btime mergesort!(array2)
end


let
    actions = Vector{TestActionType}(undef,0)
    push!(actions,TestActionType(STAY,1))
    push!(actions, TestActionType(0.5,2))
    cmssp = TestCMSSPType(actions, [PRE,CURR,POST])

    tsv = TestStateType(CURR, TestContState(0.4,false))
    @test n_actions(cmssp) == 2
    @test discount(cmssp) == 1.0
    @test convert_s(Vector{Float64},tsv,cmssp) == [2.0,0.4,0.0]
    @test get_modeswitch_actions(cmssp.actions) == [STAY]
    @test get_control_actions(cmssp.actions) == [0.5]
    olv = OpenLoopVertex{MODE,TestContState,MODESWITCH}(tsv,STAY)
end

# TODO Tests
# More complicated ordering of actions (in case alternating AD and AC)


module TestBroadcast
    using NullableArrays
    using Base.Test
    using Compat

    A1 = rand(Int, 10)
    M1 = rand(Bool, 10)
    n = rand(2:5)
    dims = [ rand(2:5) for i in 1:n]
    A2 = rand(10, dims...)
    M2 = rand(Bool, 10, dims...)
    C2 = Array{Float64}(10, dims...)
    i = rand(2:5)
    A3 = rand(10, [dims; i]...)
    M3 = rand(Bool, 10, [dims; i]...)
    C3 = Array{Float64}(10, [dims; i]...)

    m1 = broadcast((x,y)->x, M1, M2)
    m2 = broadcast((x,y)->x, M2, M3)
    R2 = reshape(Bool[ (x->(m1[x] | M2[x]))(i) for i in 1:length(M2) ], size(M2))
    R3 = reshape(Bool[ (x->(m2[x] | M3[x]))(i) for i in 1:length(M3) ], size(M3))
    r2 = broadcast((x,y)->x, R2, R3)
    Q3 = reshape(Bool[ (x->(r2[x] | R3[x]))(i) for i in 1:length(R3) ], size(M3))

    U1 = NullableArray(A1)
    U2 = NullableArray(A2)
    U3 = NullableArray(A3)
    V1 = NullableArray(A1, M1)
    V2 = NullableArray(A2, M2)
    V3 = NullableArray(A3, M3)
    Z2 = NullableArray(Float64, 10, dims...)
    Z3 = NullableArray(Float64, 10, [dims; i]...)

    f() = 5
    f(x::Real) = 5 * x
    f(x::Real, y::Real) = x * y
    f(x::Real, y::Real, z::Real) = x * y * z

    @inferred NullableArrays.lift(f, (1,))
    @inferred NullableArrays.lift(f, (1, 2.0))
    @inferred NullableArrays.lift(f, (1, 2.0, 3.0))

    for (dests, arrays, nullablearrays, mask) in
        ( ((C2, Z2), (A1, A2), (U1, U2), ()),
          ((C3, Z3), (A2, A3), (U2, U3), ()),
          ((C3, Z3), (A1, A2, A3), (U1, U2, U3), ()),

          ((C2, Z2), (A1, A2), (V1, V2), (R2,)),
          ((C3, Z3), (A2, A3), (V2, V3), (R3,)),
          ((C3, Z3), (A1, A2, A3), (V1, V2, V3), (Q3,)),
    )

        # Base.broadcast!(f, B::NullableArray, As::NullableArray...)
        broadcast!(f, dests[1], arrays...)
        broadcast!(f, dests[2], nullablearrays...)
        @test isequal(dests[2], NullableArray(dests[1], mask...))

        # Base.broadcast(f, As::NullableArray...)
        D = broadcast(f, arrays...)
        X = broadcast(f, nullablearrays...)
        @test isequal(X, NullableArray(D, mask...))
    end

    # Base.broadcast!(f, X::NullableArray)
    for (array, nullablearray, mask) in
        ( (A1, U1, ()), (A2, U2, ()), (A3, U3, ()),
          (A1, V1, (M1,)), (A2, V2, (M2,)), (A3, V3, (M3,)),
    )
        broadcast!(f, array)
        broadcast!(f, nullablearray)
        @test isequal(nullablearray, NullableArray(array, mask...))
    end

    # test broadcasted arithmetic operators
    A = rand(10)
    X1 = NullableArray(A)
    n = rand(2:5)
    dims = rand(2:5, n)
    B = rand(Float64, 10, dims...)
    X2 = NullableArray(B)
    M = rand(Bool, 10, dims...)
    Y = NullableArray(B, M)

    if VERSION < v"0.6.0-dev.1632"
        for op in (
            (.+),
            (.-),
            (.*),
            (./),
            (.%),
            (.^),
            (.==),
            (.!=),
            (.<),
            (.>),
            (.<=),
            (.>=),
        )
            @test isequal(op(X1, X2), NullableArray(op(A, B)))
            @test isequal(op(X1, Y), NullableArray(op(A, B), M))
        end
    end

    A = rand(Bool, 100)
    B = rand(Bool, 100)
    M1 = rand(Bool, 100)
    M2 = rand(Bool, 100)
    X = NullableArray(A, M1)
    Y = NullableArray(B, M2)
    @test isequal(broadcast(&, X, Y), NullableArray(A .& B, M1 .| M2))
    @test isequal(broadcast(|, X, Y), NullableArray(A .| B, M1 .| M2))

    # Test broadcasting with constructor
    immutable SurvEvent
        time::Float64
        censored::Bool
    end
    t = NullableArray(rand(3))
    c = NullableArray(rand(Bool, 3))
    @test isequal(SurvEvent.(t, c), NullableArray([SurvEvent(get(t[i]), get(c[i])) for i in 1:3]))
    @test isa(SurvEvent.(t, c), NullableVector{SurvEvent})
    @inferred NullableArrays.lift(SurvEvent, (1, true))

end # module


Base.@propagate_inbounds Base.getindex(A::DSSASolution, i::Int) = [A.u[i], A.channel[i]]
Base.@propagate_inbounds Base.getindex(A::DSSASolution, i::Int, ::Colon) = [A.u[j][i] for j in 1:length(A.t)]

function (A::DSSASolution)(s::Symbol,i::Int)
    if s == :channel
        @assert i <= length(A.channel[1])
        return [A.channel[j][i] for j in 1:length(A.t)]
    elseif s ==:u
        @assert i <= length(A.u[1])
        return [A.u[j][i] for j in 1:length(A.t)]
    end
end

function (A::DSSASolution)(tval)
    @unpack odesol = A
    odesol(tval)
end

(integrator::DSSAIntegrator)(t) = copy(integrator.u)
(integrator::DSSAIntegrator)(out,t) = (out .= integrator.u)

function Base.show(io::IO, m::MIME"text/plain", A::DSSASolution)
    println(io,string("retcode: ",A.odesol.retcode))
    println(io,string("Interpolation: "),DiffEqBase.interp_summary(A.odesol.interp))
    print(io,"t: ")
    show(io,m,A.t)
    println(io)
    print(io,"u: ")
    show(io,m,A.u)
    println(io)
    print(io,"channel: ")
    show(io,m,A.channel)
    println(io,"\n===\nUse sol.u to check the state variable and sol.channel to check the delay channel solution.\n===")
end

# """
#     function get_reaction_idx(rn::ReactionSystem)

# Get the rearranged order for a reaction system, returns a matrix `m` with first column as the rearranged reactions, second column is the order in the orign `ReactionSystem`
# If you know the old idx for a certain reaction in `ReactionSystem`, you can use
# `m[:,2][idx]` to get the new idx.
# """

# function get_reaction_idx(rn::Catalyst.ReactionSystem)
#     massactionjump_order = Int64[]
#     constantjump_order = Int64[]
#     rxvars = []
#     for (i, rx) in enumerate(ModelingToolkit.get_eqs(rn))
#         empty!(rxvars)
#         (Catalyst.reactionrates(rn) isa ModelingToolkit.Symbolic) && ModelingToolkit.get_variables!(rxvars, Catalyst.reactionrates(rn))
#         @inbounds for j = 1:length(rxvars)
#             if isequal(rxvars[j], ModelingToolkit.get_iv(rn))
#                 error("Does not support VariableRateJump")
#             end
#         end
#         if Catalyst.ismassaction(rx, rn)
#             push!(massactionjump_order, i)
#         else
#             push!(constantjump_order, i)
#         end
#     end
#     order = vcat(massactionjump_order, constantjump_order)
#     # println("The rearranged order :")
#     vcat([[ModelingToolkit.get_eqs(rn)[order[i]] order[i]] for i in eachindex(order)]...)
# end
# export get_reaction_idx


#TODO: not used. Plan to remove Plots.jl dependency and add a Plots recipe

@recipe function f(dt::stfitxy,
            framestyle=:axes,
            yminorgrid=true,
            titefont  = font(6, "Computer Modern"),
            ticfont  = font(6, "Computer Modern"),
            guiefont = font(7, "Computer Modern"),
            legndfont = font(6, "Computer Modern"),
            marershape = :circle,
            marersize = 2,
            marerstrokewidth = 0,
            fg_color_legend = nothing,
            size = (600,400),
            dpi = 300,
            margin = 0mm)

    # Default values
    titlefont  := titlefont
    tickfont  := tickfont
    guidefont := guidefont
    legendfont := legendfont
    markershape := markershape
    markersize := markersize
    markerstrokewidth := markerstrokewidth
    fg_color_legend := fg_color_legend
    size := size,
    margin := margin,
    dpi := dpi)

    legend := isnothing(legend) ? (size(x,2) <= 4 ? :outertop : :outerright) : legend

    @series begin
        label := permutedims(names_x)
        color_palette := :tab10
        seriescolor := reshape(collect(1:size(x,2)),1,size(x,2))
        seriestype := :line

        ts,x
    end
end


using SimplePartitions: find_part
import SimpleGraphs

function Base.convert(::Type{SimpleGraph}, d::SimpleDigraph)
    g = SimpleGraph{Symbol}()
    add_edges!(g, elist(d))
    g
end

# Type piracy, this should really go in SimpleGraphs
# OTOH there's not anything else this could really mean
SimpleGraphs.components(g::SimpleDigraph) = SimpleGraphs.components(convert(SimpleGraph, g))

export predictive
function predictive(m::Model, x :: Symbol)
    g = digraph(m) #Creates a new SimpleDigraph, so no need to copy before mutating

    for xparent ∈ in_neighbors(g, x)
        delete!(g, xparent, x)
    end

    # Find the strongly connected component containing x
    part = find_part(SimpleGraphs.components(g), x) |> union

    newargs = (arguments(m) ∪ [x]) ∩ part
    setdiff!(part, newargs)

    theModule = getmodule(m)
    m_init = Model(theModule, newargs, NamedTuple(), NamedTuple(), nothing)
    m = foldl(part; init=m_init) do m0,v
        merge(m0, Model(theModule, findStatement(m, v)))
    end
end

predictive(m::Model, x::Symbol, xs...) = predictive(predictive(m,x), xs...)

export predict

function predict(d::JointDistribution, post::Vector{NamedTuple{N,T}}) where {N,T}
    args = d.args
    m = d.model
    pred = predictive(m, keys(post[1])...)
    map(nt -> rand(pred(merge(args,nt))), post)
end

function predict(m::Model, post::Vector{NamedTuple{N,T}}) where {N,T}
    pred = predictive(m, keys(post[1])...)
    map(nt -> rand(pred(nt)), post)
end

# TODO: These don't yet work properly t on particles

function predict(d::JointDistribution, post::NamedTuple{N,T}) where {N,T}
    args = d.args
    m = d.model
    pred = predictive(m, keys(post)...)
    rand(pred(merge(args,post)))
end

function predict(m::Model, post::NamedTuple{N,T}) where {N,T}
    pred = predictive(m, keys(post)...)
    rand(pred(post))
end

predict(m::Model; kwargs...) = predict(m,(;kwargs...))


using NgSpice
using Test

NgSpice.init()

nl = """
* Diode test circuit
V1 inp 0 DC=1 AC=1 SIN(1 1 1000)
* Connected in forward bias:
D1 inp 0 dmod
.model dmod d cjo=0 m=1 rs=5000 area=1 pj=0 cjp=0 mj=0 mjsw=0 pb=1 php=1 n=2 bv=0 ibv=0 ik=0 ikr=0 is=1.0e-9 tt=0
.save all @d1[id] @d1[cd] @d1[charge] @d1[vd]
.END
"""

@test NgSpice.load_netlist(nl) == 0


export molarmass
# function to calculate the molar mass of an ionic liquid
function molarmass(anion, cation, pdb_dir)
  atcation = readPDB("$pdb_dir/$(cation)_VSIL.pdb")
  atanion  = readPDB("$pdb_dir/$(anion)_VSIL.pdb")
  MMIL = mass(atcation) + mass(atanion)
  return MMIL
end


startingval(e::GenericMomentBasedEstimator) = e.x0
startingval(g::MomentBasedEstimator) = startingval(g.e)

npar(e::GenericMomentBasedEstimator) = e.npar
nmom(e::GenericMomentBasedEstimator) = e.nmom
StatsBase.nobs(e::GenericMomentBasedEstimator) = e.nobs
Base.size(e::GenericMomentBasedEstimator) = (nobs(e), npar(e), nmom(e))

StatsBase.nobs(g::MomentBasedEstimator) = nobs(g.e)
npar(g::MomentBasedEstimator) = npar(g.e)
nmom(g::MomentBasedEstimator) = nmom(g.e)
Base.size(g::MomentBasedEstimator) = (nobs(g.e), npar(g.e), nmom(g.e))

objval(e::MomentBasedEstimator) = e.r.objval

# StatsBase.nobs(m::MomentFunction) = m.nobs
# npar(m::MomentFunction) = m.npar
# nmom(m::MomentFunction) = m.nmom
# Base.size(m::MomentFunction) = (nobs(m), npar(m), nmom(m))

################################################################################
## Constructor with function and x0
################################################################################
function GMMEstimator(f::Function, theta::Vector;
                      grad = nothing,
                      data = nothing,
                      initialW = nothing,
                      wts = nothing,
                      mgr::IterationManager = TwoStepGMM(),
                      constraints = Unconstrained())
    ## Set Moment Function
    g(theta) = data == nothing ? f(theta) : f(theta, data)
    ## Evaluate Moment Function
    g₀ = g(theta)
    n, m, p = (size(g₀)..., length(theta))
    ## Initial Weighting Matrix
    W₀ = initialW == nothing ? Matrix(I, m, m) : initialW
    W  = setW0(mgr, m);
    W[1] = W₀
    ## Weighting
    w = wts == nothing ? Unweighted() : Weighted(float(wts))
    ## Set Default Bounds
    lb  = [-Inf for j=1:p]
    ub  = [+Inf for j=1:p]
    nf  = Float64[]
    ni  = 0::Int64

    if grad == nothing
        mf  = make_fad_mom_fun(g, IdentitySmoother())
    else
        ∇f(theta) = data == nothing ? grad(theta) : grad(theta, data)
        mf  = make_ana_mom_fun(GMMEstimator, g, ∇f)
    end

    MomentBasedEstimator(GMMEstimator(mf, constraints, theta, lb, ub, nf, nf,
                                      mgr, IterationState([1], [10.0], theta), W,
                                      w, ni, ni, n, p, m))
end

const GradTuple = Union{Nothing, Tuple{Function, Function, Function}, Tuple{Function, Function, Function, Function}}

function MDEstimator(f::Function, theta::Vector;
                     grad::GradTuple = nothing,
                     data = nothing, wts = nothing,
                     div::Divergence = DEFAULT_DIVERGENCE,
                     kernel::SmoothingKernel = IdentitySmoother())
    ## Set Moment Function
    g(theta) = data == nothing ? f(theta) : f(theta, data)
    ## Evaluate Moment Function
    g₀ = g(theta)
    n, m, p = (size(g₀)..., length(theta))
    ## Weighting
    w = wts == nothing ? Unweighted() : Weighted(float(wts))
    #=
    Set Default bounds
    =#

    ## -- Bounds on theta           -- ##
    lb  = [-Inf for j=1:p]
    ub  = [+Inf for j=1:p]
    ## -- Bounds on mdweights   -- ##
    wlb = zeros(Float64, n)
    wub = ones(Float64, n)*n
    ## -- Bounds on constraints -- ##
    glb = [zeros(m); n];
    gub = [zeros(m); n];
    ni  = 0::Int64
    if grad == nothing
        mf  = make_fad_mom_fun(g, kernel)
    else
        ff = Array(Function, length(grad))
        if data != nothing
            for (i, f) in enumerate(grad)
                _f = copy(f)
                _g(theta) = _f(theta, data)
                ff[i] = _g(theta)
            end
            grad = (ff...,)
        end
        mf  = make_ana_mom_fun(MDEstimator, g, grad)
    end

    MomentBasedEstimator(MDEstimator(mf, Unconstrained(), theta, lb, ub, glb, gub,
                                     wlb, wub, div, w, ni, ni, n, p, m))
end

################################################################################
## Solve methods
################################################################################
# function solve!(g::MomentBasedEstimator)
# 	   if status(g) == :Uninitialized
# 		      initialize!(g)
# 	   end
# end

function initialize!(g::MomentBasedEstimator{MDEstimator{M, V, S, T}}) where {M<:MomentFunction, V<:Divergence, S<:Unconstrained, T<:Weighting}
	   n, p, m = size(g)
	   ξ₀ = [ones(n); startingval(g)]
	   g.e.gele = Int((n+p)*(m+1)-p)
	   g.e.hele = Int(n*p + n + (p+1)*p/2)
	   g_L = getmfLB(g)
	   g_U = getmfUB(g)
	   u_L = [getwtsLB(g); getparLB(g)]
	   u_U = [getwtsUB(g); getparUB(g)]
	   MathProgBase.loadproblem!(g.m, n+p, m+1, u_L, u_U, g_L, g_U, :Min, g.e)
	   MathProgBase.setwarmstart!(g.m, ξ₀)
	   g.status[1] = :Initialized
end

function initialize!(g::MomentBasedEstimator{GMMEstimator{M, V, S, T}}) where {M<:MomentFunction, V<:IterationManager, S<:Unconstrained, T<:Weighting}
    n, p, m = size(g)
	   ξ₀ = startingval(g)
	   g.e.gele = @compat Int(p)
	   g.e.hele = @compat Int(2*p)
	   g_L = Float64[]
	   g_U = Float64[]
	   u_L = getparLB(g)
	   u_U = getparUB(g)
    MathProgBase.loadproblem!(g.m, p, 0, u_L, u_U, g_L, g_U, :Min, g.e)
	   MathProgBase.setwarmstart!(g.m, ξ₀)
	   g.status[1] = :Initialized
end

function initialize!(g::MomentBasedEstimator{GMMEstimator{M, V, S, T}}) where {M<:MomentFunction, V<:IterationManager, S<:Constrained, T<:Weighting}
    n, p, m = size(g)
	   ξ₀ = MomentBasedEstimators.startingval(g)
	   g.e.gele = @compat Int(g.e.c.nc*p)
	   g.e.hele = @compat Int(0)
	   g_L = g.e.c.hlb
	   g_U = g.e.c.hub
	   u_L = getparLB(g)
	   u_U = getparUB(g)
    MathProgBase.loadproblem!(g.m, p, g.e.c.nc, u_L, u_U, g_L, g_U, :Min, g.e)
	   MathProgBase.setwarmstart!(g.m, ξ₀)
	   g.status[1] = :Initialized
end

################################################################################
## Getters
################################################################################
getparLB(g::MomentBasedEstimator) = g.e.lb
getparUB(g::MomentBasedEstimator) = g.e.ub

getmfLB(g::MomentBasedEstimator) = g.e.glb
getmfUB(g::MomentBasedEstimator) = g.e.gub

getwtsLB(g::MomentBasedEstimator{MDEstimator{M, V, T, S}}) where {M, V, T, S} = g.e.wlb
getwtsUB(g::MomentBasedEstimator{MDEstimator{M, V, T, S}}) where {M, V, T, S} = g.e.wub

################################################################################
## Set constraint on parameters
################################################################################
function check_constraint_sanity(k, x0, h::Function, hlb, hub)
	   h0 = h(x0); nc = length(h0)
	   typeof(h0)  <: Vector{Float64} || error("Constraint function must be ::Vector{Float64}")
	   nc == length(hub) && nc == length(hlb) || error("Constraint bounds of wrong dimension")
	   typeof(hlb) <: Vector{Float64} || (hlb = float(hlb))
	   typeof(hub) <: Vector{Float64} || (hub = float(hub))
	   (hlb, hub, nc)
end

## This return a constrained version of MomentBasedEstimator
function constrained(h::Function, hlb::Vector, hub::Vector, g::MomentBasedEstimator)
    p = npar(g); chk = check_constraint_sanity(p, startingval(g), h, hlb, hub)
    r  = MomentBasedEstimatorResults(:Uninitialized, 0., Array{Float64}(undef, p), Array{Float64}(undef, p, p))
    if typeof(g.e.mf) == MomentBasedEstimators.FADMomFun
        mf  = make_fad_mom_fun(g.e.mf.g, IdentitySmoother())
    else
        mf  = make_ana_mom_fun(GMMEstimator, g.e.mf.g, g.e.mf.∇g)
    end
    ce = GMMEstimator(mf,
                      Constrained(h, chk...),
                      g.e.x0,
                      g.e.lb,
                      g.e.ub,
                      g.e.glb,
                      g.e.gub,
                      g.e.mgr,
                      g.e.ist,
                      g.e.W,
                      g.e.wtg,
                      g.e.gele,
                      g.e.hele,
                      size(g)...)

    MomentBasedEstimator(ce, r, g.s, g.m, :Uninitialized)
end

################################################################################
## Update solver
################################################################################
function solver!(g::MomentBasedEstimator, s::MathProgBase.SolverInterface.AbstractMathProgSolver)
    g.s = s
    g.m = deepcopy(MathProgBase.NonlinearModel(s))
end

################################################################################
## Update lb and up on g(θ) default: (0,...,0)
################################################################################
function setmfLB!(g::MomentBasedEstimator{MDEstimator}, lb::Vector)
	   nmom(g) == length(lb) || error("Dimension error")
	   g.e.glb[:] = lb
end

function setmfUB!(g::MomentBasedEstimator{MDEstimator}, ub::Vector)
	   nmom(g) == length(ub) || error("Dimension error")
	   g.e.glb[:] = ub
end

function setmfbounds!(g::MomentBasedEstimator{MDEstimator}, lb::Vector, ub::Vector)
    setmfLB!(g, lb)
    setmfUB!(g, ub)
end

################################################################################
## Update initial lb and up on parameters(default -inf, +inf)
################################################################################
function setparLB!(g::MomentBasedEstimator{T}, lb::Vector) where T
    npar(g) == length(lb) || error("Dimension error")
    copyto!(g.e.lb, lb)
end

function setparUB!(g::MomentBasedEstimator{T}, ub::Vector) where T
    npar(g) == length(ub) || error("Dimension error")
    copyto!(g.e.ub, ub)
end

function setparbounds!(g::MomentBasedEstimator{T}, lb::Vector, ub::Vector) where T
	   setparLB!(g, lb)
	   setparUB!(g, ub)
end

################################################################################
## Update initial lb and up on mdweights (default 0, n)
################################################################################
function setwtsLB!(g::MomentBasedEstimator{T}, lb::Vector) where T <: MDEstimator
    nobs(g) == length(lb) || error("Dimension error")
    copyto!(g.e.wlb, lb)
end

function setwtsUB!(g::MomentBasedEstimator{T}, ub::Vector) where T <: MDEstimator
    nobs(g) == length(ub) || error("Dimension error")
    copyto!(g.e.wub, ub)
end

function setwtsbounds!(g::MomentBasedEstimator{T}, lb::Vector, ub::Vector) where T <: MDEstimator
    setwtsLB!(g, lb)
    setwtsUB!(g, ub)
end

################################################################################
## Update initial weighting matrix (default is I(m))
################################################################################
## TODO: This should depend on the Iteration Manager
setW0!(g::MomentBasedEstimator{GMMEstimator}, W::Array{Float64, 2}) = copyto!(g.e.W , W)

################################################################################
## Iteration
################################################################################
function set_iteration_manager!(g::MomentBasedEstimator{GMMEstimator}, mgr::IterationManager)
    g.e.mgr = mgr
end

################################################################################
## estimate!
################################################################################
function setx0!(g::MomentBasedEstimator{S}, x0::Vector{Float64}) where S<:GMMEstimator
    ## For GMM x0 is the parameter
    length(x0) == npar(g) || throw(DimensionMismatch(""))
    copyto!(g.e.x0, x0)
    MathProgBase.setwarmstart!(g.m, x0)
end

function setx0!(m::MomentBasedEstimator{S}, x0::Vector{Float64}) where S<:MDEstimator
    ## For GMM x0 is the parameter
    length(x0) == npar(m) || throw(DimensionMismatch(""))
    copyto!(m.e.x0, x0)
    x00 = [m.m.inner.x[1:nobs(m)], x0]
    MathProgBase.setwarmstart!(m.m, x00)
end

function estimate!(g::MomentBasedEstimator)
    ## There are three possible states of g.status
    ## :Uninitialized
    ## :Initialized
    ## :Solved(Success|Failure)
    initialize!(g)
    solve!(g, g.m)
    fill_in_results!(g)
    g
end

function fill_in_results!(me::MomentBasedEstimator{T}) where T<:MDEstimator
    me.r.status = MathProgBase.status(me.m)
    n, p, m = size(me)
    ss = MathProgBase.getsolution(me.m)
    copyto!(me.r.coef, ss[n+1:end])
    ## The ϕ_value is
    ## equal to 2*objval/(S_T(k1^2/k2))
    v  = MathProgBase.getobjval(me.m)
    k1 = κ₁(me)
    k2 = κ₂(me)
    S  = bw(me)
    me.r.objval = 2*k2*v/(S*k1^2)
    me.status[1] = :Solved
end

function fill_in_results!(g::MomentBasedEstimator{S}) where S<:GMMEstimator
    g.r.status = MathProgBase.status(g.m)
    n, p, m = size(g)
    copyto!(g.r.coef, MathProgBase.getsolution(g.m))
    g.r.objval = MathProgBase.getobjval(g.m)
    g.status[1] = :Solved
end

# function solve!(g::MomentBasedEstimator{S}, s::KNITRO.KnitroMathProgModel) where S<:MDEstimator
#     # KNITRO.restartProblem(g.m.inner, startingval(g), g.m.inner.numConstr)
#     # KNITRO.solveProblem(g.m.inner)
#     MathProgBase.optimize!(g.m)
# end

solve!(g::MomentBasedEstimator{S}, s::Ipopt.IpoptMathProgModel) where S<:MDEstimator = MathProgBase.optimize!(g.m)

function solve!(g::MomentBasedEstimator{S}, s::MathProgBase.SolverInterface.AbstractMathProgModel) where S<:GMMEstimator
    reset_iteration_state!(g)
    n, p, m = size(g)
    theta = zeros(p)
    while !(finished(g.e.mgr, g.e.ist))
        if g.e.ist.n[1]>1
            g.e.W[g.e.ist.n[1]][:,:] .= optimal_W(g.e.mf, theta, g.e.mgr.k, g.e.mgr.demean)
        end
        MathProgBase.optimize!(g.m)

        theta = MathProgBase.getsolution(g.m)
        update!(g.e.ist, theta)
        next!(g.e.ist)
        if !(finished(g.e.mgr, g.e.ist))
            MathProgBase.setwarmstart!(g.m, theta)
        end
    end
    fill_in_results!(g)
    g.status[1] = :Solved
    g
end

next!(x::IterationState) = x.n[1] += 1

function update!(x::IterationState, v::Vector)
    x.change[:] .= maximum(abs, x.prev - v)
    x.prev[:] .= v
end

reset_iteration_state!(g::MomentBasedEstimator) =
    g.e.ist = deepcopy(IterationState([1], [10.0], startingval(g)))

function optimal_W(g::Array{Float64, 2}, theta::Vector, k::RobustVariance, demean::Bool)
    h = demean ? g .- mean(g, 1) : g
    n = size(h, 1)
    S = CovarianceMatrices.vcov(h, k) * n
    pinv(S)
end

function optimal_W(mf::MomentFunction, theta::Vector, k::RobustVariance, demean::Bool)
    g = mf.s(theta)
    optimal_W(g, theta, k, demean)
end

function optimal_W(mf::Function, theta::Vector, k::RobustVariance, demean::Bool)
    g = mf(theta)
    optimal_W(g, theta, k, demean)
end

function optimal_W(e::MomentBasedEstimator, k::RobustVariance, demean::Bool = false)
    optimal_W(e.e.mf, coef(e), k, demean)
end


# ClusterManager for HTCondor

export LocalAffinityManager, AffinityMode, COMPACT, BALANCED

@enum AffinityMode COMPACT BALANCED

mutable struct LocalAffinityManager <: ClusterManager
    affinities::Array{Int}

    function LocalAffinityManager(;np=Sys.CPU_THREADS, mode::AffinityMode=BALANCED, affinities::Array{Int}=[])
        @assert(Sys.Sys.KERNEL == :Linux)

        if length(affinities) == 0
            if mode == COMPACT
                affinities = [i%Sys.CPU_THREADS for i in 1:np]
            else
                # mode == BALANCED
                if np > 1
                    affinities = [Int(floor(i)) for i in range(0, stop=Sys.CPU_THREADS - 1e-3, length=np)]
                else
                    affinities = [0]
                end
            end
        end

        return new(affinities)
    end
end

function launch(manager::LocalAffinityManager, params::Dict, launched::Array, c::Condition)
    dir = params[:dir]
    exename = params[:exename]
    exeflags = params[:exeflags]

    for core_id in manager.affinities
        io = open(detach(
            setenv(`taskset -c $core_id $(Base.julia_cmd(exename)) $exeflags $(worker_arg())`, dir=dir)), "r")
        wconfig = WorkerConfig()
        wconfig.process = io
        wconfig.io = io.out
        push!(launched, wconfig)
    end

    notify(c)
end

function manage(manager::LocalAffinityManager, id::Integer, config::WorkerConfig, op::Symbol)
    if op == :interrupt
        kill(get(config.process), 2)
    end
end


println("Running tests on ", gethostname(), ".")
using Test, Random, LinearAlgebra, Statistics
using QMC
using KrylovKit, BlockArrays, MonteCarloObservable, FFTW

@testset "All Tests" begin

    # General tests
    include("tests_linalg.jl")
    include("tests_statistics.jl")

    # O3 model
    include("tests_O3.jl")

    # no interactions
    include("tests_freefermions.jl")

    # Resume
    # include("tests_resume.jl")

end

nothing


insts = OrderedDict()
insts["minimal"] = [
    ((2, false),),
    ((2, true),),
    ((4, false),),
    ((4, true),),
    ]
insts["fast"] = [
    ((50, false),),
    ((50, true),),
    ((1000, false),),
    ((250, true),),
    ]
insts["various"] = [
    ((1000, false),),
    ((500, true),),
    ((4000, false),),
    ((1000, true),),
    ((8000, false),),
    ((2000, true),),
    ((10000, false),),
    ]
return (DiscreteMaxLikelihood, insts)


export AbstractSequence, AbstractMultiEchoSequence, echoAmplitudes, numEchoes, echoTimes, sequence, sequenceInfo

abstract type AbstractSequence end

# define interface for a sequence
@mustimplement echoAmplitudes(seq::AbstractSequence,R1::AbstractFloat,R2::AbstractFloat)
@mustimplement numContrasts(seq::AbstractSequence)
@mustimplement string(seq::AbstractSequence)

include("BlochSimulation.jl")
include("MultiEcho.jl")

function sequence(seqName::AbstractString, numProfiles, numSamplingPerProfile; kargs...)
  if seqName == "ME"
    return MESequence(; kargs...)
  else
    error("The sequence $seqName is not yet supported!")
  end

  return MESequence(; kargs...)
end

function sequenceInfo(seq::AbstractSequence)
  param = Dict{Symbol,Any}()
  param[:name] = string(seq)
  for field in fieldnames(typeof(seq))
    param[field] = getfield(seq,field)
  end
  return param
end


import Base: eof, close
export BinningMap, close, value, position, eof, advance!

type BinningMap
    reader::SequenceReader
    binSize::Int64
    position::Int64
    value::Float64
    skipDup::Bool
end

function BinningMap(reader::SequenceReader, binSize; skipDup=true)
    fm = BinningMap(reader, binSize, 0, 0.0, skipDup)
    advance!(fm)
    fm
end
close(fm::BinningMap) = close(fm.reader)
value(fm::BinningMap) = fm.value
position(fm::BinningMap) = fm.position
eof(fm::BinningMap) = fm.position <= 0

function advance!(fm::BinningMap)
    fm.position = floor((fm.reader.position-1)/fm.binSize) + 1
    binEnd = fm.position*fm.binSize

    # Fill in the bin
    fm.value = 0.0
    lastPos = -1
    while fm.reader.position != -1 && fm.reader.position <= binEnd
        if !fm.skipDup || fm.reader.position != lastPos
            fm.value += 1
        end
        lastPos = fm.reader.position
        advance!(fm.reader)
    end
end


import FileIO: open, close, save, @format_str, Stream
import IndirectArrays: IndirectArray
import MappedArrays: mappedarray
import Colors: colormap, RGB
import ImageContainers: ImageContainer, storeimage

const CURRENT_COLORMAP = colormap("Blues", 256)
current_colormap() = CURRENT_COLORMAP

function set_colormap(cmap::AbstractVector)
    @assert length(cmap)==256 "Number of gradation must be 256"
    @. CURRENT_COLORMAP = RGB(cmap)
end

function matshow(A::AbstractMatrix; f::Function=x->clamp(1+floor(Int, 256*x), 1, 256))
    #ImageContainer{:jlc}(IndirectArray(mappedarray(f, A), CURRENT_COLORMAP))
    storeimage(:jlc, IndirectArray(mappedarray(f, A), CURRENT_COLORMAP))
end

function addframe(io, img::ImageContainer{:jlc})
    save(Stream(format"BMP", io), img.content)
end

function openanim(func::Function, fmt::Symbol=:gif)
    fin = Pipe()
    fout = IOBuffer()

    if fmt == :mp4
        fproc = run(
            pipeline(`ffmpeg -v 0 -i - -pix_fmt yuv420p -f matroska -`,
                stdin=fin, stdout=fout),
            wait=false)

        try
            func(fin)
        finally
            close(fin)
        end

        wait(fproc)
        close(fproc)
        return ImageContainer{:mp4}(take!(fout))

    elseif fmt == :gif
        palette = tempname() * ".bmp"
        save(palette, IndirectArray(reshape(1:256, 16, 16)', SimpleHeatmaps.CURRENT_COLORMAP))

        fproc = run(
            pipeline(`ffmpeg -v 0 -i - -i $palette -lavfi paletteuse=dither=sierra2_4a -f gif -`,
                stdin=fin, stdout=fout),
            wait=false)

        try
            func(fin)
        finally
            close(fin)
        end

        wait(fproc)
        close(fproc)
        return ImageContainer{:gif}(take!(fout))

    else
        error("Only support mp4 / gif: $fmt")
    end
end


### AMSMMALA state subtypes

## MuvAMSMMALAState holds the internal state ("local variables") of the AMSMMALA sampler for multivariate parameters

type MuvAMSMMALAState <: MuvPSMMALAState
  pstate::ParameterState{Continuous, Multivariate}
  tune::MCTunerState
  sqrttunestep::Real
  ratio::Real
  μ::RealVector
  lastmean::RealVector
  secondlastmean::RealVector
  newinvtensor::RealMatrix
  oldinvtensor::RealMatrix
  cholinvtensor::RealLowerTriangular
  newfirstterm::RealVector
  oldfirstterm::RealVector
  presentupdatetensor::Bool
  pastupdatetensor::Bool
  count::Integer
  updatetensorcount::Integer

  function MuvAMSMMALAState(
    pstate::ParameterState{Continuous, Multivariate},
    tune::MCTunerState,
    sqrttunestep::Real,
    ratio::Real,
    μ::RealVector,
    lastmean::RealVector,
    secondlastmean::RealVector,
    newinvtensor::RealMatrix,
    oldinvtensor::RealMatrix,
    cholinvtensor::RealLowerTriangular,
    newfirstterm::RealVector,
    oldfirstterm::RealVector,
    presentupdatetensor::Bool,
    pastupdatetensor::Bool,
    count::Integer,
    updatetensorcount::Integer
  )
    if !isnan(ratio)
      @assert 0 < ratio < 1 "Acceptance ratio should be between 0 and 1"
      @assert sqrttunestep > 0 "Square root of tuned drift step is not positive"
    end
    new(
      pstate,
      tune,
      sqrttunestep,
      ratio,
      μ,
      lastmean,
      secondlastmean,
      newinvtensor,
      oldinvtensor,
      cholinvtensor,
      newfirstterm,
      oldfirstterm,
      presentupdatetensor,
      pastupdatetensor,
      count,
      updatetensorcount
    )
  end
end

MuvAMSMMALAState(pstate::ParameterState{Continuous, Multivariate}, tune::MCTunerState=PSMMALAMCTune()) =
  MuvAMSMMALAState(
  pstate,
  tune,
  NaN,
  NaN,
  Array(eltype(pstate), pstate.size),
  Array(eltype(pstate), pstate.size),
  Array(eltype(pstate), pstate.size),
  Array(eltype(pstate), pstate.size, pstate.size),
  Array(eltype(pstate), pstate.size, pstate.size),
  RealLowerTriangular(Array(eltype(pstate), pstate.size, pstate.size)),
  Array(eltype(pstate), pstate.size),
  Array(eltype(pstate), pstate.size),
  false,
  false,
  0,
  0
)

### Metropolis-adjusted Langevin Algorithm (AMSMMALA)

immutable AMSMMALA <: PSMMALA
  driftstep::Real
  update!::Function
  transform::Union{Function, Void}
  t0::Integer

  function AMSMMALA(driftstep::Real, update!::Function, transform::Union{Function, Void}, t0::Integer)
    @assert driftstep > 0 "Drift step is not positive"
    @assert t0 > 0 "t0 is not positive"
    new(driftstep, update!, transform, t0)
  end
end

AMSMMALA(driftstep::Real=1.; update::Function=rand_update!, transform::Union{Function, Void}=nothing, t0::Integer=3) =
  AMSMMALA(driftstep, update, transform, t0)

### Initialize AMSMMALA sampler

## Initialize parameter state

function initialize!(
  pstate::ParameterState{Continuous, Multivariate},
  parameter::Parameter{Continuous, Multivariate},
  sampler::AMSMMALA,
  outopts::Dict
)
  parameter.uptotensorlogtarget!(pstate)
  if sampler.transform != nothing
    pstate.tensorlogtarget = sampler.transform(pstate.tensorlogtarget)
  end
  pstate.diagnosticvalues[1] = false
  @assert isfinite(pstate.logtarget) "Log-target not finite: initial value out of support"
  @assert all(isfinite(pstate.gradlogtarget)) "Gradient of log-target not finite: initial values out of support"
  @assert all(isfinite(pstate.tensorlogtarget)) "Tensor of log-target not finite: initial values out of support"

  if !isempty(outopts[:diagnostics])
    pstate.diagnostickeys = copy(outopts[:diagnostics])
    pstate.diagnosticvalues = Array(Any, length(pstate.diagnostickeys))
  end
end

## Initialize AMSMMALA state

tuner_state(sampler::AMSMMALA, tuner::PSMMALAMCTuner) =
  PSMMALAMCTune(
    BasicMCTune(NaN, 0, 0, tuner.smmalatuner.period),
    BasicMCTune(NaN, 0, 0, tuner.malatuner.period),
    BasicMCTune(sampler.driftstep, 0, 0, tuner.totaltuner.period)
  )

function sampler_state(
  parameter::Parameter{Continuous, Multivariate},
  sampler::AMSMMALA,
  tuner::MCTuner,
  pstate::ParameterState{Continuous, Multivariate},
  vstate::VariableStateVector
)
  sstate = MuvAMSMMALAState(generate_empty(pstate, parameter.diffmethods, parameter.diffopts), tuner_state(sampler, tuner))
  sstate.sqrttunestep = sqrt(sampler.driftstep)
  sstate.lastmean = copy(pstate.value)
  sstate.oldinvtensor = inv(pstate.tensorlogtarget)
  sstate.cholinvtensor = ctranspose(chol(Hermitian(sstate.oldinvtensor)))
  sstate.oldfirstterm = sstate.oldinvtensor*pstate.gradlogtarget
  sstate.presentupdatetensor = true
  sstate.pastupdatetensor = false
  sstate
end

### Reset AMSMMALA sampler

## Reset parameter state

function reset!(
  pstate::ParameterState{Continuous, Multivariate},
  x::RealVector,
  parameter::Parameter{Continuous, Multivariate},
  sampler::AMSMMALA
)
  pstate.value = copy(x)
  parameter.uptotensorlogtarget!(pstate)
  pstate.diagnosticvalues[1] = false
end

## Reset sampler state

function reset!(
  sstate::MuvAMSMMALAState,
  pstate::ParameterState{Continuous, Multivariate},
  parameter::Parameter{Continuous, Multivariate},
  sampler::AMSMMALA,
  tuner::MCTuner
)
  reset!(sstate.tune, sampler, tuner)
  sstate.sqrttunestep = sqrt(sampler.driftstep)
  sstate.lastmean = copy(pstate.value)
  sstate.oldinvtensor = inv(pstate.tensorlogtarget)
  sstate.cholinvtensor = chol(sstate.oldinvtensor, Val{:L})
  sstate.oldfirstterm = sstate.oldinvtensor*pstate.gradlogtarget
  sstate.presentupdatetensor = true
  sstate.pastupdatetensor = false
end

Base.show(io::IO, sampler::AMSMMALA) = print(io, "AMSMMALA sampler: drift step = $(sampler.driftstep)")

Base.show(io::IO, ::MIME"text/plain", sampler::AMSMMALA) = show(io, sampler)


module TestExamples2DAPE

using Test
using Trixi

include("test_trixi.jl")

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

@testset "Acoustic Perturbation" begin
  @trixi_testset "elixir_ape_convergence.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_ape_convergence.jl"),
      l2 = [0.0019921138796370834, 0.002090394698052287, 0.0006091925854593805, 0.0, 0.0, 0.0, 0.0],
      linf = [0.00769282588065634, 0.008276649669227254, 0.004196479023954813, 0.0, 0.0, 0.0, 0.0])
  end

  @trixi_testset "elixir_ape_gauss.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_ape_gauss.jl"),
      l2 = [0.08005276517890283, 0.08005276517890268, 0.4187202920734123, 0.0, 0.0, 0.0, 0.0],
      linf = [0.17261097190220992, 0.17261097190220973, 1.13601894068238, 0.0, 0.0, 0.0, 0.0])
  end

  @trixi_testset "elixir_ape_gaussian_source.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_ape_gaussian_source.jl"),
      l2 = [0.004296394903650806, 0.004241280404758938, 0.006269684906035964, 0.0, 0.0, 0.0, 0.0],
      linf = [0.03970270697049378, 0.04151096349298151, 0.0640019829058819, 0.0, 0.0, 0.0, 0.0])
  end

  @trixi_testset "elixir_ape_gauss_wall.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_ape_gauss_wall.jl"),
      l2 = [0.019419398248465843, 0.019510701017551826, 0.04818246051887614,
            7.382060834820337e-17, 0.0, 1.4764121669640674e-16, 1.4764121669640674e-16],
      linf = [0.18193631937316496, 0.1877464607867628, 1.0355388011792845,
              2.220446049250313e-16, 0.0, 4.440892098500626e-16, 4.440892098500626e-16])
  end

  @trixi_testset "elixir_ape_monopole.jl" begin
    @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_ape_monopole.jl"),
      l2 = [0.006816790293009947, 0.0065068948357351625, 0.008724512056168938,
            0.0009894398191644543, 0.0, 7.144325530679576e-17, 7.144325530679576e-17],
      linf = [1.000633375007386, 0.5599788929862504, 0.5738432957070382,
              0.015590137026938428, 0.0, 2.220446049250313e-16, 2.220446049250313e-16],
      maxiters=50)
  end
end

end # module


__precompile__()

module Spice

using Requests
import Requests: get

include("installer.jl")
include("spice_funcs.jl")

vma = VERSION.major
vmi = VERSION.minor
juliaversion = "v$vma.$vmi"
install_path = joinpath(homedir(), ".julia", juliaversion, "Spice")

@static if is_linux()
  const sharedLib = joinpath(install_path, "cspice", "lib", "spice.so")
end

@static if is_apple()
  const sharedLib = joinpath(install_path, "cspice", "lib", "spice.dylib")
end

@static if is_windows()
  const sharedLib = joinpath(install_path, "cspice", "lib", "spice.dll")
end

export furnsh,
       pxform,
       unload,
       utc2et,
       scs2e,
       vsep,
       str2et,
       spkpos,
       timout,
       reclat,
       init_spice,
       sharedLib

end


"""
    axes_types(::Type{T}, dim)

Returns the axis type along dimension `dim`.
"""
axes_types(x, dim) = axes_types(typeof(x), dim)
@inline axes_types(::Type{T}, dim) where {T} = axes_types(T, to_dims(T, dim))
@inline function axes_types(::Type{T}, dim::StaticInt{D}) where {T,D}
    if D > ndims(T)
        return OptionallyStaticUnitRange{One,One}
    else
        return _get_tuple(axes_types(T), dim)
    end
end
@inline function axes_types(::Type{T}, dim::Int) where {T}
    if dim > ndims(T)
        return OptionallyStaticUnitRange{One,One}
    else
        return axes_types(T).parameters[dim]
    end
end

"""
    axes_types(::Type{T}) -> Type

Returns the type of the axes for `T`
"""
axes_types(x) = axes_types(typeof(x))
axes_types(::Type{T}) where {T<:Array} = Tuple{Vararg{OneTo{Int},ndims(T)}}
function axes_types(::Type{T}) where {T}
    if parent_type(T) <: T
        return Tuple{Vararg{OptionallyStaticUnitRange{One,Int},ndims(T)}}
    else
        return axes_types(parent_type(T))
    end
end
axes_types(::Type{LinearIndices{N,R}}) where {N,R} = R
axes_types(::Type{CartesianIndices{N,R}}) where {N,R} = R
function axes_types(::Type{T}) where {T<:Union{Adjoint,Transpose}}
    P = parent_type(T)
    return Tuple{axes_types(P, static(2)), axes_types(P, static(1))}
end
function axes_types(::Type{T}) where {T<:PermutedDimsArray}
    return eachop_tuple(_get_tuple, to_parent_dims(T), axes_types(parent_type(T)))
end
function axes_types(::Type{T}) where {T<:AbstractRange}
    if known_length(T) === nothing
        return Tuple{OptionallyStaticUnitRange{One,Int}}
    else
        return Tuple{OptionallyStaticUnitRange{One,StaticInt{known_length(T)}}}
    end
end
function axes_types(::Type{T}) where {N,T<:Base.ReshapedArray{<:Any,N}}
    return Tuple{Vararg{OptionallyStaticUnitRange{One,Int},N}}
end

@inline function axes_types(::Type{T}) where {N,P,I,T<:SubArray{<:Any,N,P,I}}
    return eachop_tuple(_sub_axis_type, to_parent_dims(T), T)
end
@inline function _sub_axis_type(::Type{A}, dim::StaticInt) where {T,N,P,I,A<:SubArray{T,N,P,I}}
    return OptionallyStaticUnitRange{
        _int_or_static_int(known_first(axes_types(P, dim))),
        _int_or_static_int(known_length(_get_tuple(I, dim)))
    }
end

function axes_types(::Type{R}) where {T,N,S,A,R<:ReinterpretArray{T,N,S,A}}
    if _is_reshaped(R)
        if sizeof(S) === sizeof(T)
            return axes_types(A)
        elseif sizeof(S) > sizeof(T)
            return eachop_tuple(_reshaped_axis_type, to_parent_dims(R), R)
        else
            return eachop_tuple(axes_types, to_parent_dims(R), A)
        end
    else
        return eachop_tuple(_non_reshaped_axis_type, to_parent_dims(R), R)
    end
end

function _reshaped_axis_type(::Type{R}, dim::StaticInt) where {T,N,S,A,R<:ReinterpretArray{T,N,S,A}}
    return axes_types(parent_type(R), dim)
end
function _reshaped_axis_type(::Type{R}, dim::Zero) where {T,N,S,A,R<:ReinterpretArray{T,N,S,A}}
    return OptionallyStaticUnitRange{One,StaticInt{div(sizeof(S), sizeof(T))}}
end

function _non_reshaped_axis_type(::Type{R}, dim::StaticInt) where {T,N,S,A,R<:ReinterpretArray{T,N,S,A}}
    return axes_types(parent_type(R), dim)
end
function _non_reshaped_axis_type(::Type{R}, dim::One) where {T,N,S,A,R<:ReinterpretArray{T,N,S,A}}
    paxis = axes_types(A, dim)
    len = known_length(paxis)
    if len === nothing
        raxis = OptionallyStaticUnitRange{One,Int}
    else
        raxis = OptionallyStaticUnitRange{One,StaticInt{div(len * sizeof(S), sizeof(T))}}
    end
    return similar_type(paxis, Int, raxis)
end

#=
    similar_type(orignal_type, new_data_type)
=#
similar_type(::Type{OneTo{Int}}, ::Type{Int}, ::Type{OneTo{Int}}) = OneTo{Int}
similar_type(::Type{OneTo{Int}}, ::Type{Int}, ::Type{OptionallyStaticUnitRange{One,Int}}) = OneTo{Int}
similar_type(::Type{OneTo{Int}}, ::Type{Int}, ::Type{OptionallyStaticUnitRange{One,StaticInt{N}}}) where {N} = OptionallyStaticUnitRange{One,StaticInt{N}}

similar_type(::Type{OptionallyStaticUnitRange{One,Int}}, ::Type{Int}, ::Type{OneTo{Int}}) = OptionallyStaticUnitRange{One,Int}
similar_type(::Type{OptionallyStaticUnitRange{One,Int}}, ::Type{Int}, ::Type{OptionallyStaticUnitRange{One,Int}}) = OptionallyStaticUnitRange{One,Int}
similar_type(::Type{OptionallyStaticUnitRange{One,Int}}, ::Type{Int}, ::Type{OptionallyStaticUnitRange{One,StaticInt{N}}}) where {N} = OptionallyStaticUnitRange{One,StaticInt{N}}

similar_type(::Type{OptionallyStaticUnitRange{One,StaticInt{N}}}, ::Type{Int}, ::Type{OneTo{Int}}) where {N} = OptionallyStaticUnitRange{One,Int}
similar_type(::Type{OptionallyStaticUnitRange{One,StaticInt{N}}}, ::Type{Int}, ::Type{OptionallyStaticUnitRange{One,Int}}) where {N} = OptionallyStaticUnitRange{One,Int}
similar_type(::Type{OptionallyStaticUnitRange{One,StaticInt{N1}}}, ::Type{Int}, ::Type{OptionallyStaticUnitRange{One,StaticInt{N2}}}) where {N1,N2} = OptionallyStaticUnitRange{One,StaticInt{N2}}

"""
    axes(A, d)

Return a valid range that maps to each index along dimension `d` of `A`.
"""
axes(a, dim) = axes(a, to_dims(a, dim))
axes(a, dims::Tuple{Vararg{Any,K}}) where {K} = (axes(a, first(dims)), axes(a, tail(dims))...)
axes(a, dims::Tuple{T}) where {T} = (axes(a, first(dims)), )
axes(a, ::Tuple{}) = ()
function axes(a::A, dim::Integer) where {A}
    if parent_type(A) <: A
        return Base.axes(a, Int(dim))
    else
        return axes(parent(a), to_parent_dims(A, dim))
    end
end
function axes(A::CartesianIndices{N}, dim::Integer) where {N}
    if dim > N
        return static(1):static(1)
    else
        return getfield(axes(A), Int(dim))
    end
end
function axes(A::LinearIndices{N}, dim::Integer) where {N}
    if dim > N
        return static(1):static(1)
    else
        return getfield(axes(A), Int(dim))
    end
end

axes(A::SubArray, dim::Integer) = Base.axes(A, Int(dim))  # TODO implement ArrayInterface version
axes(A::ReinterpretArray, dim::Integer) = Base.axes(A, Int(dim))  # TODO implement ArrayInterface version
axes(A::Base.ReshapedArray, dim::Integer) = Base.axes(A, Int(dim))  # TODO implement ArrayInterface version

"""
    axes(A)

Return a tuple of ranges where each range maps to each element along a dimension of `A`.
"""
@inline function axes(a::A) where {A}
    if parent_type(A) <: A
        return Base.axes(a)
    else
        return axes(parent(a))
    end
end
axes(A::PermutedDimsArray) = permute(axes(parent(A)), to_parent_dims(A))
axes(A::Union{Transpose,Adjoint}) = _axes(A, parent(A))
_axes(A::Union{Transpose,Adjoint}, p::AbstractVector) = (One():One(), axes(p, One()))
_axes(A::Union{Transpose,Adjoint}, p::AbstractMatrix) = (axes(p, StaticInt(2)), axes(p, One()))
axes(A::SubArray) = Base.axes(A)  # TODO implement ArrayInterface version
axes(A::ReinterpretArray) = Base.axes(A)  # TODO implement ArrayInterface version
axes(A::Base.ReshapedArray) = Base.axes(A)  # TODO implement ArrayInterface version
axes(A::CartesianIndices) = A.indices
axes(A::LinearIndices) = A.indices

"""
    LazyAxis{N}(parent::AbstractArray)

A lazy representation of `axes(parent, N)`.
"""
struct LazyAxis{N,P} <: AbstractUnitRange{Int}
    parent::P

    LazyAxis{N}(parent::P) where {N,P} = new{N::Int,P}(parent)
    @inline function LazyAxis{:}(parent::P) where {P}
        if ndims(P) === 1
            return new{1,P}(parent)
        else
            return new{:,P}(parent)
        end
    end
end

@inline Base.parent(x::LazyAxis{N,P}) where {N,P} = axes(getfield(x, :parent), static(N))
@inline function Base.parent(x::LazyAxis{:,P}) where {P}
    return eachindex(IndexLinear(), getfield(x, :parent))
end

@inline parent_type(::Type{LazyAxis{N,P}}) where {N,P} = axes_types(P, static(N))
# TODO this approach to parent_type(::Type{LazyAxis{:}}) is a bit hacky. Something like
# LabelledArrays has a linear set of symbolic keys, which could be propagated through
# `to_indices` for key based indexing. However, there currently isn't a good way of handling
# that when the linear indices aren't linearly accessible through a child array (e.g, adjoint)
# For now we just make sure the linear elements are accurate.
parent_type(::Type{LazyAxis{:,P}}) where {P<:Array} = OneTo{Int}
@inline function parent_type(::Type{LazyAxis{:,P}}) where {P}
    if known_length(P) === nothing
        return OptionallyStaticUnitRange{StaticInt{1},Int}
    else
        return OptionallyStaticUnitRange{StaticInt{1},StaticInt{known_length(P)}}
    end
end

Base.keys(x::LazyAxis) = keys(parent(x))

Base.IndexStyle(::Type{T}) where {T<:LazyAxis} = IndexStyle(parent_type(T))

can_change_size(::Type{LazyAxis{N,P}}) where {N,P} = can_change_size(P)

known_first(::Type{T}) where {T<:LazyAxis} = known_first(parent_type(T))

known_length(::Type{LazyAxis{N,P}}) where {N,P} = known_size(P, N)
known_length(::Type{LazyAxis{:,P}}) where {P} = known_length(P)

@inline function known_last(::Type{T}) where {T<:LazyAxis}
    return _lazy_axis_known_last(known_first(T), known_length(T))
end
_lazy_axis_known_last(start::Int, length::Int) = (length + start) - 1
_lazy_axis_known_last(::Any, ::Any) = nothing

@inline function Base.first(x::LazyAxis{N})::Int where {N}
    if known_first(x) === nothing
        return offsets(getfield(x, :parent), static(N))
    else
        return known_first(x)
    end
end
@inline function Base.first(x::LazyAxis{:})::Int
    if known_first(x) === nothing
        return firstindex(getfield(x, :parent))
    else
        return known_first(x)
    end
end

@inline function Base.length(x::LazyAxis{N})::Int where {N}
    if known_length(x) === nothing
        return size(getfield(x, :parent), static(N))
    else
        return known_length(x)
    end
end
@inline function Base.length(x::LazyAxis{:})::Int
    if known_length(x) === nothing
        return lastindex(getfield(x, :parent))
    else
        return known_length(x)
    end
end

@inline function Base.last(x::LazyAxis)::Int
    if known_last(x) === nothing
        if known_first(x) === 1
            return length(x)
        else
            return (static_length(x) + static_first(x)) - 1
        end
    else
        return known_last(x)
    end
end

Base.to_shape(x::LazyAxis) = length(x)

@inline function Base.checkindex(::Type{Bool}, x::LazyAxis, i::Integer)
    if known_first(x) === nothing || known_last(x) === nothing
        return checkindex(Bool, parent(x), i)
    else  # everything is static so we don't have to retrieve the axis
        return (!(known_first(x) > i) || !(known_last(x) < i))
    end
end

@propagate_inbounds function Base.getindex(x::LazyAxis, i::Integer)
    @boundscheck checkindex(Bool, x, i) || throw(BoundsError(x, i))
    return Int(i)
end
@propagate_inbounds Base.getindex(x::LazyAxis, i::StepRange{T}) where {T<:Integer} = parent(x)[i]
@propagate_inbounds Base.getindex(x::LazyAxis, i::AbstractUnitRange{<:Integer}) = parent(x)[i]

Base.show(io::IO, x::LazyAxis{N}) where {N} = print(io, "LazyAxis{$N}($(parent(x))))")

"""
    lazy_axes(x)

Produces a tuple of axes where each axis is constructed lazily. If an axis of `x` is already
constructed or it is simply retrieved.
"""
@generated function lazy_axes(x::X) where {X}
    Expr(:block,
         Expr(:meta, :inline),
         Expr(:tuple, [:(LazyAxis{$dim}(x)) for dim in 1:ndims(X)]...)
    )
end
lazy_axes(x::LinearIndices) = axes(x)
lazy_axes(x::CartesianIndices) = axes(x)
@inline lazy_axes(x::MatAdjTrans) = reverse(lazy_axes(parent(x)))
@inline lazy_axes(x::VecAdjTrans) = (LazyAxis{1}(x), first(lazy_axes(parent(x))))
@inline lazy_axes(x::PermutedDimsArray) = permute(lazy_axes(parent(x)), to_parent_dims(A))