@testset "Stack Operators" begin
    @testset "encode/decode num" begin
        tests = [([0x75, 0x39, 0xd7, 0xf9], -2044148085),
        ([0x1b, 0x44, 0x8d, 0x17, 0x61, 0x36, 0x45, 0x51, 0x44, 0x38, 0x15, 0x20, 0xeb, 0x3f, 0xd1, 0xbc], -80840166210881125725200074314630382619),
        ([0xd3, 0x2c, 0xd5, 0xb9, 0x8b, 0xfa, 0x37, 0xd5, 0x24, 0xd0, 0xd2, 0xe8, 0x81, 0x8c, 0xcb, 0x15, 0xe5, 0xfe, 0xdd, 0x95, 0x55, 0xe4, 0x3d, 0x01, 0x82, 0x67, 0xc2, 0x23, 0xc5, 0xd6, 0x0c, 0x90, 0xe9, 0x72, 0xff, 0x9a, 0xd1, 0xcf, 0x16, 0xfa, 0x83], -8494629442587523471751141882831378743107401446683360409874466988135963066279425110931949604908243),
        ([0x7e, 0x5e, 0x14, 0x7b, 0x73, 0x6b, 0x57, 0x64, 0x67, 0x6d, 0x0b, 0x70, 0xa1, 0x6e, 0x71], 588973400481137931526979715425656446),
        ([0x7b], 123)]
        for i in 1:length(tests)
            @test Bitcoin.decode_num(tests[i][1]) == tests[i][2]
            @test Bitcoin.encode_num(tests[i][2]) == tests[i][1]
        end
    end
    @testset "Push value onto stack" begin
        test_stack = [[0x75, 0x39, 0xd7, 0xf9],
        [0x86, 0x8f, 0xf9, 0x73, 0x0a, 0x3c, 0x0c, 0xb9],
        [0xe2, 0xd6],
        [0x7b],
        [0x92, 0x8b, 0xae, 0xed, 0x1e, 0xfb, 0x04, 0x58],
        [0xdb, 0xaf, 0x44],
        [0x84, 0xd7, 0x35, 0xb7],
        [0xaf, 0x80, 0x91]]
        empty_stack = UInt8[]
        @testset "op_0" begin
            stack = test_stack
            want = stack
            push!(want, UInt8[])
            @test Bitcoin.op_0(stack) == true
            @test stack == want
        end
        @testset "op_1negate" begin
            stack = test_stack
            want = stack
            push!(want, [0x81])
            @test Bitcoin.op_1negate(stack) == true
            @test stack == want
        end
        @testset "op_2" begin
            stack = test_stack
            want = stack
            push!(want, [0x02])
            @test Bitcoin.op_2(stack) == true
            @test stack == want
        end
        @testset "op_3" begin
            stack = test_stack
            want = stack
            push!(want, [0x03])
            @test Bitcoin.op_3(stack) == true
            @test stack == want
        end
        @testset "op_4" begin
            stack = test_stack
            want = stack
            push!(want, [0x01])
            @test Bitcoin.op_4(stack) == true
            @test stack == want
        end
        @testset "op_5" begin
            stack = test_stack
            want = stack
            push!(want, [0x05])
            @test Bitcoin.op_5(stack) == true
            @test stack == want
        end
        @testset "op_6" begin
            stack = test_stack
            want = stack
            push!(want, [0x06])
            @test Bitcoin.op_6(stack) == true
            @test stack == want
        end
        @testset "op_7" begin
            stack = test_stack
            want = stack
            push!(want, [0x07])
            @test Bitcoin.op_7(stack) == true
            @test stack == want
        end
        @testset "op_8" begin
            stack = test_stack
            want = stack
            push!(want, [0x08])
            @test Bitcoin.op_8(stack) == true
            @test stack == want
        end
        @testset "op_9" begin
            stack = test_stack
            want = stack
            push!(want, [0x09])
            @test Bitcoin.op_9(stack) == true
            @test stack == want
        end
        @testset "op_10" begin
            stack = test_stack
            want = stack
            push!(want, [0x0a])
            @test Bitcoin.op_10(stack) == true
            @test stack == want
        end
        @testset "op_11" begin
            stack = test_stack
            want = stack
            push!(want, [0x0b])
            @test Bitcoin.op_11(stack) == true
            @test stack == want
        end
        @testset "op_12" begin
            stack = test_stack
            want = stack
            push!(want, [0x0c])
            @test Bitcoin.op_12(stack) == true
            @test stack == want
        end
        @testset "op_13" begin
            stack = test_stack
            want = stack
            push!(want, [0x0d])
            @test Bitcoin.op_13(stack) == true
            @test stack == want
        end
        @testset "op_14" begin
            stack = test_stack
            want = stack
            push!(want, [0x0e])
            @test Bitcoin.op_14(stack) == true
            @test stack == want
        end
        @testset "op_15" begin
            stack = test_stack
            want = stack
            push!(want, [0x0f])
            @test Bitcoin.op_15(stack) == true
            @test stack == want
        end
        @testset "op_16" begin
            stack = test_stack
            want = stack
            push!(want, [0x10])
            @test Bitcoin.op_16(stack) == true
            @test stack == want
        end
    end
    @testset "Conditional control flow" begin
        @testset "op_nop" begin
            @test Bitcoin.op_nop([UInt8[]]) == true
            @test Bitcoin.op_nop([[0x64],
                [0x86, 0x8f, 0xf9, 0x73, 0x0a, 0x3c, 0x0c, 0xb9],
                [0xe2, 0xd6]]) == true
        end
        # TODO review those tests
        @testset "op_if" begin
            want = [[0x64],
                [0x86, 0x8f, 0xf9, 0x73, 0x0a, 0x3c, 0x0c, 0xb9],
                [0xe2, 0xd6]]
            stack = Array{UInt8,1}[]
            items = [0x63,0x0c,0xe2]
            @test Bitcoin.op_if(stack,items) == false
            @test stack == []
            @test items == [0x63,0x0c,0xe2]

            stack = want
            @test Bitcoin.op_if(stack,items) == false
            @test stack == want
            @test items == UInt8[]

            items == [0x67,0x0d,0x69]
            @test Bitcoin.op_if(stack,items) == false
            @test stack == want
            @test items == UInt8[]

            items == [0x63,0x68,0x6b]
            @test Bitcoin.op_if(stack,items) == false
            @test stack == want
            @test items == UInt8[]

            items == [0x0d,0x0f,0x7a]
            @test Bitcoin.op_if(stack,items) == false
            @test stack == want
            @test items == UInt8[]

            push!(stack,UInt8[])
            items = [0x68,0xc4,0x07]
            @test Bitcoin.op_if(stack,items) == true
            @test stack == want
            @test items == [0xc4,0x07]

            items = [0x68,0xc4,0x07]
            @test Bitcoin.op_if(stack,items) == true
            pop!(want)
            @test stack == want
            @test items == [0xc4,0x07]
        end
        # TODO create those tests
        @testset "op_notif" begin
        end
        @testset "op_verify" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_verify(stack) == false
            @test stack ==  Array{UInt8,1}[]
            stack = [[0x64], [0x86, 0x8f, 0xf9], [0xe2, 0xd6]]
            @test Bitcoin.op_verify(stack) == true
            @test stack ==  [[0x64], [0x86, 0x8f, 0xf9]]
            stack = [[0x64], [0x86, 0x8f, 0xf9], [0xe2, 0xd6], UInt8[]]
            @test Bitcoin.op_verify(stack) == false
            @test stack ==  [[0x64], [0x86, 0x8f, 0xf9], [0xe2, 0xd6]]
        end
        @testset "op_return" begin
            @test Bitcoin.op_return([UInt8[]]) == false
            @test Bitcoin.op_return([[0x64],
                [0x86, 0x8f, 0xf9, 0x73, 0x0a, 0x3c, 0x0c, 0xb9],
                [0xe2, 0xd6]]) == false
        end
    end
    @testset "Timelock operations" begin
        empty = Array{UInt8,1}[]
        stack = Array{UInt8,1}[[0x03], [0x04]]
        @testset "op_checklocktimeverify" begin
            @test Bitcoin.op_checklocktimeverify(stack, 1, 0xffffffff) == false
            @test Bitcoin.op_checklocktimeverify(empty, 100, 1) == false
            push!(stack, [0x81])
            @test Bitcoin.op_checklocktimeverify(stack, 100, 1) == false
            pop!(stack)
            @test Bitcoin.op_checklocktimeverify(stack, 500000001, 1) == false
            @test Bitcoin.op_checklocktimeverify(stack, 1, 1) == false
            @test Bitcoin.op_checklocktimeverify(stack, 100, 1) == true
        end
        # TODO
        @testset "op_checksequenceverify" begin
        end
    end
    @testset "Stack operations" begin
        @testset "op_toaltstack" begin
            stack = Array{UInt8,1}[]
            altstack = stack
            @test Bitcoin.op_toaltstack(stack, altstack) == false
            stack = [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06]]
            altstack = [[0xf4], [0xf6, 0xff, 0xf9], [0xf2, 0xf6]]
            @test Bitcoin.op_toaltstack(stack, altstack) == true
            @test stack == [[0x04], [0x06, 0x0f, 0x09]] && altstack == [[0xf4], [0xf6, 0xff, 0xf9], [0xf2, 0xf6], [0x02, 0x06]]
        end
        @testset "op_fromaltstack" begin
            stack = Array{UInt8,1}[]
            altstack = stack
            @test Bitcoin.op_toaltstack(stack, altstack) == false
            stack = [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06]]
            altstack = [[0xf4], [0xf6, 0xff, 0xf9], [0xf2, 0xf6]]
            @test Bitcoin.op_fromaltstack(stack, altstack) == true
            @test stack == [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06],[0xf2, 0xf6]] && altstack == [[0xf4], [0xf6, 0xff, 0xf9]]
        end
        @testset "op_2drop" begin
            stack = [[0x04]]
            @test Bitcoin.op_2drop(stack) == false
            stack = [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06]]
            @test Bitcoin.op_2drop(stack) == true
            @test stack == [[0x04]]
        end
        @testset "op_2dup" begin
            stack = [[0x04]]
            @test Bitcoin.op_2dup(stack) == false
            stack = [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06]]
            @test Bitcoin.op_2dup(stack) == true
            @test stack == [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06], [0x06, 0x0f, 0x09], [0x02, 0x06]]
        end
        @testset "op_3dup" begin
            stack = [[0x04], [0x06, 0x0f, 0x09]]
            @test Bitcoin.op_3dup(stack) == false
            stack = [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06]]
            @test Bitcoin.op_3dup(stack) == true
            @test stack == [[0x04], [0x06, 0x0f, 0x09], [0x02, 0x06], [0x04], [0x06, 0x0f, 0x09], [0x02, 0x06]]
        end
        @testset "op_2over" begin
            stack = [[0x01], [0x02], [0x03]]
            @test Bitcoin.op_2over(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04], [0x05]]
            @test Bitcoin.op_2over(stack) == true
            @test stack == [[0x01], [0x02], [0x03], [0x04], [0x05], [0x02], [0x03]]
        end
        @testset "op_2rot" begin
            stack = [[0x01], [0x02], [0x03], [0x04], [0x05]]
            @test Bitcoin.op_2rot(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04], [0x05], [0x06], [0x07]]
            @test Bitcoin.op_2rot(stack) == true
            @test stack == [[0x01], [0x02], [0x03], [0x04], [0x05], [0x06], [0x07], [0x02], [0x03]]
        end
        @testset "op_2swap" begin
            stack = [[0x01], [0x02], [0x03]]
            @test Bitcoin.op_2swap(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04], [0x05]]
            @test Bitcoin.op_2swap(stack) == true
            @test stack == [[0x01], [0x04], [0x05], [0x02], [0x03]]
        end
        @testset "op_ifdup" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_ifdup(stack) == false
            stack = [[0x01], [0x00]]
            @test Bitcoin.op_ifdup(stack) == true
            @test stack == [[0x01], [0x00]]
            stack = [[0x01], [0x02]]
            @test Bitcoin.op_ifdup(stack) == true
            @test stack == [[0x01], [0x02], [0x02]]
        end
        @testset "op_depth" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_depth(stack) == true
            @test stack == [[]]
            stack = [[0x01], [0x02]]
            @test Bitcoin.op_depth(stack) == true
            @test stack == [[0x01], [0x02], [0x02]]
        end
        @testset "op_dup" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_dup(stack) == false
            stack = [[0x01], [0x02], [0x03]]
            @test Bitcoin.op_dup(stack) == true
            @test stack == [[0x01], [0x02], [0x03], [0x03]]
        end
        @testset "op_nip" begin
            stack = Array{UInt8,1}[[0x01]]
            @test Bitcoin.op_nip(stack) == false
            stack = [[0x01], [0x02], [0x03]]
            @test Bitcoin.op_nip(stack) == true
            @test stack == [[0x01], [0x03]]
        end
        @testset "op_over" begin
            stack = [[0x01]]
            @test Bitcoin.op_over(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_over(stack) == true
            @test stack == [[0x01], [0x02], [0x03], [0x04], [0x03]]
        end
        @testset "op_pick" begin
            stack = Array{UInt8,1}[[0x01]]
            @test Bitcoin.op_pick(stack) == false
            stack = [[0x01], [0x0b], [0x03], [0x05]]
            @test Bitcoin.op_pick(stack) == false
            stack = [[0x01], [0x0b], [0x03], [0x02]]
            @test Bitcoin.op_pick(stack) == true
            @test stack == [[0x01], [0x0b], [0x03], [0x0b]]
        end
        @testset "op_roll" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_roll(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_roll(stack) == false
            @test stack == [[0x01], [0x02], [0x03]]
            stack = [[0x01], [0x0b], [0x03], [0x00]]
            @test Bitcoin.op_roll(stack) == true
            @test stack == [[0x01], [0x0b], [0x03]]
            stack = [[0x01], [0x0b], [0x03], [0x02]]
            @test Bitcoin.op_roll(stack) == true
            @test stack == [[0x01], [0x03], [0x0b]]
        end
        @testset "op_rot" begin
            stack = [[0x01], [0x02]]
            @test Bitcoin.op_rot(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_rot(stack) == true
            @test stack == [[0x01], [0x03], [0x04], [0x02]]
        end
        @testset "op_swap" begin
            stack = [[0x01]]
            @test Bitcoin.op_swap(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_swap(stack) == true
            @test stack == [[0x01], [0x02], [0x04], [0x03]]
        end
        @testset "op_tuck" begin
            stack = [[0x01]]
            @test Bitcoin.op_tuck(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_tuck(stack) == true
            @test stack == [[0x01], [0x02], [0x04], [0x03], [0x04]]
        end
    end
    @testset "String splice operations" begin
        @testset "op_size" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_size(stack) == false
            stack = [[0x01], [0x02], [0x01, 0x08]]
            @test Bitcoin.op_size(stack) == true
            @test stack == [[0x01], [0x02], [0x01, 0x08], [0x02]]
        end
    end
    @testset "Binary arithmetic and conditionals" begin
        @testset "op_equal" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_equal(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_equal(stack) == true
            @test stack == [[0x01], [0x02], []]
            stack = [[0x01], [0x02], [0x03], [0x03]]
            @test Bitcoin.op_equal(stack) == true
            @test stack == [[0x01], [0x02], [0x01]]
        end
        @testset "op_equalverify" begin
            stack = Array{UInt8,1}[]
            @test Bitcoin.op_equalverify(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_equalverify(stack) == false
            stack = [[0x01], [0x02], [0x03], [0x03]]
            @test Bitcoin.op_equalverify(stack) == true
        end
    end
    @testset "Numeric operators" begin
        empty = Array{UInt8,1}[]
        stack = Array{UInt8,1}[[0x03]]
        @testset "op_1add" begin
            @test Bitcoin.op_1add(empty) == false
            @test Bitcoin.op_1add(stack) == true
            @test stack == [[0x04]]
        end
        @testset "op_1sub" begin
            @test Bitcoin.op_1sub(empty) == false
            @test Bitcoin.op_1sub(stack) == true
            @test stack == [[0x03]]
        end
        @testset "op_negate" begin
            @test Bitcoin.op_negate(empty) == false
            @test Bitcoin.op_negate(stack) == true
            @test stack == [[0x83]]
        end
        @testset "op_abs" begin
            @test Bitcoin.op_abs(empty) == false
            @test Bitcoin.op_abs(stack) == true
            @test stack == [[0x03]]
            @test Bitcoin.op_abs(stack) == true
            @test stack == [[0x03]]
        end
        @testset "op_not" begin
            @test Bitcoin.op_not(empty) == false
            @test Bitcoin.op_not(stack) == true
            @test stack == [UInt8[]]
            @test Bitcoin.op_not(stack) == true
            @test stack == [[0x01]]
        end
        @testset "op_0notequal" begin
            @test Bitcoin.op_0notequal(empty) == false
            stack = [[0x1a]]
            @test Bitcoin.op_0notequal(stack) == true
            @test stack == [[0x01]]
            stack = [[0x00]]
            @test Bitcoin.op_0notequal(stack) == true
            @test stack == [UInt8[]]
        end
        @testset "op_add" begin
            @test Bitcoin.op_add(empty) == false
            stack = [[0x09], [0x02], [0x03]]
            @test Bitcoin.op_add(stack) == true
            @test stack == [[0x09], [0x05]]
        end
        @testset "op_sub" begin
            @test Bitcoin.op_sub(empty) == false
            @test Bitcoin.op_sub(stack) == true
            @test stack == [[0x04]]
        end
        @testset "op_booland" begin
            @test Bitcoin.op_booland(empty) == false
            stack = [[0x00], [0x00]]
            @test Bitcoin.op_booland(stack) == true
            @test stack == [UInt8[]]
            stack = [[0x01], [0x00]]
            @test Bitcoin.op_booland(stack) == true
            @test stack == [UInt8[]]
            stack = [[0x00], [0x01]]
            @test Bitcoin.op_booland(stack) == true
            @test stack == [UInt8[]]
            stack = [[0x01], [0x01]]
            @test Bitcoin.op_booland(stack) == true
            @test stack == [UInt8[0x01]]
        end
        @testset "op_boolor" begin
            @test Bitcoin.op_boolor(empty) == false
            stack = [[0x00], [0x00]]
            @test Bitcoin.op_boolor(stack) == true
            @test stack == [UInt8[]]
            stack = [[0x00], [0x01]]
            @test Bitcoin.op_boolor(stack) == true
            @test stack == [UInt8[0x01]]
            stack = [[0x01], [0x00]]
            @test Bitcoin.op_boolor(stack) == true
            @test stack == [UInt8[0x01]]
            stack = [[0x01], [0x01]]
            @test Bitcoin.op_boolor(stack) == true
            @test stack == [UInt8[0x01]]
        end
        @testset "op_numequal" begin
            @test Bitcoin.op_numequal(empty) == false
            stack = [[0x01], [0x02], [0x00], [0x00]]
            @test Bitcoin.op_numequal(stack) == true
            @test stack == [[0x01], [0x02], [0x01]]
            @test Bitcoin.op_numequal(stack) == true
            @test stack == [[0x01], UInt8[]]
        end
        @testset "op_numequalverify" begin
            @test Bitcoin.op_numequalverify(empty) == false
            stack = [[0x01], [0x02], [0x00], [0x00]]
            @test Bitcoin.op_numequalverify(stack) == true
            @test stack == [[0x01], [0x02]]
            @test Bitcoin.op_numequalverify(stack) == false
            @test stack == empty
        end
        @testset "op_numnotequal" begin
            @test Bitcoin.op_numnotequal(empty) == false
            stack = [[0x01], [0x02], [0x03], [0x03]]
            @test Bitcoin.op_numnotequal(stack) == true
            @test stack == [[0x01], [0x02], UInt8[]]
            stack = [[0x01], [0x02], [0x03], [0x04]]
            @test Bitcoin.op_numnotequal(stack) == true
            @test stack == [[0x01], [0x02], [0x01]]
        end
        @testset "op_lessthan" begin
            @test Bitcoin.op_lessthan(empty) == false
            stack = [[0x01], [0x01], [0x03], [0x04]]
            @test Bitcoin.op_lessthan(stack) == true
            @test stack == [[0x01], [0x01], [0x01]]
            @test Bitcoin.op_lessthan(stack) == true
            @test stack == [[0x01], UInt8[]]
        end
        @testset "op_greaterthan" begin
            @test Bitcoin.op_greaterthan(empty) == false
            stack = [[0x04], [0x01], [0x02], [0x01]]
            @test Bitcoin.op_greaterthan(stack) == true
            @test stack == [[0x04], [0x01], [0x01]]
            @test Bitcoin.op_greaterthan(stack) == true
            @test stack == [[0x04], UInt8[]]
        end
        @testset "op_lessthanorequal" begin
            @test Bitcoin.op_lessthanorequal(empty) == false
            stack = [[0x01], [0x02], [0x04], [0x04]]
            @test Bitcoin.op_lessthanorequal(stack) == true
            @test stack == [[0x01], [0x02], [0x01]]
            @test Bitcoin.op_lessthanorequal(stack) == true
            @test stack == [[0x01], UInt8[]]
        end
        @testset "op_greaterthanorequal" begin
            @test Bitcoin.op_greaterthanorequal(empty) == false
            stack = [[0x04], [0x00], [0x02], [0x02]]
            @test Bitcoin.op_greaterthanorequal(stack) == true
            @test stack == [[0x04], [0x00], [0x01]]
            @test Bitcoin.op_greaterthanorequal(stack) == true
            @test stack == [[0x04], UInt8[]]
        end
        @testset "op_min" begin
            @test Bitcoin.op_min(empty) == false
            stack = [[0x01], [0x08], [0x03], [0x04]]
            @test Bitcoin.op_min(stack) == true
            @test stack == [[0x01], [0x08], [0x03]]
            @test Bitcoin.op_min(stack) == true
            @test stack == [[0x01], [0x03]]
        end
        @testset "op_max" begin
            @test Bitcoin.op_max(empty) == false
            stack = [[0x01], [0x08], [0x03], [0x04]]
            @test Bitcoin.op_max(stack) == true
            @test stack == [[0x01], [0x08], [0x04]]
            @test Bitcoin.op_max(stack) == true
            @test stack == [[0x01], [0x08]]
        end
        @testset "op_within" begin
            @test Bitcoin.op_within(empty) == false
            stack = [[0xa1], [0x02], [0x02], [0x04]]
            @test Bitcoin.op_within(stack) == true
            @test stack == [[0xa1], [0x01]]
            stack = [[0xa1], [0x08], [0x02], [0x04]]
            @test Bitcoin.op_within(stack) == true
            @test stack == [[0xa1], UInt8[]]
        end
    end
    @testset "Cryptographic and hashing operations" begin
        empty = Array{UInt8,1}[]
        stack = Array{UInt8,1}[[0x03], [0x04]]
        @testset "op_ripemd160" begin
            @test Bitcoin.op_ripemd160(empty) == false
            @test Bitcoin.op_ripemd160(stack) == true
            @test stack == Array{UInt8,1}[[0x03], [0x44, 0x9b, 0x34, 0xb6, 0xa3, 0x41, 0x19, 0x43, 0xe3, 0x3a, 0x25, 0x87, 0xeb, 0xf2, 0x81, 0xca, 0xff, 0x16, 0x74, 0x98]]
        end
        stack = Array{UInt8,1}[[0x03], [0x04]]
        @testset "op_sha1" begin
            @test Bitcoin.op_sha1(empty) == false
            @test Bitcoin.op_sha1(stack) == true
            @test stack == Array{UInt8,1}[[0x03], [0xa4, 0x2c, 0x6c, 0xf1, 0xde, 0x3a, 0xbf, 0xde, 0xa9, 0xb9, 0x5f, 0x34, 0x68, 0x7c, 0xbb, 0xe9, 0x2b, 0x9a, 0x73, 0x83]]
        end
        stack = Array{UInt8,1}[[0x03], [0x04]]
        @testset "op_sha256" begin
            @test Bitcoin.op_sha256(empty) == false
            @test Bitcoin.op_sha256(stack) == true
            @test stack == Array{UInt8,1}[[0x03], [0xe5, 0x2d, 0x9c, 0x50, 0x8c, 0x50, 0x23, 0x47, 0x34, 0x4d, 0x8c, 0x07, 0xad, 0x91, 0xcb, 0xd6, 0x06, 0x8a, 0xfc, 0x75, 0xff, 0x62, 0x92, 0xf0, 0x62, 0xa0, 0x9c, 0xa3, 0x81, 0xc8, 0x9e, 0x71]]
        end
        stack = Array{UInt8,1}[[0x03], [0x04]]
        @testset "op_hash160" begin
            @test Bitcoin.op_hash160(empty) == false
            @test Bitcoin.op_hash160(stack) == true
            @test stack == Array{UInt8,1}[[0x03], [0x6d, 0x4c, 0x0a, 0xa9, 0x72, 0xc3, 0x14, 0x84, 0x0a, 0xc0, 0x7b, 0xe9, 0x6c, 0x5d, 0xde, 0x9c, 0x71, 0x4c, 0x9c, 0xa4]]
        end
        stack = Array{UInt8,1}[[0x03], [0x04]]
        @testset "op_hash256" begin
            @test Bitcoin.op_hash256(empty) == false
            @test Bitcoin.op_hash256(stack) == true
            @test stack == Array{UInt8,1}[[0x03], [0x21, 0x4e, 0x63, 0xbf, 0x41, 0x49, 0x0e, 0x67, 0xd3, 0x44, 0x76, 0x77, 0x8f, 0x67, 0x07, 0xaa, 0x6c, 0x8d, 0x2c, 0x8d, 0xcc, 0xdf, 0x78, 0xae, 0x11, 0xe4, 0x0e, 0xe9, 0xf9, 0x1e, 0x89, 0xa7]]
        end
        # TODO
        @testset "op_codeseparator" begin
        end
        # TODO
        @testset "op_checksig" begin
            z = parse(BigInt, "7c076ff316692a3d7eb3c3bb0f8b1488cf72e1afcd929e29307032997a838a3d", base=16)
            sec = hex2bytes("04887387e452b8eacc4acfde10d9aaf7f6d9a0f975aabb10d006e4da568744d06c61de6d95231cd89026e286df3b6ae4a894a3378e393e93a0f45b666329a0ae34")
            sig = hex2bytes("3045022000eff69ef2b1bd93a66ed5219add4fb51e11a840f404876325a1e8ffe0529a2c022100c7207fee197d27c618aea621406f6bf5ef6fca38681d82b2f06fddbdce6feab601")
            stack = [sig, sec]
            @test Bitcoin.op_checksig(stack, z) == true
            @test Bitcoin.decode_num(stack[1]) == 1
        end
        # TODO
        @testset "op_checksigverify" begin
        end
        # TODO
        @testset "op_checkmultisig" begin
        end
        # TODO
        @testset "op_checkmultisigverify" begin
        end
    end
    @testset "Nonoperators" begin
    end
    @testset "Reserved OP codes for internal use by the parser operators" begin
    end
end


struct NonDecomposedModel <: AbstractNLPEvaluator
    problem::ProxALProblem
    modelinfo::ModelInfo
    algparams::AlgParams
    opfdata::OPFData
    rawdata::RawData
    space::AbstractBackend
end

"""
    NonDecomposedModel(
        case_file::String,
        load_file::String,
        modelinfo::ModelInfo,
        algparams::AlgParams,
        space::AbstractBackend=JuMPBackend(),
        time_horizon_start = 1,
    )

Instantiate non-decomposed multi-period ACOPF instance
specified in `case_file` with loads in `load_file` with model parameters
`modelinfo` and algorithm parameters `algparams`.
The problem is defined over the horizon
[`time_horizon_start`, `modelinfo.num_time_periods`]

"""
function NonDecomposedModel(
    case_file::String, load_file::String,
    modelinfo::ModelInfo,
    algparams::AlgParams,
    space::AbstractBackend=JuMPBackend(),
    time_horizon_start=1
)
    rawdata = RawData(case_file, load_file)
    opfdata = opf_loaddata(
        rawdata;
        time_horizon_start = time_horizon_start,
        time_horizon_end = modelinfo.num_time_periods,
        load_scale = modelinfo.load_scale,
        ramp_scale = modelinfo.ramp_scale,
        corr_scale = modelinfo.corr_scale
    )

    # ctgs_arr = deepcopy(rawdata.ctgs_arr)
    problem = ProxALProblem(opfdata, rawdata, modelinfo, algparams, space, nothing)
    return NonDecomposedModel(problem, modelinfo, algparams, opfdata, rawdata, space)
end

"""
    optimize!(nlp::NonDecomposedModel)

Solve problem using the `nlp` evaluator
of the nondecomposed model.

"""
function optimize!(nlp::NonDecomposedModel)

    opfmodel = opf_model_nondecomposed(nlp.opfdata, nlp.rawdata, nlp.modelinfo, nlp.algparams)
    return opf_solve_nondecomposed(opfmodel, nlp.opfdata, nlp.modelinfo, nlp.algparams)
end

function opf_model_nondecomposed(opfdata::OPFData, rawdata::RawData, modelinfo::ModelInfo, algparams::AlgParams)
    opfmodel = JuMP.Model(algparams.optimizer)
    opf_model_add_variables(opfmodel, opfdata, modelinfo, algparams)
    opf_model_add_block_constraints(opfmodel, opfdata, rawdata, modelinfo)
    obj_expr = compute_objective_function(opfmodel, opfdata, modelinfo, algparams)

    if algparams.mode == :lyapunov_bound
        lyapunov_expr = compute_quadratic_penalty(opfmodel, opfdata,  modelinfo, algparams)
        if !algparams.decompCtgs
            opf_model_add_ctgs_linking_constraints(opfmodel, opfdata, modelinfo)
        end
    else
        lyapunov_expr = 0
        opf_model_add_time_linking_constraints(opfmodel, opfdata, modelinfo)
        opf_model_add_ctgs_linking_constraints(opfmodel, opfdata, modelinfo)
    end

    @objective(opfmodel, Min, obj_expr + lyapunov_expr)

    return opfmodel
end

function opf_solve_nondecomposed(opfmodel::JuMP.Model, opfdata::OPFData,
                                 modelinfo::ModelInfo,
                                 algparams::AlgParams)
    JuMP.optimize!(opfmodel)
    status = termination_status(opfmodel)
    if status ∉ MOI_OPTIMAL_STATUSES
        (algparams.verbose > 0) &&
            @warn("$(algparams.mode) model not solved to optimality. status: $status")
        return nothing
    end

    x = OPFPrimalSolution(opfdata, modelinfo)
    x.Pg .= value.(opfmodel[:Pg])
    x.Qg .= value.(opfmodel[:Qg])
    x.Vm .= value.(opfmodel[:Vm])
    x.Va .= value.(opfmodel[:Va])
    x.ωt .= value.(opfmodel[:ωt])
    x.St .= value.(opfmodel[:St])
    x.Zt .= value.(opfmodel[:Zt])
    x.Sk .= value.(opfmodel[:Sk])
    x.Zk .= value.(opfmodel[:Zk])
    if modelinfo.allow_constr_infeas
        x.sigma_real .= value.(opfmodel[:sigma_real])
        x.sigma_imag .= value.(opfmodel[:sigma_imag])
        x.sigma_lineFr .= value.(opfmodel[:sigma_lineFr])
        x.sigma_lineTo .= value.(opfmodel[:sigma_lineTo])
    end

    result = Dict()
    result["primal"] = x
    result["objective_value_" * String(algparams.mode)] = objective_value(opfmodel)
    result["solve_time"] = JuMP.solve_time(opfmodel)

    return result
end


"""
Function to obtain the output current time series of a Dynamic Inverter model out of the DAE Solution. It receives the simulation inputs,
the dynamic device and bus voltage. It is dispatched for device type to compute the specific current.

"""
function compute_output_current(
    res::SimulationResults,
    dynamic_device::G,
    V_R::Vector{Float64},
    V_I::Vector{Float64},
) where {G <: PSY.DynamicInverter}

    #Obtain Data
    sys = get_system(res)

    #Get machine
    filt = PSY.get_filter(dynamic_device)
    converter = PSY.get_converter(dynamic_device)
    Sbase = PSY.get_base_power(sys)
    basepower = PSY.get_base_power(dynamic_device)
    base_power_ratio = basepower / Sbase
    return _output_current(
        filt,
        converter,
        PSY.get_name(dynamic_device),
        V_R,
        V_I,
        base_power_ratio,
        res,
        dynamic_device,
    )
end

"""
Function to obtain the output current time series of a LCL Filter model out of the DAE Solution. It is dispatched via the Filter type.

"""
function _output_current(
    ::PSY.LCLFilter,
    ::C,
    name::String,
    ::Vector{Float64},
    ::Vector{Float64},
    base_power_ratio::Float64,
    res::SimulationResults,
    dynamic_device::G,
) where {C <: PSY.Converter, G <: PSY.DynamicInverter}
    ir_filter = post_proc_state_series(res, (name, :ir_filter))
    ii_filter = post_proc_state_series(res, (name, :ii_filter))

    return base_power_ratio * ir_filter, base_power_ratio * ii_filter
end

"""
Function to obtain the output current time series of a REGCA converter model out of the DAE Solution. It is dispatched via the Converter type.

"""
function _output_current(
    filt::PSY.RLFilter,
    converter::PSY.RenewableEnergyConverterTypeA,
    name::String,
    V_R::Vector{Float64},
    V_I::Vector{Float64},
    base_power_ratio::Float64,
    res::SimulationResults,
    dynamic_device::G,
) where {G <: PSY.DynamicInverter}

    #Get Converter parameters
    Lv_pnt0, Lv_pnt1 = PSY.get_Lv_pnts(converter)
    K_hv = PSY.get_K_hv(converter)
    Vo_lim = PSY.get_Vo_lim(converter)
    Brkpt = PSY.get_Brkpt(converter)
    Zerox = PSY.get_Zerox(converter)
    Lvpl1 = PSY.get_Lvpl1(converter)
    Lvpl_sw = PSY.get_Lvpl_sw(converter)
    R_source = PSY.get_R_source(converter)
    X_source = PSY.get_X_source(converter)
    #Get Filter parameters
    rf = PSY.get_rf(filt)
    lf = PSY.get_lf(filt)

    #Compute additional terms
    V_t = sqrt.(V_R .^ 2 + V_I .^ 2)
    G_lv = get_LV_current_gain.(V_t, Lv_pnt0, Lv_pnt1)
    Iq_extra = max.(K_hv * (V_t .- Vo_lim), 0.0)

    #Compute current
    Ip = post_proc_state_series(res, (name, :Ip))
    Iq = post_proc_state_series(res, (name, :Iq))
    Vmeas = post_proc_state_series(res, (name, :Vmeas))
    Ip_sat = Ip
    if Lvpl_sw == 1
        LVPL = get_LVPL_gain.(Vmeas, Zerox, Brkpt, Lvpl1)
        Ip_sat = Ip <= LVPL ? Ip : LVPL
    end
    Id_cnv = G_lv .* Ip_sat
    Iq_cnv = -Iq - Iq_extra
    θ = atan.(V_I ./ V_R)
    Id_cnv = G_lv .* Ip_sat
    Iq_cnv = -Iq - Iq_extra
    Ir_cnv = Id_cnv .* cos.(θ) - Iq_cnv .* sin.(θ)
    Ii_cnv = Id_cnv .* sin.(θ) + Iq_cnv .* cos.(θ)

    #Compute filter current
    Zf = rf + lf * 1im
    Z_source = R_source + X_source * 1im

    function V_cnv_calc(Ir_cnv, Ii_cnv, Vr_inv, Vi_inv)
        I_cnv = Ir_cnv + 1im * Ii_cnv
        V_inv = Vr_inv + 1im * Vi_inv
        if lf != 0.0 || rf != 0.0
            V_cnv = (I_cnv + V_inv / Zf) / (1.0 / Z_source + 1.0 / Zf)
        else
            V_cnv = V_inv
        end
        return V_cnv
    end

    V_cnv = V_cnv_calc(Ir_cnv, Ii_cnv, V_R, V_I)
    Vr_cnv = real(V_cnv)
    Vi_cnv = imag(V_cnv)
    if lf != 0.0 || rf != 0.0
        Zmag = rf^2 + lf^2
        Ir_filt = (1.0 / Zmag) * ((Vr_cnv - V_R) * rf + (Vi_cnv - V_I) * lf)
        Ii_filt = (1.0 / Zmag) * ((Vi_cnv - V_I) * rf - (Vr_cnv - V_I) * lf)
    else
        Ir_filt = Ir_cnv
        Ii_filt = Ii_cnv
    end

    return base_power_ratio * Ir_filt, base_power_ratio * Ii_filt
end


# This file is automatically created by the folowing script. Only re-run it if
# AMPL adds new opcodes (which is unlikely).
# ```julia
# using ASL_jll
# open("opcode.jl", "w") do io
#     filename = joinpath(ASL_jll.artifact_dir, "include", "opcode.hd")
#     for line in readlines(filename)
#         items = split(line, c -> c == '\t' || c == ' '; keepempty = false)
#         println(io, "const ", items[2], " = ", items[3])
#     end
# end
# ```

const OPPLUS = 0
const OPMINUS = 1
const OPMULT = 2
const OPDIV = 3
const OPREM = 4
const OPPOW = 5
const OPLESS = 6
const MINLIST = 11
const MAXLIST = 12
const FLOOR = 13
const CEIL = 14
const ABS = 15
const OPUMINUS = 16
const OPOR = 20
const OPAND = 21
const LT = 22
const LE = 23
const EQ = 24
const GE = 28
const GT = 29
const NE = 30
const OPNOT = 34
const OPIFnl = 35
const OP_tanh = 37
const OP_tan = 38
const OP_sqrt = 39
const OP_sinh = 40
const OP_sin = 41
const OP_log10 = 42
const OP_log = 43
const OP_exp = 44
const OP_cosh = 45
const OP_cos = 46
const OP_atanh = 47
const OP_atan2 = 48
const OP_atan = 49
const OP_asinh = 50
const OP_asin = 51
const OP_acosh = 52
const OP_acos = 53
const OPSUMLIST = 54
const OPintDIV = 55
const OPprecision = 56
const OPround = 57
const OPtrunc = 58
const OPCOUNT = 59
const OPNUMBEROF = 60
const OPNUMBEROFs = 61
const OPATLEAST = 62
const OPATMOST = 63
const OPPLTERM = 64
const OPIFSYM = 65
const OPEXACTLY = 66
const OPNOTATLEAST = 67
const OPNOTATMOST = 68
const OPNOTEXACTLY = 69
const ANDLIST = 70
const ORLIST = 71
const OPIMPELSE = 72
const OP_IFF = 73
const OPALLDIFF = 74
const OPSOMESAME = 75
const OP1POW = 76
const OP2POW = 77
const OPCPOW = 78
const OPFUNCALL = 79
const OPNUM = 80
const OPHOL = 81
const OPVARVAL = 82
const N_OPS = 83


# MDF reconstruction data is loaded/stored using ImageMeta objects from
# the ImageMetadata.jl package.

export converttometer, imcenter, loadRecoData, saveRecoData

converttometer(x) = ustrip.(uconvert.(u"m",x))
imcenter(img::AxisArray) = map(x->(0.5*(last(x)+first(x))), ImageAxes.filter_space_axes(AxisArrays.axes(img), axisvalues(img)))
imcenter(img::ImageMeta) = imcenter(data(img))

function saveRecoData(filename, image::ImageMeta)
  C = colordim(image) == 0 ? 1 : size(image,colordim(image))
  L = timedim(image) == 0 ? 1 : size(image,timedim(image))

  if colordim(image) == 0
    grid = size(image)[1:3]
  else
    grid = size(image)[2:4]
  end
  N = div(length(data(image)), L*C)
  c = reshape(convert(Array,image), C, N, L )

  params = properties(image)
  params["recoData"] = c
  params["recoFov"] = collect(grid) .* collect(converttometer(pixelspacing(image)))
  params["recoFovCenter"] = collect(converttometer(imcenter(image)))[1:3]
  params["recoSize"] = collect(grid)
  params["recoOrder"] = "xyz"
  if haskey(params,"recoParams")
    params["recoParameters"] = params["recoParams"]
  end

  h5open(filename, "w") do file
    saveasMDF(file, params)
  end
end

function loadRecoData(filename::AbstractString)
  f = MPIFile(filename)
  return loadRecoData(f)
end

function loadRecoData(f::MDFFile)
  header = loadMetadata(f)
  header["datatype"] = "MPI"

  recoParams = recoParameters(f)
  if recoParams != nothing
    header["recoParams"] = recoParams
  end
  im = loadRecoData_(f)
  imMeta = ImageMeta(im, header)

  return imMeta
end

function loadRecoData_(f::MDFFile)
  # preparation for spatial axes
  rsize::Vector{Int64} = recoSize(f)
  pixspacing = (recoFov(f) ./ rsize)*1000u"mm"
  off::Vector{Float64} = vec(recoFovCenter(f))
  offset = [0.0,0.0,0.0]*u"mm"
  if off != nothing
    offset[:] = (off .- 0.5.*recoFov(f))*u"m" .+ 0.5.*pixspacing
  end

  # preparation for time axis
  periodTime = Float64(acqNumAverages(f)*acqFramePeriod(f))*u"s"
  if exists(f.file, "/reconstruction/_parameters/nAverages")
    periodTime *= read(f.file, "/reconstruction/_parameters/nAverages")
  else
    @warn "No reconstruction averaging number found. tempoaral spacings in axis `:time` might be wrong."
  end

  # load data
  c_::Array{Float32,3} = recoData(f)
  c::Array{Float32,5} = reshape(c_, size(c_,1), rsize[1], rsize[2], rsize[3], size(c_,3))

  return makeAxisArray(c, pixspacing, offset, periodTime)
end

function loadRecoData(f::BrukerFile)
  # preparation for spatial axes
  rsize::Vector{Int64} = recoSize(f)
  pixspacing = (recoFov(f) ./ rsize)*1000u"mm"
  off::Vector{Float64} = vec(recoFovCenter(f))
  offset = [0.0,0.0,0.0]*u"mm"
  if off != nothing
    offset[:] = (off .- 0.5.*recoFov(f))*u"m" .+ 0.5.*pixspacing
  end

  periodTime = 1.0*u"s"

  # load data
  c_::Array{Float32,3} = recoData(f)
  c::Array{Float32,5} = reshape(c_, size(c_,1), rsize[1], rsize[2], rsize[3], size(c_,3))

  return ImageMeta( makeAxisArray(c, pixspacing, offset, periodTime) )
end


# Julia script to read a list of gene trees
# and create the NEXUS file for PhyloNet
# Claudia (January 2022)

# PhyloNet parameters
command = "InferNetwork_MPL" # phylonet command
numhyb = 1 # number of hybridizations allowed
netret = 1 # number of optimal networks to return (default 5)
numproc = 2 # number of processors to run computation in parallel
numruns = 10 # number of runs of the search (default 10)

# File with gene trees
treefile = "trees.txt"

# Output NEXUS file
rootname = split(treefile,'.')
outfile = string(rootname[1],"-phylonet.nex")

## Reading the gene trees
trees = readlines(treefile)

## Writing the NEXUS file
f = open(outfile, "w") ## this will overwrite the file if it exists
write(f,"#NEXUS \n\nBEGIN TREES;\n\n")

i = 0
for t in trees
    write(f,string("Tree gt",i,"= "))
    write(f,t)
    write(f,"\n")
    i += 1
end

write(f,"\nEND;\n\n\n")

## Writing PhyloNet command
write(f,"BEGIN PHYLONET;\n\n")
write(f,string(command," (all) ",numhyb, " -x ", numruns, " -n ", netret, " -pl ", numproc, ";"))
write(f,"\n\nEND;")
close(f)


using Plots, GR, Distributions, HypothesisTests, Statistics

GR.inline("png")

## plotting
function line_plot(xset, yset, xname, yname, figname, figname2, AllAlgorithms, availability_zoom, avg_med, inverse_log, x_log)
    colormap = Dict(
        "HYPERTHETICAL" => "black",
        "ARROW_NAIVE" => "blue",
        "FFC1" => "green",
        "FFC2" => "yellow",
        "TEAVAR" => "purple",
        "ECMP" => "orange",
    )
    markermap = Dict(
        "HYPERTHETICAL" => "o",
        "ARROW_NAIVE" => "o",
        "FFC1" => "o",
        "FFC2" => "o",
        "TEAVAR"=>"P",
        "ECMP" => "o",
    )
    arrowmarkermap = Dict(
        "HYPERTHETICAL" => "o",
        "ARROW"=>"o",
        "ARROW_BIN"=>"P",
    )
    datafilename = replace(figname, ".png"=>".txt")
    if inverse_log figname = replace(figname, ".png"=>"_r.png") end
    if inverse_log figname2 = replace(figname2, ".png"=>"_r.png") end
    open(datafilename, "w+") do io
        PyPlot.clf()
        Plots.plot()
        for algorithm in AllAlgorithms
            line = []
            line_ci_min = []
            line_ci_max = []
            for s in 1:length(xset)
                vectorized = reshape(yset[algorithm][:,:,:,s], size(yset[algorithm][:,:,:,s],1)*size(yset[algorithm][:,:,:,s],2)*size(yset[algorithm][:,:,:,s],3))
                null_position = findall(x->x==-1, vectorized)
                real_vectorized = deleteat!(vectorized, null_position)
                if avg_med
                    metric = round(sum(real_vectorized)/length(real_vectorized), digits=16)
                    avg = metric
                else
                    if length(real_vectorized) > 0
                        metric = median(real_vectorized)
                    else
                        metric = nothing
                    end
                    avg = round(sum(real_vectorized)/length(real_vectorized), digits=16)
                end
                push!(line, metric)
                if length(real_vectorized) > 1
                    if avg_med
                        conf = confint(OneSampleTTest(real_vectorized, avg))  # confidence interval calculation based on average
                    else
                        medianinterval(d,p = 0.95) = quantile(d,1-(1+p)/2),quantile(d,(1+p)/2)
                        conf = medianinterval(real_vectorized)
                    end
                    push!(line_ci_min, min(metric - conf[1], metric))
                    push!(line_ci_max, min(conf[2] - metric, 1 - metric))
                elseif length(real_vectorized) > 0
                    push!(line_ci_min, metric - minimum(real_vectorized))
                    push!(line_ci_max, maximum(real_vectorized) - metric)
                end
            end
            writedlm(io, (yname,))
            if algorithm == "ARROW" || algorithm == "ARROW_BIN"
                if inverse_log
                    reverse_line = []
                    for i in 1:length(line)
                        if line[i] === nothing
                            push!(reverse_line, nothing)
                        else
                            push!(reverse_line, 1-line[i])
                        end
                    end
                    PyPlot.plot(xset, reverse_line, linewidth=1, marker=arrowmarkermap[algorithm], alpha = 0.8, label=algorithm, color="red")
                else
                    PyPlot.plot(xset, line, linewidth=1, marker=arrowmarkermap[algorithm], alpha = 0.8, label=algorithm, color="red")
                end
                writedlm(io, (algorithm, line))
            else
                if inverse_log
                    reverse_line = []
                    for i in 1:length(line)
                        if line[i] === nothing
                            push!(reverse_line, nothing)
                        else
                            push!(reverse_line, 1-line[i])
                        end
                    end
                    PyPlot.plot(xset, reverse_line, linewidth=1, marker=markermap[algorithm], alpha = 0.8, label=algorithm, color=colormap[algorithm])
                else
                    PyPlot.plot(xset, line, linewidth=1, marker=markermap[algorithm], alpha = 0.8, label=algorithm, color=colormap[algorithm])
                end
                writedlm(io, (algorithm, line))
            end
            writedlm(io, (yname,"error border"))
            if availability_zoom
                if inverse_log
                    if length(line_ci_min) > 0 && length(line_ci_max) > 0
                        Plots.plot!(xset, 1 .- line; ribbon=(line_ci_min, line_ci_max), fillalpha=0.2, xaxis=xname, yaxis=yname, label=algorithm, ylims = (0.00001, 0.01), linewidth=2, marker=:auto, yscale=:log10, yflip=true)
                        Plots.plot!(yticks=([0.01, 0.001, 0.0001, 0.00001], [0.99, 0.999, 0.9999, 0.99999]))
                        Plots.plot!(layout=1, legend=:bottomleft)
                    end
                else
                    if length(line_ci_min) > 0 && length(line_ci_max) > 0
                        Plots.plot!(xset, line; ribbon=(line_ci_min, line_ci_max), fillalpha=0.2, xaxis=xname, yaxis=yname, label=algorithm, ylims = (0.99, 1.001), linewidth=2, marker=:auto)
                        Plots.plot!(layout=1, legend=:bottomleft)
                    end
                end
                writedlm(io, (algorithm, line_ci_min, line_ci_max))
            else
                if inverse_log
                    if length(line_ci_min) > 0 && length(line_ci_max) > 0
                        Plots.plot!(xset, 1 .- line; ribbon=(line_ci_min, line_ci_max), fillalpha=0.2, xaxis=xname, yaxis=yname, label=algorithm, linewidth=2, marker=:auto, yscale=:log10, yflip=true)
                        Plots.plot!(yticks=([0.1, 0.01, 0.001, 0.0001, 0.00001], [0.9, 0.99, 0.999, 0.9999, 0.99999]))
                        Plots.plot!(layout=1, legend=:bottomleft)
                    end
                else
                    if length(line_ci_min) > 0 && length(line_ci_max) > 0
                        Plots.plot!(xset, line; ribbon=(line_ci_min, line_ci_max), fillalpha=0.2, xaxis=xname, yaxis=yname, label=algorithm, linewidth=2, marker=:auto)
                        Plots.plot!(layout=1, legend=:bottomleft)
                    end
                end
            end
        end
        PyPlot.legend(loc="best")
        PyPlot.xlabel(xname)
        PyPlot.ylabel(yname)
        if x_log PyPlot.xscale("log") end
        if inverse_log
            PyPlot.yscale("log")
            PyPlot.yticks([0.1, 0.01, 0.001, 0.0001, 0.00001], [0.9, 0.99, 0.999, 0.9999, 0.99999])
            PyPlot.gca().invert_yaxis()
        end
        if availability_zoom
            PyPlot.ylim((0.99, 1.001))
            if inverse_log
                PyPlot.ylim((0.00001, 0.01))
                PyPlot.yticks([0.01, 0.001, 0.0001, 0.00001], [0.99, 0.999, 0.9999, 0.99999])
                PyPlot.gca().invert_yaxis()
            end
        end
        PyPlot.savefig(figname)
        Plots.savefig(figname2)
    end
end


struct ReifiedBridge{T, B<:MOIBC.SetMapBridge{T}, A, S} <: MOIBC.AbstractBridge
    con_idx::CI
end

function MOI.supports_constraint(
    ::Type{<:ReifiedBridge{T, B}},
    ::Type{F},
    ::Type{<:CS.ReifiedSet{A,S}}
) where {T, B, F<:MOI.VectorAffineFunction, A, S}
    is_supported = MOI.supports_constraint(B, MOIU.scalar_type(F), S)
    !is_supported && return false
    S <: BoolSet && return true

    concrete_B = MOIBC.concrete_bridge_type(B, MOI.ScalarAffineFunction{T}, S)
    return supports_concreteB(concrete_B)
end

function MOIBC.concrete_bridge_type(
    ::Type{<:ReifiedBridge{T,B}},
    G::Type{<:MOI.VectorAffineFunction},
    ::Type{IS},
) where {T,B,A,S,IS<:CS.ReifiedSet{A,S}}
    if S <: BoolSet
        concrete_B = B
    else
        concrete_B = MOIBC.concrete_bridge_type(B, MOI.ScalarAffineFunction{T}, S)
    end
    return ReifiedBridge{T,concrete_B,A,S}
end

function MOIB.added_constraint_types(
    ::Type{<:ReifiedBridge{T,B,A,S}}
) where {T,B,A,S}
    if S <: BoolSet
        added_constraints = added_constraint_types(B, S)
    else
        added_constraints = MOIB.added_constraint_types(B)
    end
    return [(MOI.VectorAffineFunction{T}, CS.ReifiedSet{A,added_constraints[1][2]})]
end

function MOIB.added_constrained_variable_types(::Type{<:ReifiedBridge{T,B}}) where {T,B}
    return MOIB.added_constrained_variable_types(B)
end

function MOIBC.bridge_constraint(::Type{<:ReifiedBridge{T, B, A, S}}, model, func, set) where {T, B, A, S}
    f = MOIU.eachscalar(func)
    if S <: BoolSet
        new_func = MOIU.operate(vcat, T, f[1], map_function(B, f[2:end], set.set))
        new_inner_set = map_set(B, set.set)
    else
        new_func = MOIU.operate(vcat, T, f[1], MOIBC.map_function(B, f[2:end]))
        new_inner_set = MOIBC.map_set(B, set.set)
    end
    new_set = CS.ReifiedSet{A,typeof(new_inner_set)}(new_inner_set, 1+MOI.dimension(new_inner_set))
    return ReifiedBridge{T,B,A,S}(MOI.add_constraint(model, new_func, new_set))
end


 #returns the specification symbol in multicomponent
#returns :singlecomponent if there are no specifications
#throws error if more than one specification is found

const AMOUNT_CONST =Dict{Int,Any}(
     0000 => OneMol()
    ,0001 => MaterialAmount{MOLAR}()
    ,0002 => MaterialAmount{MASS}()
    ,0099 => SingleComponent()
    ,1100 => MaterialCompounds{MOLAR,FRACTION}()
    ,1200 => MaterialCompounds{MASS,FRACTION}()
    ,2100 => MaterialCompounds{MOLAR,TOTAL_AMOUNT}()
    ,2200 => MaterialCompounds{MASS,TOTAL_AMOUNT}()
    ,3100 => HumiditySpec{WetBulbTemperature}()
    ,3200 => HumiditySpec{HumidityRatio}()
    ,3300 => HumiditySpec{MolarHumidity}()
    ,3400 => HumiditySpec{MassHumidity}()
    ,3500 => HumiditySpec{RelativeHumidity}()
    ,3600 => HumiditySpec{HumidityDewPoint}()
)

#the following functions are essencial in the ordering
#do not change the relative order!!

#equilibria properties are always first
_stateorder(x::PhaseFractions) = 1
_stateorder(x::TwoPhaseEquilibrium) = 2
_stateorder(x::VaporFraction) = 3

#pressure is always first in the scalar properties
_stateorder(x::Pressure) = 4
_stateorder(x::Helmholtz) = 5
_stateorder(x::Gibbs) = 6
_stateorder(x::InternalEnergy) = 7
_stateorder(x::VolumeAmount) = 8
_stateorder(x::Enthalpy) = 9
_stateorder(x::Entropy) = 10
#temperature always for last of the scalar properties
_stateorder(x::Temperature) = 11

#From here, it doesnt matter the order, as those are treated specially

_stateorder(x::HumiditySpec) = 1000

_stateorder(x::MaterialCompounds) = 10002

_stateorder(x::MaterialAmount ) = 10001
_stateorder(x::PhaseTag) = 10000
_stateorder(x::Options) = 10004
_stateorder(x::MolecularWeight) = 10005
_stateorder(x::VariableSpec) = 10006

#using specs here allows to delete all reduce_check_mass(::Type)
function _static_specs(x::Type{ThermodynamicState{S,C}}) where {S,C}
    @nospecialize S,C
    tvec = S.parameters
    res1 = [tvec[i].parameters[1]() for i in 1:length(tvec)]
    if C === Tuple{}
        return res1
    else
        tvec2 = C.parameters
        res2 = [tvec2.parameters[i]() for i in 1:length(tvec)]
        append!(res1,res2)
        return res1
    end
end

@generated function static_amount_type(x::T) where T<: ThermodynamicState
    sps = _static_specs(x)
    val = mapreduce(_reduce_check_mass,+,sps)
    modval = mod(val,MATERIAL_SINGLE_MAX)
    if val in (2100,2200)
        _1 = val
        _2 = val
    else
        _1 = max(val-modval,99)
        _2 = modval
    end
    res =  (AMOUNT_CONST[_1],AMOUNT_CONST[_2])

    return :($res)
end

function _state_from_type(x::Type{T}) where T<: ThermodynamicState
    @nospecialize T
    state_specs =  _static_specs(T)
    has_compound = false
    for spec in state_specs
        if spec isa MaterialCompounds
            has_compound = true
            comp_spec = spec
            break
        end
    end

    #double iteration, used in moist air to create an invalid
    #state where compound and humidity spec are present
    #in this case, the resulting type is MultiPT instead of HumPT
    for spec in state_specs
        if (spec isa HumiditySpec) & !has_compound
            has_compound = true
            comp_spec = spec
            break
        end
    end

    if !has_compound
        comp_spec = SingleComponent()
    end
    sorted_specs = sort(state_specs,by= _stateorder)
    res = (sorted_specs[1],sorted_specs[2],comp_spec)
end

function _static_get_spec_idx(@nospecialize(x::Type{ThermodynamicState{S,C}}),@nospecialize(sp::Type{S2}))::Int where {S,C,S2}
    @nospecialize S,C,S2
    state_specs =  _static_specs(x)
    for (i,spec) in pairs(state_specs)
        if spec isa sp
            return i
        end
    end
    return 0
end

"""
    state_type(x::ThermodynamicState)

Returns a sorted tuple of thermodynamic specifications of the input state,
 the tuple has 3 elements, corresponding to two general specifications and a last amount specification,
  indicating if the state is single or multicomponent.

"""
@generated function state_type(x::ThermodynamicState)
    res = _state_from_type(x)
    return :($res)
end

function get_spec(val::T,st::Tuple) where T<:AbstractSpec
    for spec in st
        if specification(spec) isa T
            return spec
        end
    end
    return nothing
end

function get_spec(val::Type{T},st::Tuple) where T<: AbstractSpec
    for spec in st
        if specification(spec) isa T
            return spec
        end
    end
    return nothing
end

get_spec(val,st::ThermodynamicState) = get_spec(val,st.specs)

function throw_get_spec(val,st::ThermodynamicState)
    return static_get_spec(st,val)
end

function has_spec(val::Type{T},st::ThermodynamicState) where T<: AbstractSpec
    static_has_spec(st,val)
end

function amount_type(st::T) where T<:ThermodynamicState
    return static_amount_type(st)
end

@generated function static_get_spec(st::ThermodynamicState,val::Type{T}) where T<:AbstractSpec
    idx =_static_get_spec_idx(st,T)
    if iszero(idx)
        return :(nothing)
    else
        return :(st.specs[$idx])
    end
end

@generated function static_has_spec(st::ThermodynamicState,val::Type{T}) where T<:AbstractSpec
    idx =_static_get_spec_idx(st,T)
    res = !iszero(idx)
    return :($res)
end


abstract type AbstractPoint end
abstract type CartesianPoint <: AbstractPoint end
abstract type WGS84Point <: AbstractPoint  end

Base.:(==)(x::AbstractPoint, y::AbstractPoint) = x.srid == y.srid && x.coords == y.coords

mutable struct Point <: AbstractPoint
    srid::Integer
    coords::Array{Float64}
end

mutable struct CartesianPoint2 <: CartesianPoint
    srid::Integer
    coords::Array{Float64}
    x::Float64
    y::Float64
end

mutable struct CartesianPoint3 <: CartesianPoint
    srid::Integer
    coords::Array{Float64}
    x::Float64
    y::Float64
    z::Float64
end

CartesianPoint(x::Number,y::Number) = CartesianPoint2(7203, [x,y], x, y)
CartesianPoint(x::Number,y::Number,z::Number) = CartesianPoint3(9157, [x,y,z], x, y, z)
CartesianPoint(p::Tuple{Number,Number}) = CartesianPoint(p[1], p[2])
CartesianPoint(p::Tuple{Number,Number, Number}) = CartesianPoint(p[1], p[2], p[3])

mutable struct WGS84Point2 <: WGS84Point
    srid::Integer
    coords::Array{Float64}
    longitude::Float64
    latitude::Float64
    x::Float64
    y::Float64
end

mutable struct WGS84Point3 <: WGS84Point
    srid::Integer
    coords::Array{Float64}
    longitude::Float64
    latitude::Float64
    height::Float64
    x::Float64
    y::Float64
    z::Float64
end

WGS84Point(longitude::Number,latitude::Number) = WGS84Point2(4326, [longitude,latitude], longitude, latitude, longitude, latitude)
WGS84Point(longitude::Number,latitude::Number,height::Number) = WGS84Point3(4979, [longitude,latitude,height], longitude, latitude, height, longitude, latitude, height)
WGS84Point(p::Tuple{Number,Number}) = WGS84Point(p[1], p[2])
WGS84Point(p::Tuple{Number,Number, Number}) = WGS84Point(p[1], p[2], p[3])

function hydrate_point(srid::Integer, coordinates::Vector)
    if srid == 7203
        CartesianPoint2(srid, coordinates, coordinates[1], coordinates[2])
    elseif srid == 9157
        CartesianPoint3(srid, coordinates, coordinates[1], coordinates[2], coordinates[3])
    elseif srid == 4326
        WGS84Point2(srid, coordinates, coordinates[1], coordinates[2], coordinates[1], coordinates[2])
    elseif srid == 4979
        WGS84Point3(srid, coordinates, coordinates[1], coordinates[2], coordinates[3], coordinates[1], coordinates[2], coordinates[3])
    else
        Point(srid, coordinates)
    end
end

hydrate_point(srid::Integer, a, b...) = hydrate_point(srid, [a, b...])

function dehydrate_point(value::AbstractPoint)
    dim = length(value.coords)
    if dim == 2
        return Structure(UInt8('X'), Any[value.srid, value.coords...])
    elseif dim == 3
        return Structure(UInt8('Y'), Any[value.srid, value.coords...])
    else
        throw(ValueError("Cannot dehydrate Point with $(dim) dimensions"))
    end
end

dehydrate(value::AbstractPoint) = dehydrate_point(value)

function spatial_hydration_functions()
    return Dict(
        UInt8('X') => (psh, values)->hydrate_point(values...),
        UInt8('Y') => (psh, values)->hydrate_point(values...)
    )
end


using PSIS
using CairoMakie
using Test

@testset "Makie.jl recipes" begin
    PSIS.plotting_backend!(:Makie)

    @testset "paretoshapeplot/plot" begin
        @testset "$f(::$T; showlines=$showlines) sz=$sz" for f in
                                                             (paretoshapeplot, Makie.plot),
            T in (PSISResult, Vector),
            showlines in (true, false),
            sz in (100, (10, 100))

            f === Makie.plot && T <: Vector && continue
            result = psis(randn(sz...))
            values = T <: PSISResult ? result : result.pareto_shape
            plt = f(values; showlines=showlines, linewidth=5)
            @test plt.plot isa PSIS.MakieRecipe.ParetoShapePlot
            plots = plt.plot.plots
            @test plt.axis.xlabel[] == "Parameter index"
            @test plt.axis.ylabel[] == "Pareto shape"
            @test length(plots) == 5
            @test plots[5] isa Scatter
            @test plots[5].converted[1][] ==
                Point2f0.(Base.OneTo(result.nparams), PSIS.as_array(result.pareto_shape))
            linestyles = [:dot, :dashdot, :dash, :solid]
            yvals = [0, 0.5, 0.7, 1]
            line_xmin, line_xmax = sz isa Tuple ? (0.91, 10.09) : (1, 1)
            for i in 1:4
                @test plots[i] isa LineSegments
                @test plots[i].converted[1][] ==
                    [Point2f0(line_xmin, yvals[i]), Point2f0(line_xmax, yvals[i])]
                @test plots[i].attributes[:linestyle][] == linestyles[i]
                @test plots[i].attributes[:linewidth][] == 5
                @test plots[i].attributes[:visible][] == showlines
            end
        end
    end

    @testset "paretoshapeplot!/plot!" begin
        @testset "$f!(::$T; showlines=$showlines) sz=$sz" for f! in (
                paretoshapeplot!, Makie.plot!
            ),
            T in (PSISResult, Vector),
            showlines in (true, false),
            sz in (100, (10, 100))

            f! === Makie.plot! && T <: Vector && continue
            result = psis(randn(sz...))
            values = T <: PSISResult ? result : result.pareto_shape
            fig = Figure()
            ax = Axis(fig[1, 1])
            plt = f!(values; showlines=showlines, linewidth=5)
            axis = fig.content[1]
            @test last(axis.scene.plots) isa PSIS.MakieRecipe.ParetoShapePlot
            plots = last(axis.scene.plots).plots
            @test axis.xlabel[] == "Parameter index"
            @test axis.ylabel[] == "Pareto shape"
            @test length(plots) == 5
            @test plots[5] isa Scatter
            @test plots[5].converted[1][] ==
                Point2f0.(Base.OneTo(result.nparams), PSIS.as_array(result.pareto_shape))
            linestyles = [:dot, :dashdot, :dash, :solid]
            yvals = [0, 0.5, 0.7, 1]
            line_xmin, line_xmax = sz isa Tuple ? (0.91, 10.09) : (1, 1)
            for i in 1:4
                @test plots[i] isa LineSegments
                @test plots[i].converted[1][] ==
                    [Point2f0(line_xmin, yvals[i]), Point2f0(line_xmax, yvals[i])]
                @test plots[i].attributes[:linestyle][] == linestyles[i]
                @test plots[i].attributes[:linewidth][] == 5
                @test plots[i].attributes[:visible][] == showlines
            end
        end
    end
end


module TestMatrix

# TODO: Test `diag_dat` argument to `upload!` and `replace_coefficients!`

import ..repl_output
using AMGX, Defer, Test, JSON, CUDA, SparseArrays
using AMGX: Config, Resources, AMGXMatrix, dDDI, dFFI

@scope @testset "Matrix" begin
    c = @! Config("")
    r = @! Resources(c)
    m = @! AMGXMatrix(r, dDDI)
    @test occursin("dDDI", repl_output(m))

    @testset "upload" begin
        AMGX.upload!(m,
            Cint[0, 1, 3],
            Cint[1, 0, 1],
            [1.0, 2.0, 3.0]
        )
        @test nnz(m) == 3
        @test AMGX.matrix_get_size(m) == (2, (1,1))
        @test size(m) == (2, 2)
        @test occursin("of size 2×2 with 3 stored entries", repl_output(m))

        @testset "replace coefficients" begin
            # TODO: Should test this does something
            AMGX.replace_coefficients!(m, [2.0, 3.0, 4.0])
            @test_throws ArgumentError AMGX.replace_coefficients!(m, [2.0, 3.0, 4.0, 5.0])
            @test_throws ArgumentError AMGX.replace_coefficients!(m, Float32[2.0, 3.0, 4.0])
        end

        # Wrong element type
        m2 = @! AMGXMatrix(r, dFFI)
        @test_throws ArgumentError AMGX.upload!(m2,
            Cint[0, 1, 3],
            Cint[1, 0, 1],
            [1.0, 2.0, 3.0]
        )
    end

    @testset "blocked upload" begin
        # blocked
        blocks = [[1.0 2.0; 3.0 4.0],
                  [5.0 6.0; 7.0 8.0],
                  [9.0 10.0; 11.0 12.0]]
        blocks_flatten = collect(Iterators.flatten(blocks))
        AMGX.upload!(m,
            Cint[0, 1, 3],
            Cint[1, 0, 1],
            blocks_flatten;
            block_dims = (2,2)
        )
        @test AMGX.matrix_get_size(m) == (2, (2,2))
        @test size(m) == (4, 4)
        @test nnz(m) == 12
        @test occursin("of size 2⋅2×2⋅2 with 3 stored block entries", repl_output(m))

        @testset "replace coefficients" begin
            AMGX.replace_coefficients!(m, ones(Float64, length(blocks_flatten)))
            @test_throws ArgumentError AMGX.replace_coefficients!(m, ones(Float64, length(blocks_flatten)-1))
            @test_throws ArgumentError AMGX.replace_coefficients!(m, Float32.(blocks_flatten))
        end
    end

    @testset "upload rectangular" begin
        # rectangular
        AMGX.upload!(m,
            Cint[0, 1, 3],
            Cint[1, 0, 2],
            [1.0, 2.0, 3.0]
        )
        @test nnz(m) == 3
    end

    @testset "wrong element type" begin
        @test_throws ArgumentError AMGX.upload!(m,
            Cint[0, 1, 3],
            Cint[1, 0, 1],
            Float32[1.0, 2.0, 3.0]
        )
    end

    @scope @testset "upload device" begin
        AMGX.upload!(m,
            CuArray(Cint[0, 1, 3]),
            CuArray(Cint[1, 0, 1]),
            CuArray([1.0, 2.0, 3.0])
        )
        @test nnz(m) == 3
    end

    @testset "upload CUDA sparse matrix" begin
        c = CUDA.CUSPARSE.CuSparseMatrixCSR(sprand(Float64, 10, 10, 0.5))
        AMGX.upload!(m, c)
        @test nnz(m) == nnz(c)
        @test size(m) == size(c)
    end
end

end # module


# need conversions for things like:
# a = BInt{1:10,Int8}[ 1, 2, 3, 4 ]
# a = BInt{1:10}[ 1, 2, 3, 4 ]
# a[2] = 4

@generated function Base.convert{R}( ::Type{ Bool }, x::BInt{R} )
        l,u = get_range_bounds( R )
        if l > 0 || u < 0
                # range does not include 0 - so must be true
                return true
        elseif l == u == 0
                return false
        else
                return :( Bool(x.n) )
        end
end

# conversion from Integer to BInt:
@generated function Base.convert{R,T1<:Integer}( ::Type{ BInt{R} }, x::T1 )
        T = default_int_type( R )
        Tworking = default_int_type( range_union( T, T1 ) )
        l,u = get_range_bounds( R, Tworking )
        return :(
                if ( $Tworking(x) >= $l ) ;
                        if ( $Tworking(x) <= $u ) ;
                                return UncheckedBInt{R,$T}( x )
                        end;
                end;
                throw( InexactError() );
        )
end

@generated function Base.convert{R,T,T1<:Integer}( ::Type{ BInt{R,T} }, x::T1 )
        l,u = get_range_bounds( R, T )
        return :(
                if ( $T(x) >= $l ) ;
                        if ( $T(x) <= $u ) ;
                                return UncheckedBInt{R,T}( x )
                        end;
                end;
                throw( InexactError() );
        )
end

# conversion from BInt to Integer:
Base.convert{R1,T1,T<:Integer}( ::Type{ T }, x::BInt{R1,T1} ) = T( x.n )

# for conversion from BInt to BInt, only allow narrower ranges to
# be assigned to wider ranges
@generated function Base.convert{R1,R2,T2}( ::Type{ BInt{R2} }, x::BInt{R1,T2} )
        if !range_within_range( R1, R2 )
                throw( InexactError() );
        end
        :( UncheckedBInt{ R2 }( x.n ) )
end
@generated function Base.convert{R1,T1,R2,T2}( ::Type{ BInt{R2,T2} }, x::BInt{R1,T1} )
        if !range_within_range( R1, R2 )
                throw( InexactError() );
        end
        :( UncheckedBInt{ R2, T2 }( x.n ) )
end

# converting from a BInt to a narrower range BInt
# is a special case - want to make the runtime check
# be explicit
# so allow
#           Int -> BInt conversion
#           BInt -> wider BInt conversion
#           BInt -> narrower BInt - only with explicit tighten_range fn.

@generated function tighten_range{R,R2,T2}( ::Type{BInt{R}}, x::BInt{R2,T2} )
        T = default_int_type( R )
        l,u = get_range_bounds( R, T )

        :(
                if ( $T(x.n) < $l ) ; throw( InexactError() ); end;
                if ( $T(x.n) > $u ) ; throw( InexactError() ); end;
                BInt{R,$T}( $T(x.n), 0 )
        )
end

# this is a little lazy - there are corner cases where it will fail
# but Bint * Int * Int cannot be protected from overflow, so its a lost
# cause - stay within the BInt type, or else risk overflow

# this is because of things like promote_type( Int64, UInt64 ) which
# gives a UInt64.  While it is true that an Int64 can be losslessly converted
# to a UInt64 and back again, it doesn't mean that arithmetic will work
Base.promote_rule{R1,T1,T2<:Integer }(::Type{BInt{R1,T1}}, ::Type{T2} ) = promote_type( T1,T2)

Base.promote_rule{R1,R2}(::Type{ BInt{R1} },::Type{ BInt{R2} } ) = begin
        R = range_union( R1, R2 )
        T = default_int_type( R )
        return BInt{R,T}
end
Base.promote_rule{R1,T1,R2}(::Type{ BInt{R1,T1} },::Type{ BInt{R2} } ) = begin
        R = range_union( R1, R2 )
        T = default_int_type( R )
        return BInt{R,T}
end
Base.promote_rule{R1,T1,R2,T2}(::Type{ BInt{R1,T1} },::Type{ BInt{R2,T2} } ) = begin
        R = range_union( R1, R2 )
        T = default_int_type( R )
        return BInt{R,T}
end


#VERSION >= v"0.4.0-dev+6521" && __precompile__()

module ReTerms

using DataArrays, DataFrames, NLopt, StatsBase

export LMM,ReMat,VectorReMat,ColMajorLowerTriangular,DiagonalLowerTriangular

export AIC, BIC, fixef, lowerbd, objective, pwrss

using Base.LinAlg.BlasInt

import Base: ==

include("utils.jl")
include("blockmats.jl")
include("remat.jl")
include("paramlowertriangular.jl")
include("cfactor.jl")
include("inject.jl")
include("pls.jl")

end # module


"""
`module  JAC.MultiPhotonDeExcitation`
    ... a submodel of JAC that contains all methods for computing multi-photon excitation and decay rates.
"""
module MultiPhotonDeExcitation

    using Printf, ..Basics, ..ManyElectron, ..Nuclear, ..Radial, ..TableStrings

    """
    `struct  MultiPhotonDeExcitation.Settings`  ... defines a type for the settings in estimating multi-photon excitation and decay rates.

        + NoPhotons               ::Int64                        ... Number of photons in the multi-photon ionization
        + multipoles              ::Array{EmMultipole}           ... Specifies the multipoles of the radiation field that are to be included.
        + gauges                  ::Array{UseGauge}              ... Specifies the gauges to be included into the computations.
        + printBeforeComputation  ::Bool                         ... True, if all energies and lines are printed before their evaluation.
        + selectLines             ::Bool                         ... True, if lines are selected individually for the computations.
        + selectedLines           ::Array{Tuple{Int64,Int64},1}  ... List of lines, given by tupels (inital-level, final-level).

    """
    struct Settings
        NoPhotons                 ::Int64
        multipoles                ::Array{EmMultipole}
        gauges                    ::Array{UseGauge}
        printBeforeComputation    ::Bool
        selectLines               ::Bool
        selectedLines             ::Array{Tuple{Int64,Int64},1}
    end

    """
    `MultiPhotonDeExcitation.Settings()`  ... constructor for the default values of multi-photon excitation and decay rates.
    """
    function Settings()
        Settings(0, EmMultipole[], UseGauge[], false, false, Tuple{Int64,Int64}[])
    end

    # `Base.show(io::IO, settings::MultiPhotonDeExcitation.Settings)`
    #		... prepares a proper printout of the variable settings::MultiPhotonDeExcitation.Settings.
     function Base.show(io::IO, settings::MultiPhotonDeExcitation.Settings)
        println(io, "NoPhotons:                $(settings.NoPhotons)  ")
        println(io, "multipoles:               $(settings.multipoles)  ")
        println(io, "gauges:                   $(settings.gauges)  ")
        println(io, "printBeforeComputation:   $(settings.printBeforeComputation)  ")
        println(io, "selectLines:              $(settings.selectLines)  ")
        println(io, "selectedLines:            $(settings.selectedLines)  ")
    end

    """
    `struct  MultiPhotonDeExcitation.Channel`
        ... defines a type for a multi-photon channel to help characterize multi-photon (single-electron) ionization with well-defined
            multipolarities.

        + multipoles     ::Array{EmMultipole,1}   ... Multipoles of all N incoming/outgoing photons.
        + gauge          ::UseGauge               ... Gauge for dealing with the (coupled) radiation field.
        + amplitude      ::Complex{Float64}       ... multi-photon ionization amplitude associated with the given channel.
   """
    struct  Channel
        multipoles       ::Array{EmMultipole,1}
        gauge            ::UseGauge
        amplitude        ::Complex{Float64}
    end

    """
    `struct  MultiPhotonDeExcitation.Line`  ... defines a type for a multi-photon ionization line that may include the definition of channels.

        + initialLevel     ::Level                  ... initial-(state) level
        + finalLevel       ::Level                  ... final-(state) level
        + photonEnergy     ::Float64                ... Energy of the incoming photons; all photons are assumed to have equal energy.
        + totalRate        ::EmProperty             ... Cross section for this multi-photon ionization.
        + hasChannels      ::Bool                   ... Determines whether the individual (sub-) channels are defined in terms of their
                                                        multipolarities, etc., or not.
        + channels         ::Array{MultiPhotonDeExcitation.Channel,1}  ... List of MultiPhotonDeExcitation.Channels of this line.
    """
    struct  Line
        initialLevel       ::Level
        finalLevel         ::Level
        photonEnergy       ::Float64
        totalRate          ::EmProperty
        hasChannels        ::Bool
        channels           ::Array{MultiPhotonDeExcitation.Channel,1}
    end

    # `Base.show(io::IO, line::MultiPhotonDeExcitation.Line)`  ... prepares a proper printout of the variable line::MultiPhotonDeExcitation.Line.
    function Base.show(io::IO, line::MultiPhotonDeExcitation.Line)
        println(io, "initialLevel:      $(line.initialLevel)  ")
        println(io, "finalLevel:        $(line.finalLevel)  ")
        println(io, "photonEnergy:      $(line.photonEnergy)  ")
        println(io, "totalRate:         $(line.totalRate)  ")
        println(io, "hasChannels:       $(line.hasChannels)  ")
        println(io, "channels:          $(line.channels)  ")
    end

    """
    `MultiPhotonDeExcitation.computeAmplitudesProperties(line::MultiPhotonDeExcitation.Line, grid::Radial.Grid,
                                                             settings::MultiPhotonDeExcitation.Settings)`
        ... to compute all amplitudes and properties of the given line; a line::Einstein.Line is returned for which the amplitudes
            and properties are now evaluated.
    """
    function  computeAmplitudesProperties(line::MultiPhotonDeExcitation.Line, grid::Radial.Grid, settings::MultiPhotonDeExcitation.Settings)
        global JAC_counter
        newChannels = MultiPhotonDeExcitation.Channel[]
        for channel in line.channels
            # matrix    = MultiPhotonDeExcitation.computeMatrix(channel.multipole, channel.gauge, line.omega, line.finalLevel.basis,
            #                                                                                line.initialLevel.basis, grid, settings)
            # amplitude = line.finalLevel.mc * matrix * line.initialLevel.mc
            JAC_counter = JAC_counter + 1
            if   JAC_counter < 20   println("MultiPhotonDeExcitation.computeAmplitudesProperties-aa: warning ... amplitude always -1.")    end
            amplitude = 1.0
            push!( newChannels, MultiPhotonDeExcitation.Channel( channel.multipoles, channel.gauge, amplitude) )
        end
        # Calculate the photonrate and angular beta if requested
        JAC_counter = JAC_counter + 1
        if   JAC_counter < 20   println("MultiPhotonDeExcitation.computeAmplitudesProperties-ab: warning ... rate always -1.")    end
        totalRate = EmProperty(-1., -1.)
        line = MultiPhotonDeExcitation.Line( line.initialLevel, line.finalLevel, line.photonEnergy, totalRate, true, newChannels)
        return( line )
    end

    """
    `MultiPhotonDeExcitation.computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                                          settings::MultiPhotonDeExcitation.Settings; output=true)`
        ... to compute the multiphoton transition amplitudes and all properties as requested by the given settings. A list of
            lines::Array{MultiPhotonDeExcitation.Lines} is returned.
    """
    function  computeLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, grid::Radial.Grid,
                           settings::MultiPhotonDeExcitation.Settings; output=true)
        println("")
        printstyled("MultiPhotonDeExcitation.computeLines(): The computation of multiphoton transition amplitudes starts now ... \n", color=:light_green)
        printstyled("----------------------------------------------------------------------------------------------------------- \n", color=:light_green)
        println("")
        #
        lines = MultiPhotonDeExcitation.determineLines(finalMultiplet, initialMultiplet, settings)
        # Display all selected lines before the computations start
        if  settings.printBeforeComputation    MultiPhotonDeExcitation.displayLines(lines)    end
        # Calculate all amplitudes and requested properties
        newLines = MultiPhotonDeExcitation.Line[]
        for  line in lines
            newLine = MultiPhotonDeExcitation.computeAmplitudesProperties(line, grid, settings)
            push!( newLines, newLine)
        end
        # Print all results to screen
        MultiPhotonDeExcitation.displayRates(stdout, lines)
        printSummary, iostream = Defaults.getDefaults("summary flag/stream")
        if  printSummary    MultiPhotonDeExcitation.displayRates(iostream, lines)   end
        #
        if    output    return( lines )
        else            return( nothing )
        end
    end

    """
    `MultiPhotonDeExcitation.determineChannels(finalLevel::Level, initialLevel::Level, settings::MultiPhotonDeExcitation.Settings)`
        ... to determine a list of MultiPhotonDeExcitation.Channel for a transitions from the initial to final level and by taking into account
            the particular settings of for this computation; an Array{MultiPhotonDeExcitation.Channel,1} is returned.
    """
    function determineChannels(finalLevel::Level, initialLevel::Level, settings::MultiPhotonDeExcitation.Settings)
        global JAC_counter
        channels = MultiPhotonDeExcitation.Channel[];
        symi = LevelSymmetry(initialLevel.J, initialLevel.parity);    symf = LevelSymmetry(finalLevel.J, finalLevel.parity)
        mpListList = Array{Basics.EmMultipole,1}[];    morePhotons = true;    NoPhotons = 1
        for  mp in settings.multipoles    push!( mpListList, [mp] )    end
        if  NoPhotons + 1 > settings.NoPhotons    error("stop a")      end
        while  morePhotons
            NoPhotons     = NoPhotons + 1
            newMpListList = Array{Basics.EmMultipole,1}[]
            for  mpList in mpListList
                for  mp in settings.multipoles
                    mppList = deepcopy(mpList)
                    push!( newMpListList, push!(mppList, mp) )
                end
            end
            mpListList = deepcopy(newMpListList)
            if  NoPhotons + 1 > settings.NoPhotons    morePhotons = false   end
        end
        #
        for  gauge in settings.gauges
            for  mpList in mpListList
                # Cycle if list of multipoles cannot connect the initial and final levels for parity reasons
                JAC_counter = JAC_counter + 1
                if   JAC_counter < 5   println("MultiPhotonDeExcitation.determineChannels-aa: warning ... no test of parities yet.")  end
                push!( channels, MultiPhotonDeExcitation.Channel(mpList, gauge, 0.) )
            end
        end
        ##x println("MultiPhotonDeExcitation.determineChannels-ad:  channels = $channels")
        return( channels )
    end

    """
    `MultiPhotonDeExcitation.determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::MultiPhotonDeExcitation.Settings)`
        ... to determine a list of MultiPhotonDeExcitation Line's for transitions between the levels from the given initial- and final-state
            multiplets and by taking into account the particular selections and settings for this computation; an Array{MultiPhotonDeExcitation.Line,1}
            is returned. Apart from the level specification, all physical properties are set to zero during the initialization process.
    """
    function  determineLines(finalMultiplet::Multiplet, initialMultiplet::Multiplet, settings::MultiPhotonDeExcitation.Settings)
        if    settings.selectLines    selectLines   = true;   selectedLines = Basics.determine("selected lines", settings.selectedLines)
        else                          selectLines   = false
        end

        lines = MultiPhotonDeExcitation.Line[]
        for  i = 1:length(initialMultiplet.levels)
            for  f = 1:length(finalMultiplet.levels)
                if  selectLines  &&  !(haskey(selectedLines, (i,f)) )    continue   end
                ##x println("MultiPhotonDeExcitation.determineLines-aa: angular i = $i, f = $f")
                omega    = abs( initialMultiplet.levels[i].energy - finalMultiplet.levels[f].energy)

                channels = MultiPhotonDeExcitation.determineChannels(finalMultiplet.levels[f], initialMultiplet.levels[i], settings)
                push!( lines, MultiPhotonDeExcitation.Line(initialMultiplet.levels[i], finalMultiplet.levels[f], omega, EmProperty(0., 0.),
                                                           true, channels) )
            end
        end
        return( lines )
    end

    """
    `MultiPhotonDeExcitation.displayLines(lines::Array{Einstein.Line,1})`
        ... to display a list of lines and channels that have been selected due to the prior settings. A neat table of all selected
            transitions and energies is printed but nothing is returned otherwise.
    """
    function  displayLines(lines::Array{MultiPhotonDeExcitation.Line,1})
        println(" ")
        println("  Selected multi-photon excitation or decay lines:")
        println(" ")
        println("  ", TableStrings.hLine(165))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=2);                         sb = sb * TableStrings.hBlank(20)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.center(14, "Energy"; na=4);
        sb = sb * TableStrings.center(14, TableStrings.inUnits("energy"); na=4)
        sa = sa * TableStrings.flushleft(70, "List of multipoles"; na=4)
        sb = sb * TableStrings.flushleft(70, "Used gauge [multipolo_1, multipole_2, ...]"; na=4)
        println(sa);    println(sb);    println("  ", TableStrings.hLine(165))
        #
        for  line in lines
            sa  = "  ";    isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                           fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", line.photonEnergy)) * "    "
            mpGaugeList = Tuple{UseGauge, Array{Basics.EmMultipole,1}}[]
            for  i in 1:length(line.channels)
                push!( mpGaugeList, (line.channels[i].gauge, line.channels[i].multipoles) )
            end
            wa = TableStrings.gaugeMultipolesTupels(105, mpGaugeList)
            if  length(wa) > 0    sb = sa * wa[1];    println( sb )    end
            for  i = 2:length(wa)
                sb = TableStrings.hBlank( length(sa) );    sb = sb * wa[i];    println( sb )
            end
        end
        println("  ", TableStrings.hLine(165))
        #
        return( nothing )
    end

    """
    `MultiPhotonDeExcitation.displayRates(stream::IO, lines::Array{MultiPhotonDeExcitation.Line,1})`
        ... to display all results, energies, rates, etc. of the selected lines. A neat table is printed but nothing is
            returned otherwise.
    """
    function  displayRates(stream::IO, lines::Array{MultiPhotonDeExcitation.Line,1})
        println(stream, " ")
        println(stream, "  Multi-photon excitation or decay lines:")
        println(stream, " ")
        println(stream, "  ", TableStrings.hLine(95))
        sa = "  ";   sb = "  "
        sa = sa * TableStrings.center(18, "i-level-f"; na=2);                         sb = sb * TableStrings.hBlank(20)
        sa = sa * TableStrings.center(18, "i--J^P--f"; na=4);                         sb = sb * TableStrings.hBlank(22)
        sa = sa * TableStrings.center(14, "Energy"; na=4);
        sb = sb * TableStrings.center(14, TableStrings.inUnits("energy"); na=3)
        sa = sa * TableStrings.center(32, "Cou -- Rate -- Bab"; na=4);
        sb = sb * TableStrings.center(32, TableStrings.inUnits("rate") * "           " * TableStrings.inUnits("rate"); na=4)

        println(stream, sa);    println(stream, b);    println(stream, "  ", TableStrings.hLine(95))
        #
        for  line in lines
            sa  = "  ";    isym = LevelSymmetry( line.initialLevel.J, line.initialLevel.parity)
                           fsym = LevelSymmetry( line.finalLevel.J,   line.finalLevel.parity)
            sa = sa * TableStrings.center(18, TableStrings.levels_if(line.initialLevel.index, line.finalLevel.index); na=2)
            sa = sa * TableStrings.center(18, TableStrings.symmetries_if(isym, fsym); na=4)
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("energy: from atomic", line.photonEnergy))         * "    "
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("rate: from atomic",   line.totalRate.Coulomb))    * "    "
            sa = sa * @sprintf("%.8e", Defaults.convertUnits("rate: from atomic",   line.totalRate.Babushkin))  * "    "
            println(stream, sa )
        end
        println(stream, "  ", TableStrings.hLine(95))
        #
        return( nothing )
    end

end # module