text:
using SaferIntegers
using Test

baseint = SaferIntegers.baseint
safeint = SaferIntegers.safeint

macro no_error(x)
    :(@test (try $x; true; catch e; false; end))
end
macro is_error(x)
    :(@test (try $x; false; catch e; true; end))
end

@testset "construct0" begin
    for T in (:Int8, :Int16, :Int32, :Int64, :Int128, :UInt8, :UInt16, :UInt32, :UInt64, :UInt128)
      @eval begin
        @test $T(BigInt(2)) == $T(2)
      end
    end
end

@testset "construct1" begin
    for (S,I) in (
        (:SafeInt8, :Int8), (:SafeInt16, :Int16), (:SafeInt32, :Int32), (:SafeInt64, :Int64), (:SafeInt128, :Int128),
        (:SafeUInt8, :UInt8), (:SafeUInt16, :UInt16), (:SafeUInt32, :UInt32), (:SafeUInt64, :UInt64), (:SafeUInt128, :UInt128) )
      @eval begin
        @test safeint($I) === $S
        @test safeint($S) === $S
        @test baseint($I) === $I
        @test baseint($S) === $I
        @test safeint($I(5)) === reinterpret($S, $I(5))
        @test baseint($S(5)) === reinterpret($I, $S(5))
        @test safeint($S(5)) === $S(5)
        @test baseint($I(5)) === $I(5)
        @test $S($I(5)) === reinterpret($S, $I(5))
        @test $I($S(5)) === reinterpret($I, $S(5))
      end
    end
end

@testset "construct2" begin
    @test safeint(Bool) === Bool
    @test baseint(Bool) === Bool
    @test safeint(true) === true
    @test baseint(true) === true

    @test Float16(SafeInt(5)) === Float16(5)
    @test BigInt(SafeInt(5)) == BigInt(5)
    @test BigFloat(SafeInt(5)) == BigFloat(5)
end

@testset "construct3" begin
    @test SafeInt128(Float16(5)) === SafeInt128(5)
    @test SafeInt128(Float32(5)) === SafeInt128(5)
    @test SafeInt128(Float64(5)) === SafeInt128(5)
    @test SafeInt64(Float16(5)) === SafeInt64(5)
    @test SafeInt64(Float32(5)) === SafeInt64(5)
    @test SafeInt64(Float64(5)) === SafeInt64(5)
    @test SafeInt32(Float16(5)) === SafeInt32(5)
    @test SafeInt32(Float32(5)) === SafeInt32(5)
    @test SafeInt32(Float64(5)) === SafeInt32(5)
    @test SafeInt16(Float16(5)) === SafeInt16(5)
    @test SafeInt16(Float32(5)) === SafeInt16(5)
    @test SafeInt16(Float64(5)) === SafeInt16(5)
    @test SafeInt8(Float16(5)) === SafeInt8(5)
    @test SafeInt8(Float32(5)) === SafeInt8(5)
    @test SafeInt8(Float64(5)) === SafeInt8(5)

    @test SafeUInt128(Float16(5)) === SafeUInt128(5)
    @test SafeUInt128(Float32(5)) === SafeUInt128(5)
    @test SafeUInt128(Float64(5)) === SafeUInt128(5)
    @test SafeUInt64(Float16(5)) === SafeUInt64(5)
    @test SafeUInt64(Float32(5)) === SafeUInt64(5)
    @test SafeUInt64(Float64(5)) === SafeUInt64(5)
    @test SafeUInt32(Float16(5)) === SafeUInt32(5)
    @test SafeUInt32(Float32(5)) === SafeUInt32(5)
    @test SafeUInt32(Float64(5)) === SafeUInt32(5)
    @test SafeUInt16(Float16(5)) === SafeUInt16(5)
    @test SafeUInt16(Float32(5)) === SafeUInt16(5)
    @test SafeUInt16(Float64(5)) === SafeUInt16(5)
    @test SafeUInt8(Float16(5)) === SafeUInt8(5)
    @test SafeUInt8(Float32(5)) === SafeUInt8(5)
    @test SafeUInt8(Float64(5)) === SafeUInt8(5)
end

@testset "construct 4" begin
    @test SafeInt8(typemax(SafeInt8)) == typemax(SafeInt8)
    @test SafeInt16(typemax(SafeInt16)) == typemax(SafeInt16)
    @test SafeInt32(typemax(SafeInt32)) == typemax(SafeInt32)
    @test SafeInt64(typemax(SafeInt64)) == typemax(SafeInt64)
    @test SafeInt128(typemax(SafeInt128)) == typemax(SafeInt128)
    @test SafeUInt8(typemax(SafeUInt8)) == typemax(SafeUInt8)
    @test SafeUInt16(typemax(SafeUInt16)) == typemax(SafeUInt16)
    @test SafeUInt32(typemax(SafeUInt32)) == typemax(SafeUInt32)
    @test SafeUInt64(typemax(SafeUInt64)) == typemax(SafeUInt64)
    @test SafeUInt128(typemax(SafeUInt128)) == typemax(SafeUInt128)
end

@testset "checked signed" begin
    @test_throws OverflowError (+)(SafeInt8(120), SafeInt8(20))
    @test_throws OverflowError (-)(SafeInt8(-120), SafeInt8(20))
    @test_throws OverflowError (*)(SafeInt8(100), SafeInt8(100))
    @test div(SafeInt16(100), SafeInt16(20)) === SafeInt16(div(100, 20))
    @test fld(SafeInt16(100), SafeInt16(15)) === SafeInt16(fld(100, 15))
    @test fld1(SafeInt16(100), SafeInt16(15)) === SafeInt16(fld1(100, 15))
    @test cld(SafeInt16(100), SafeInt16(15)) === SafeInt16(cld(100, 15))
    @test mod(SafeInt16(100), SafeInt16(15)) === SafeInt16(mod(100, 15))
    @test mod1(SafeInt16(100), SafeInt16(15)) === SafeInt16(mod1(100, 15))
    @test rem(SafeInt16(100), SafeInt16(15)) === SafeInt16(rem(100, 15))

    @test_throws OverflowError (+)(typemax(SafeInt16), SafeInt8(20))
    @test_throws OverflowError (-)(typemin(SafeInt16), SafeInt8(20))
    @test_throws OverflowError (*)(typemax(SafeInt16), SafeInt8(100))
    @test div(SafeInt16(100), SafeInt64(20)) === SafeInt64(div(100, 20))
    @test fld(SafeInt16(100), SafeInt32(15)) === SafeInt32(fld(100, 15))
    @test fld1(SafeInt16(100), SafeInt32(15)) === SafeInt32(fld1(100, 15))
    @test cld(SafeInt16(100), SafeInt32(15)) === SafeInt32(cld(100, 15))
    @test mod(SafeInt16(100), SafeInt32(15)) === SafeInt32(mod(100, 15))
    @test mod1(SafeInt16(100), SafeInt32(15)) === SafeInt32(mod1(100, 15))
    @test rem(SafeInt16(100), SafeInt32(15)) === SafeInt32(rem(100, 15))
end

@testset "checked unsigned" begin
    @test_throws OverflowError (+)(SafeUInt8(250), SafeUInt8(20))
    @test_throws OverflowError (-)(SafeUInt8(10), SafeUInt8(20))
    @test_throws OverflowError (*)(SafeUInt8(100), SafeUInt8(100))
    @test div(SafeUInt16(100), SafeUInt16(20)) === SafeUInt16(div(UInt16(100), UInt16(20)))
    @test fld(SafeUInt16(100), SafeUInt16(15)) === SafeUInt16(fld(UInt16(100), UInt16(15)))
    @test fld1(SafeUInt16(100), SafeUInt16(15)) === SafeUInt16(fld1(UInt16(100), UInt16(15)))
    @test cld(SafeUInt16(100), SafeUInt16(15)) === SafeUInt16(cld(UInt16(100), UInt16(15)))
    @test mod(SafeUInt16(100), SafeUInt16(15)) === SafeUInt16(mod(UInt16(100), UInt16(15)))
    @test mod1(SafeUInt16(100), SafeUInt16(15)) === SafeUInt16(mod1(UInt16(100), UInt16(15)))
    @test rem(SafeUInt16(100), SafeUInt16(15)) === SafeUInt16(rem(UInt16(100), UInt16(15)))

    @test_throws OverflowError (+)(SafeUInt8(10), typemax(SafeUInt16))
    @test_throws OverflowError (-)(SafeUInt8(10), typemax(SafeUInt16))
    @test_throws OverflowError (*)(SafeUInt8(100), typemax(SafeUInt16))
    @test div(SafeUInt32(100), SafeUInt16(20)) === SafeUInt32(div(UInt16(100), UInt16(20)))
    @test fld(SafeUInt32(100), SafeUInt16(15)) === SafeUInt32(fld(UInt16(100), UInt16(15)))
    @test fld1(SafeUInt32(100), SafeUInt16(15)) === SafeUInt32(fld1(UInt16(100), UInt16(15)))
    @test cld(SafeUInt32(100), SafeUInt16(15)) === SafeUInt32(cld(UInt16(100), UInt16(15)))
    @test mod(SafeUInt32(100), SafeUInt16(15)) === SafeUInt32(mod(UInt16(100), UInt16(15)))
    @test mod1(SafeUInt32(100), SafeUInt16(15)) === SafeUInt32(mod1(UInt16(100), UInt16(15)))
    @test rem(SafeUInt32(100), SafeUInt16(15)) === SafeUInt32(rem(UInt16(100), UInt16(15)))
end

@testset "throw" begin
    for (S,T) in ((:SafeInt8, :Int8), (:SafeInt16, :Int16), (:SafeInt32, :Int32),
                  (:SafeInt64, :Int64), (:SafeInt128, :Int128))
       @eval begin
            @test one($S) === $S(one($T))
            @test one($T) === $T(one($S))
            @no_error( $S(typemin($T)) )
            @is_error( $S(typemax($T)) + one($T) )
            @is_error( $S(typemin($T)) - one($T) )
            @is_error( $S(typemax($T)) + one($S) )
            @is_error( $S(typemin($T)) - one($S) )
       end
    end

    for (S,T) in ((:SafeUInt8, :UInt8), (:SafeUInt16, :UInt16), (:SafeUInt32, :UInt32),
                  (:SafeUInt64, :UInt64), (:SafeUInt128, :UInt128))
       @eval begin
            @test one($S) === $S(one($T))
            @test one($T) === $T(one($S))
            @no_error( $S(typemax($T)) )
            @is_error( $S(typemax($T)) + one($T) )
            @is_error( zero($S) - one($T) )
            @is_error( $S(typemax($T)) + one($S) )
            @is_error( zero($S) - one($S) )
            @test $S(5) === $S($T(5))
            @test $T(5) === $T($S(5))
       end
    end
end

@testset "-unsigned" begin
    @is_error( -SafeUInt(0) )
    @is_error( -SafeUInt8(1) )
    @is_error( -SafeUInt16(1) )
    @is_error( -SafeUInt32(1) )
    @is_error( -SafeUInt64(1) )
    @is_error( -SafeUInt128(1) )
end

@testset "float" begin
    @test Float64(SafeInt16(22)) === Float64(22)
    @test Float32(SafeInt64(22)) === Float32(22)
end

@testset "constructors" begin
    @test SafeInt16(Int8(2)) === SafeInt16(2)
    @test Int16(SafeInt8(2)) === Int16(2)
    @test SafeUInt64(UInt8(2)) === SafeUInt64(2)
    @test UInt16(SafeUInt64(2)) === UInt16(2)
    @test SafeInt16(UInt32(2)) === SafeInt16(2)
    @test UInt32(SafeInt8(2)) === UInt32(2)
    @test SafeUInt128(Int8(2)) === SafeUInt128(2)
    @test Int16(SafeUInt128(2)) === Int16(2)
end

@testset "comparisons" begin
    @test SafeInt64(7) ==  SafeInt32(7)
    @test SafeInt32(7) !=  SafeInt32(2)
    @test SafeInt16(7) >  SafeInt32(2)
    @test SafeInt8(7)  >= SafeInt64(2)
    @test SafeInt16(2) <  SafeInt32(7)
    @test SafeInt8(2)  <= SafeInt64(7)
    @test isless(SafeInt16(2), SafeInt16(7))
    @test isequal(SafeInt16(2), SafeInt64(2))

    @test SafeInt64(7) ==  Int32(7)
    @test SafeInt32(7) !=  Int32(2)
    @test SafeInt16(7) >   Int32(2)
    @test SafeInt8(7)  >=  Int64(2)
    @test SafeInt16(2) <   Int32(7)
    @test SafeInt8(2)  <=  Int64(7)
    @test isless(SafeInt16(2), Int16(7))
    @test isequal(SafeInt16(2), Int64(2))

    @test Int64(7) ==  SafeInt32(7)
    @test Int32(7) !=  SafeInt32(2)
    @test Int16(7) >  SafeInt32(2)
    @test Int8(7)  >= SafeInt64(2)
    @test Int16(2) <  SafeInt32(7)
    @test Int8(2)  <= SafeInt64(7)
    @test isless(Int16(2), SafeInt16(7))
    @test isequal(Int16(2), SafeInt64(2))
end

@testset "boolean" begin
    @test ~SafeUInt32(5) === SafeUInt32(~UInt32(5))
    @test SafeInt32(7) & SafeInt32(2) === SafeInt32(7&2)
    @test SafeInt16(7) | SafeInt16(2) === SafeInt16(7|2)
    @test xor(SafeInt64(7), SafeInt64(2)) === SafeInt64(xor(7,2))

    @test SafeInt32(7) & SafeInt16(2) === SafeInt32(7&2)
    @test SafeInt32(7) | SafeInt16(2) === SafeInt32(7|2)
    @test xor(SafeInt32(7), SafeInt16(2)) === SafeInt32(xor(7,2))

    @test ~SafeUInt32(5) == ~UInt32(5)
    @test SafeInt32(7) & Int32(2) === SafeInt32(7&2)
    @test SafeInt16(7) | Int16(2) === SafeInt16(7|2)
    @test xor(SafeInt64(7), Int64(2)) === SafeInt64(xor(7,2))

    @test Int32(7) & SafeInt32(2) === SafeInt32(7&2)
    @test Int16(7) | SafeInt16(2) === SafeInt16(7|2)
    @test xor(Int64(7), SafeInt64(2)) === SafeInt64(xor(7,2))
end

@testset "arithmetic" begin
    @test SafeInt16(7) + SafeInt32(2) === SafeInt32(7+2)
    @test SafeInt8(7) - SafeInt64(2) === SafeInt64(7-2)
    @test SafeInt32(7) * SafeInt16(2) === SafeInt32(7*2)
    @test div(SafeInt32(7), SafeInt16(2)) === SafeInt32(div(7,2))
    @test rem(SafeInt32(7), SafeInt16(-2)) === SafeInt32(rem(7,-2))
    @test mod(SafeInt32(7), SafeInt16(-2)) === SafeInt32(mod(7,-2))
    @test fld(SafeInt32(7), SafeInt16(2)) === SafeInt32(fld(7,2))
    @test mod1(SafeInt32(7), SafeInt16(-2)) === SafeInt32(mod1(7,-2))
    @test fld1(SafeInt32(7), SafeInt16(2)) === SafeInt32(fld1(7,2))
    @test cld(SafeInt32(7), SafeInt16(2)) === SafeInt32(cld(7,2))

    @test SafeInt16(7) + Int32(2) === SafeInt16(7+2)
    @test SafeInt8(7) - Int64(2) === SafeInt8(7-2)
    @test SafeInt32(7) * Int16(2) === SafeInt32(7*2)
    @test div(SafeInt32(7), Int16(2)) === SafeInt32(div(7,2))
    @test rem(SafeInt32(7), Int16(-2)) === SafeInt32(rem(7,-2))
    @test mod(SafeInt32(7), Int16(-2)) === SafeInt32(mod(7,-2))
    @test fld(SafeInt32(7), Int16(2)) === SafeInt32(fld(7,2))
    @test mod1(SafeInt32(7), Int16(-2)) === SafeInt32(mod1(7,-2))
    @test fld1(SafeInt32(7), Int16(2)) === SafeInt32(fld1(7,2))
    @test cld(SafeInt32(7), Int16(2)) === SafeInt32(cld(7,2))

    @test Int16(7) + SafeInt32(2) === SafeInt32(7+2)
    @test Int8(7) - SafeInt64(2) === SafeInt64(7-2)
    @test Int32(7) * SafeInt16(2) === SafeInt16(7*2)
    @test div(Int32(7), SafeInt16(2)) === SafeInt16(div(7,2))
    @test rem(Int32(7), SafeInt16(-2)) === SafeInt16(rem(7,-2))
    @test mod(Int32(7), SafeInt16(-2)) === SafeInt16(mod(7,-2))
    @test fld(Int32(7), SafeInt16(2)) === SafeInt16(fld(7,2))
    @test mod1(Int32(7), SafeInt16(-2)) === SafeInt16(mod1(7,-2))
    @test fld1(Int32(7), SafeInt16(2)) === SafeInt16(fld1(7,2))
    @test cld(Int32(7), SafeInt16(2)) === SafeInt16(cld(7,2))

    @test Int32(521) + Int32(125) == Int32(SafeInt32(521) + SafeInt32(125))
    @test Int32(521) - Int32(125) == Int32(SafeInt32(521) - SafeInt32(125))
    @test Int32(521) * Int32(125) == Int32(SafeInt32(521) * SafeInt32(125))
    @test div(Int32(521), Int32(125)) == Int32(div(SafeInt32(521), SafeInt32(125)))

    @test SafeInt32(521) + SafeInt32(125) == SafeInt32(Int32(521) + Int32(125))
    @test SafeInt32(521) - SafeInt32(125) == SafeInt32(Int32(521) - Int32(125))
    @test SafeInt32(521) * SafeInt32(125) == SafeInt32(Int32(521) * Int32(125))
    @test div(SafeInt32(521), SafeInt32(125)) == SafeInt32(div(Int32(521), Int32(125)))

    @test Int64(521) + Int64(125) == Int64(SafeInt64(521) + SafeInt64(125))
    @test Int64(521) - Int64(125) == Int64(SafeInt64(521) - SafeInt64(125))
    @test Int64(521) * Int64(125) == Int64(SafeInt64(521) * SafeInt64(125))
    @test div(Int64(521), Int64(125)) == Int64(div(SafeInt64(521), SafeInt64(125)))

    @test SafeInt64(521) + SafeInt64(125) == SafeInt64(Int64(521) + Int64(125))
    @test SafeInt64(521) - SafeInt64(125) == SafeInt64(Int64(521) - Int64(125))
    @test SafeInt64(521) * SafeInt64(125) == SafeInt64(Int64(521) * Int64(125))
    @test div(SafeInt64(521), SafeInt64(125)) == SafeInt64(div(Int64(521), Int64(125)))

    @test Int64(73)^Int64(5) == Int64(SafeInt64(73)^SafeInt64(5))
    @test SafeInt64(73)^SafeInt64(5) == SafeInt64(Int64(73)^Int64(5))
    @test Int32(73)^Int32(5) == Int64(SafeInt32(73)^SafeInt32(5))
    @test SafeInt32(73)^SafeInt32(5) == SafeInt32(Int32(73)^Int32(5))

    @test Int32(521)+Int32(125) == Int32(SafeInt32(521) + SafeInt32(125))
    @test Int32(521)-Int32(125) == Int32(SafeInt32(521) - SafeInt32(125))
end

@testset "checked signed arithmetic" begin
    @test SaferIntegers.checked_neg(SafeInt16(7)) === SafeInt16(-7)
    @test SaferIntegers.checked_abs(SafeInt16(-7)) === SafeInt16(7)

    @test SaferIntegers.checked_add(SafeInt32(7), SafeInt32(2)) === SafeInt32(7+2)
    @test SaferIntegers.checked_sub(SafeInt64(7), SafeInt64(2)) === SafeInt64(7-2)
    @test SaferIntegers.checked_mul(SafeInt32(7), SafeInt32(2)) === SafeInt32(7*2)
    @test SaferIntegers.checked_div(SafeInt32(7), SafeInt32(2)) === SafeInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeInt32(7), SafeInt32(-2)) === SafeInt32(rem(7,-2))
    @test SaferIntegers.checked_mod(SafeInt32(7), SafeInt32(-2)) === SafeInt32(mod(7,-2))
    @test SaferIntegers.checked_fld(SafeInt16(7), SafeInt16(2)) === SafeInt16(fld(7,2))
    @test SaferIntegers.checked_cld(SafeInt32(7), SafeInt32(2)) === SafeInt32(cld(7,2))

    @test SaferIntegers.checked_add(SafeInt16(7), SafeInt32(2)) === SafeInt32(7+2)
    @test SaferIntegers.checked_sub(SafeInt8(7), SafeInt64(2)) === SafeInt64(7-2)
    @test SaferIntegers.checked_mul(SafeInt32(7), SafeInt16(2)) === SafeInt32(7*2)
    @test SaferIntegers.checked_div(SafeInt32(7), SafeInt16(2)) === SafeInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeInt32(7), SafeInt16(-2)) === SafeInt32(rem(7,-2))
    @test SaferIntegers.checked_mod(SafeInt32(7), SafeInt16(-2)) === SafeInt32(mod(7,-2))
    @test SaferIntegers.checked_fld(SafeInt32(7), SafeInt16(2)) === SafeInt32(fld(7,2))
    @test SaferIntegers.checked_cld(SafeInt32(7), SafeInt16(2)) === SafeInt32(cld(7,2))

    @test SaferIntegers.add_with_overflow(SafeInt32(7), SafeInt32(2))[2] === false
    @test SaferIntegers.sub_with_overflow(SafeInt32(7), SafeInt32(2))[2] === false
    @test SaferIntegers.mul_with_overflow(SafeInt32(7), SafeInt32(2))[2] === false

    @test SaferIntegers.add_with_overflow(SafeInt32(7), SafeInt16(2))[2] === false
    @test SaferIntegers.sub_with_overflow(SafeInt32(7), SafeInt16(2))[2] === false
    @test SaferIntegers.mul_with_overflow(SafeInt32(7), SafeInt16(2))[2] === false
end

@testset "checked unsigned arithmetic" begin
    @test SaferIntegers.checked_add(SafeUInt32(7), SafeUInt32(2)) === SafeUInt32(7+2)
    @test SaferIntegers.checked_sub(SafeUInt64(7), SafeUInt64(2)) === SafeUInt64(7-2)
    @test SaferIntegers.checked_mul(SafeUInt32(7), SafeUInt32(2)) === SafeUInt32(7*2)
    @test SaferIntegers.checked_div(SafeUInt32(7), SafeUInt32(2)) === SafeUInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeUInt32(7), SafeUInt32(2)) === SafeUInt32(rem(7,2))
    @test SaferIntegers.checked_mod(SafeUInt32(7), SafeUInt32(2)) === SafeUInt32(mod(7,2))
    @test SaferIntegers.checked_fld(SafeUInt16(7), SafeUInt16(2)) === SafeUInt16(fld(7,2))
    @test SaferIntegers.checked_cld(SafeUInt32(7), SafeUInt32(2)) === SafeUInt32(cld(7,2))

    @test SaferIntegers.checked_add(SafeUInt16(7), SafeUInt32(2)) === SafeUInt32(7+2)
    @test SaferIntegers.checked_sub(SafeUInt8(7), SafeUInt64(2)) === SafeUInt64(7-2)
    @test SaferIntegers.checked_mul(SafeUInt32(7), SafeUInt16(2)) === SafeUInt32(7*2)
    @test SaferIntegers.checked_div(SafeUInt32(7), SafeUInt16(2)) === SafeUInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeUInt32(7), SafeUInt16(2)) === SafeUInt32(rem(7,2))
    @test SaferIntegers.checked_mod(SafeUInt32(7), SafeUInt16(2)) === SafeUInt32(mod(7,2))
    @test SaferIntegers.checked_fld(SafeUInt32(7), SafeUInt16(2)) === SafeUInt32(fld(7,2))
    @test SaferIntegers.checked_cld(SafeUInt32(7), SafeUInt16(2)) === SafeUInt32(cld(7,2))

    @test SaferIntegers.add_with_overflow(SafeUInt32(7), SafeUInt32(2))[2] === false
    @test SaferIntegers.sub_with_overflow(SafeUInt32(7), SafeUInt32(2))[2] === false
    @test SaferIntegers.mul_with_overflow(SafeUInt32(7), SafeUInt32(2))[2] === false

    @test SaferIntegers.add_with_overflow(SafeUInt32(7), SafeUInt16(2))[2] === false
    @test SaferIntegers.sub_with_overflow(SafeUInt32(7), SafeUInt16(2))[2] === false
    @test SaferIntegers.mul_with_overflow(SafeUInt32(7), SafeUInt16(2))[2] === false
end

@testset "checked mixed arithmetic" begin
    @test SaferIntegers.checked_add(SafeInt32(7), SafeUInt32(2)) === SafeUInt32(7+2)
    @test SaferIntegers.checked_sub(SafeInt64(7), SafeUInt64(2)) === SafeUInt64(7-2)
    @test SaferIntegers.checked_mul(SafeInt32(7), SafeUInt32(2)) === SafeUInt32(7*2)
    @test SaferIntegers.checked_div(SafeInt32(7), SafeUInt32(2)) === SafeUInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeInt32(7), SafeUInt32(2)) === SafeUInt32(rem(7,2))
    @test SaferIntegers.checked_mod(SafeInt32(7), SafeUInt32(2)) === SafeUInt32(mod(7,2))
    @test SaferIntegers.checked_fld(SafeInt16(7), SafeUInt16(2)) === SafeUInt16(fld(7,2))
    @test SaferIntegers.checked_cld(SafeInt32(7), SafeUInt32(2)) === SafeUInt32(cld(7,2))

    @test SaferIntegers.checked_add(SafeInt16(7), SafeUInt32(2)) === SafeUInt32(7+2)
    @test SaferIntegers.checked_sub(SafeInt8(7), SafeUInt64(2)) === SafeUInt64(7-2)
    @test SaferIntegers.checked_mul(SafeInt32(7), SafeUInt16(2)) === SafeInt32(7*2)
    @test SaferIntegers.checked_div(SafeInt32(7), SafeUInt16(2)) === SafeInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeInt32(7), SafeUInt16(2)) === SafeInt32(rem(7,2))
    @test SaferIntegers.checked_mod(SafeInt32(7), SafeUInt16(2)) === SafeInt32(mod(7,2))
    @test SaferIntegers.checked_fld(SafeInt32(7), SafeUInt16(2)) === SafeInt32(fld(7,2))
    @test SaferIntegers.checked_cld(SafeInt32(7), SafeUInt16(2)) === SafeInt32(cld(7,2))
end

@testset "checked safe with unsafe arithmetic" begin
    @test SaferIntegers.checked_add(SafeInt32(7), Int32(2)) === SafeInt32(7+2)
    @test SaferIntegers.checked_sub(SafeInt64(7), Int64(2)) === SafeInt64(7-2)
    @test SaferIntegers.checked_mul(SafeInt32(7), Int32(2)) === SafeInt32(7*2)
    @test SaferIntegers.checked_div(SafeInt32(7), Int32(2)) === SafeInt32(div(7,2))
    @test SaferIntegers.checked_rem(SafeInt32(7), Int32(2)) === SafeInt32(rem(7,2))
    @test SaferIntegers.checked_mod(SafeInt32(7), Int32(2)) === SafeInt32(mod(7,2))
    @test SaferIntegers.checked_fld(SafeInt16(7), Int16(2)) === SafeInt16(fld(7,2))
    @test SaferIntegers.checked_cld(SafeInt32(7), Int32(2)) === SafeInt32(cld(7,2))

    @test SaferIntegers.checked_add(Int16(7), SafeInt32(2)) === SafeInt32(7+2)
    @test SaferIntegers.checked_sub(Int8(7), SafeInt64(2)) === SafeInt64(7-2)
    @test SaferIntegers.checked_mul(Int32(7), SafeInt16(2)) === SafeInt16(7*2)
    @test SaferIntegers.checked_div(Int32(7), SafeInt16(2)) === SafeInt16(div(7,2))
    @test SaferIntegers.checked_rem(Int32(7), SafeInt16(2)) === SafeInt16(rem(7,2))
    @test SaferIntegers.checked_mod(Int32(7), SafeInt16(2)) === SafeInt16(mod(7,2))
    @test SaferIntegers.checked_fld(Int32(7), SafeInt16(2)) === SafeInt16(fld(7,2))
    @test SaferIntegers.checked_cld(Int32(7), SafeInt64(2)) === SafeInt64(cld(7,2))
end

@testset "divide" begin
    @test (/)(SafeInt32(625), SafeInt32(125)) == (/)(Int32(625), Int32(125))
    @test (/)(SafeInt64(625), SafeInt32(125)) == (/)(Int64(625), Int32(125))
    @test (/)(SafeInt32(625), Int32(125)) == (/)(Int32(625), Int32(125))
    @test (/)(Int32(625), SafeInt32(125)) == (/)(Int32(625), Int32(125))
    @test (/)(Int64(625), Int64(125)) == Int64((/)(SafeInt64(625), SafeInt64(125)))
    @test (\)(SafeInt32(125), SafeInt32(625)) == (\)(Int32(125), Int32(625))
    @test (\)(SafeInt64(125), SafeInt32(625)) == (\)(Int64(125), Int32(625))
    @test (\)(SafeInt32(125), Int32(625)) == (\)(Int32(125), Int32(625))
    @test (\)(SafeInt32(125), Int32(625)) == (\)(Int32(125), Int32(625))
    @test (\)(Int32(125), SafeInt32(625)) == (\)(Int32(125), Int32(625))
    @test (\)(Int64(125), Int64(625)) == Int64((\)(SafeInt64(125), SafeInt64(625)))
end

@testset "number theory" begin
    @test lcm(SafeInt32(7), SafeInt16(2)) === SafeInt32(lcm(7,2))
    @test gcd(SafeInt32(7), SafeInt16(-2)) === SafeInt32(gcd(7,-2))
    @test divrem(SafeInt32(7), SafeInt16(2)) === SafeInt32.(divrem(7,2))
    @test fldmod(SafeInt32(7), SafeInt16(-2)) === SafeInt32.(fldmod(7,-2))
    @test fldmod1(SafeInt32(7), SafeInt16(-2)) === SafeInt32.(fldmod1(7,-2))
    @test SaferIntegers.divgcd(SafeInt32(7), SafeInt32(-2)) === SafeInt32.(Base.divgcd(7,-2))
    @test SaferIntegers.divgcd(SafeInt32(7), SafeInt16(-2)) === SafeInt32.(Base.divgcd(7,-2))

    @test lcm(SafeInt32(7), Int16(2)) === SafeInt32(lcm(7,2))
    @test gcd(SafeInt32(7), Int16(-2)) === SafeInt32(gcd(7,-2))
    @test divrem(SafeInt32(7), Int16(2)) === SafeInt32.(divrem(7,2))
    @test fldmod(SafeInt32(7), Int16(-2)) === SafeInt32.(fldmod(7,-2))
    @test fldmod1(SafeInt32(7), Int16(-2)) === SafeInt32.(fldmod1(7,-2))
    @test SaferIntegers.divgcd(SafeInt32(7), Int16(-2)) === SafeInt32.(Base.divgcd(7,-2))

    @test lcm(Int32(7), SafeInt16(2)) === SafeInt16(lcm(7,2))
    @test gcd(Int32(7), SafeInt16(-2)) === SafeInt16(gcd(7,-2))
    @test divrem(Int32(7), SafeInt16(2)) === SafeInt16.(divrem(7,2))
    @test fldmod(Int32(7), SafeInt16(-2)) === SafeInt16.(fldmod(7,-2))
    @test fldmod1(Int32(7), SafeInt16(-2)) === SafeInt16.(fldmod1(7,-2))
    @test SaferIntegers.divgcd(Int32(7), SafeInt16(-2)) === SafeInt16.(Base.divgcd(7,-2))
end

@testset "int ops" begin
    @test copysign(SafeInt32(1), SafeInt32(-1)) === SafeInt32(-1)
    @test copysign(SafeInt32(-1), SafeInt32(1)) === SafeInt32(1)
    @test copysign(SafeInt16(1), -1.0) === SafeInt16(-1)
    @test copysign(SafeInt16(-1), SafeInt32(1)) === SafeInt16(1)
    @test copysign(1, SafeInt16(1)) === 1
    @test copysign(1, SafeInt16(-1)) === -1
    @test copysign(SafeInt16(1), -1) === SafeInt16(-1)
    @test copysign(SafeInt16(-1), 1) === SafeInt16(1)
    @test copysign(SafeInt16(1), SafeInt32(-1)) === SafeInt16(-1)
    @test copysign(SafeInt16(-1), SafeInt32(1)) === SafeInt16(1)

    @test flipsign(SafeInt32(1), SafeInt32(-1)) === SafeInt32(-1)
    @test flipsign(SafeInt32(-1), SafeInt32(1)) === SafeInt32(-1)
    @test flipsign(SafeInt16(1), -1.0) === SafeInt16(-1)
    @test flipsign(SafeInt16(-1), SafeInt32(1)) === SafeInt16(-1)
    @test flipsign(1, SafeInt16(1)) === 1
    @test flipsign(1, SafeInt16(-1)) === -1
    @test flipsign(SafeInt16(1), -1) === SafeInt16(-1)
    @test flipsign(SafeInt16(-1), 1) === SafeInt16(-1)
    @test flipsign(SafeInt16(1), SafeInt32(-1)) === SafeInt16(-1)
    @test flipsign(SafeInt32(-1), SafeInt16(1)) === SafeInt32(-1)

    @test -SafeInt(1) === SafeInt(-1)

    @test abs2(SafeInt(5)) === SafeInt(25)
    @test abs2(SafeUInt(5)) === SafeUInt(25)
end

# shifts
const bitsof = SaferIntegers.bitsof

@testset "shifts" begin
    @no_error( SafeInt(typemax(Int)) << bitsof(Int) )
    @is_error( SafeInt(typemax(Int)) << (bitsof(Int)+1) )
    @no_error( SafeInt(typemax(Int)) << (-bitsof(Int)) )
    @is_error( SafeInt(typemax(Int)) << (-(bitsof(Int)+1)) )

    @no_error( SafeInt(typemax(Int)) >>> bitsof(Int) )
    @is_error( SafeInt(typemax(Int)) >>> (bitsof(Int)+1) )
    @no_error( SafeInt(typemax(Int)) >>> (-bitsof(Int)) )
    @is_error( SafeInt(typemax(Int)) >>> (-(bitsof(Int)+1)) )

    @test SafeInt32(7) >>> SafeInt32(2) === SafeInt32(7 >>> 2)
    @test SafeInt32(7) >> SafeInt32(2) === SafeInt32(7 >> 2)
    @test SafeInt32(7) << SafeInt32(2) === SafeInt32(7 << 2)

    @test SafeInt32(7) >>> SafeInt16(2) === SafeInt32(7 >>> 2)
    @test SafeInt16(7) >> SafeInt32(2) === SafeInt16(7 >> 2)
    @test SafeInt16(7) << SafeInt32(2) === SafeInt16(7 << 2)

    @test SafeInt32(7) >>> Int32(2) === SafeInt32(7 >>> 2)
    @test SafeInt32(7) >> Int32(2) === SafeInt32(7 >> 2)
    @test SafeInt32(7) << Int32(2) === SafeInt32(7 << 2)

    @test Int32(7) >>> SafeInt32(2) === Int32(7 >>> 2)
    @test Int32(7) >> SafeInt32(2) === Int32(7 >> 2)
    @test Int32(7) << SafeInt32(2) === Int32(7 << 2)
end

@testset "power" begin
    @test SafeInt32(7)^SafeInt32(5) === SafeInt32(7^5)
    @test SafeInt32(7)^5 === SafeInt32(7^5)
    @test 7^SafeInt32(5) === 7^5
    @test SafeInt32(32)^3 === SafeInt32(32^3)

    @test SafeInt64(40)^SafeInt32(2) === SafeInt64(Int64(40)^2)
    @test SafeInt64(130)^SafeInt64(2) === SafeInt64(Int64(130)^2)
    @test_throws OverflowError SafeInt16(32)^SafeInt16(32)
    @test_throws DomainError SafeInt16(32)^SafeInt16(-2)
    @test_throws DomainError SafeInt64(130)^SafeInt64(-2)

    @test SafeInt64(40)^2 === SafeInt64(Int64(40^2))
    @test Int64(40)^SafeInt32(2) === Int64(40)^2
    @test SafeInt64(40)^2 === SafeInt64(Int64(40)^2)

    @test SaferIntegers.ipower(SafeInt32(2), SafeInt32(3)) === SafeInt32(8)
    @test_throws OverflowError SaferIntegers.ipower(SafeInt16(16), SafeInt16(16))
    @test_throws OverflowError SaferIntegers.ipower(typemax(SafeInt64), typemax(SafeInt64))
end

@testset "rational" begin
    a = SafeInt32(32); b = SafeInt32(25); c = a//b - 1;

    @test typeof(SafeInt32(7) // SafeInt32(5)) === Rational{SafeInt32}
    @test typeof(c) === Rational{SafeInt32}
    @test a//b * b//a === Rational{SafeInt32}(1,1)
end

@testset "string" begin
    @test string(SafeInt16(5)) == string(Int16(5))
    @test string(SafeUInt16(5)) == string(UInt16(5))
end


text:
# module Buffer

using DataFrames
import Base.size, Base.getindex

# export CircularBuffer, add!, size, capacity, getindex
# abstract Buffer

"""
CircularBuffer

Maintains a buffer of fixed size w/o reallocating and deallocating memory through a circular queue data struct.
"""
mutable struct CircularBuffer
    """The structure the data is stored"""
    _data_frame::DataFrame
    """Current column."""
    _current_row::Int64
    """Max size"""
    _capacity::Int64
    """Whether the datastruct is full (i.e. has gone through a single rotation)"""
    _full::Bool
    """Data_types of the data stored in the buffer."""
    _data_types::Array{DataType, 1}
    function CircularBuffer(size, types; column_names=nothing)
        if column_names != nothing
            data_frame = DataFrame(types, column_names, size)
        else
            data_frame = DataFrame(types, size)
        end
        new(data_frame, 1, size, false, types)
    end
    function CircularBuffer(args...; kwargs...)
        data_frame = DataFrame(args...; kwargs...)
        new(data_frame, 1, size(data_frame)[1], false, eltypes(d))
    end
end

# function _set_row
"""
    add!(buffer, data)

    Adds data to the buffer, where data is an array of collections of types defined in CircularBuffer._data_types

    returns row of data of added data
"""
function add!(buffer::CircularBuffer, data)
    ret = buffer._current_row
    for (idx, dat) in enumerate(data)
        buffer._data_frame[buffer._current_row, idx] = copy(dat)
    end
    buffer._current_row += 1
    if buffer._current_row > buffer._capacity
        buffer._current_row = 1
        buffer._full = true
    end
    return ret
end

"""
    size(buffer)

    Returns the current amount of data in the circular buffer.
    If the full flag is true then we return the size of the whole data frame.

"""
function size(buffer::CircularBuffer)
    if (buffer._full)
        size(buffer._data_frame)
    else
        (buffer._current_row-1, length(buffer._data_types))
    end
end

"""
    capacity(buffer)

    returns the max number of elements the buffer can store.
"""
capacity(buffer::CircularBuffer) = buffer._capacity
getindex(buffer::CircularBuffer, idx) = getindex(buffer._data_frame, idx)
getrow(buffer::CircularBuffer, idx) = buffer._data_frame[idx,:]
# get(idx::Array{Int64, 1}) = d[idx]

# end

ext: jl


text:
using Documenter, RoundingEmulator

makedocs(
    sitename = "RoundingEmulator.jl",
    pages = [
        "Home" => "index.md",
        "Functions" => "functions.md",
        "References" => "references.md"
    ]
)

deploydocs(repo = "github.com/matsueushi/RoundingEmulator.jl")


text:
module FDMIC_Geodynamics
using Printf, Statistics
using LoopVectorization
import Plots
using LinearAlgebra, SparseArrays
import UnicodePlots
using Base.Threads
include("DataStructures.jl")
include("ThermalRoutines.jl")
include("MechanicsRoutines.jl")
include("MechanicsSolvers.jl")
include("MarkerRoutines.jl")
include("GridRoutines.jl")
include("SparseRoutines.jl")
include("DamageRoutines.jl")
end


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

module Meshing

using GeometryTypes
using Compat

include("marching_tetrahedra.jl")
include("marching_cubes.jl")

export marching_cubes

end # module


text:
module APIUtils

# helpers that facilitate working with C APIs
include("call.jl")
include("enum.jl")

end


text:
module FFTLogDocs

# Package is just for auto-docs generation

end # module


text:
import LinearMaps

struct ZeroMap{T} <: LinearMap{T}
    num_rows::Int
    num_cols::Int
end

Base.size(A::ZeroMap) = (A.num_rows, A.num_cols,)

function LinearAlgebra.mul!(y::AbstractVector, L::ZeroMap, x::AbstractVector, α::Number, β::Number)
    y .= β * y
    # LinearAlgebra.mul!(yI, AIJ, xJ, α, β)
end


text:
@testset "Read and write" begin
    import ONNX

    function serdeser(p::T) where T
        iob = PipeBuffer();
        ONNX.writeproto(iob, p)
        return ONNX.readproto(iob, T())
    end

    @testset "TensorProto" begin
        import ONNX: TensorProto, array

        @testset "Tensor type $T size $s" for T in (Int8, Int32, Int64, Float16, Float32, Float64), s in ((1,),
        (1, 2),
        (1, 2, 3),
        (1, 2, 3, 4),
        (1, 2, 3, 4, 5))
            exp = reshape(collect(T, 1:prod(s)), s...)
            @test TensorProto(exp) |> serdeser |> array == exp
        end
    end

    @testset "ValueInfo" begin
        import ONNX: ValueInfoProto

        @testset "ValueInfo shape $s" for s in ((), (missing,), (1, 2), (3, 4, missing))

            vip = ValueInfoProto("test", s)

            dvip = serdeser(vip)

            @test dvip.name == vip.name

            vsize = size(dvip)
            @test length(vsize) == length(s)
            if !isempty(s)
                @test vsize[findall(!ismissing, s)] == Tuple(skipmissing(s))
            end
        end
    end

    @testset "Attribute" begin
        import ONNX: AttributeProto, TensorProto, attribute, array

        @testset "Attribute type $(first(p))" for p in (
        :Int64 => 12,
        :Float32 => 23f0,
        :Float32s => Float32.(1:4),
        :Int64s => [1, 2, 3, 4],
        :String => "relu",
        :Strings => split("abcdefg", "")
        )
            @test AttributeProto(p) |> serdeser |> attribute == p
        end

        @testset "Attribute type Float64" begin
            # Float64 does not exist as attribute type in ONNX so above test will fail with rounging errors
            @test AttributeProto(:ff => 1.23) |> serdeser |> attribute |> last == 1.23f0
        end

        @testset "Attribute type TensorProto" begin
            # TensorProto has undef fields which mess up straigh comparison
            arr = collect(1:4)
            @test AttributeProto(:ff => TensorProto(arr)) |> serdeser |> attribute |> last |> array == arr
        end

        @testset "Attribute Dict" begin
            attrs = Dict(:int => 12, :str => "aaa", :floats => Float32.(2:5))
            @test pairs(attrs) |> collect .|> AttributeProto .|> serdeser |> Dict == attrs
        end
    end
end


text:
using LIPO
using Test

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


text:
import NNlib.relu
relu(x::C) where C <: Complex = real(x) > 0 ? x : C(0)

# ways to convert between gpu and cpu
import Adapt.adapt
function adapt(to, cft::ConvFFT{D,OT,F,A,V,PD,P,T,An}) where {D,OT,F,A,V,PD,P,T,An}
    cuw = adapt(to, cft.weight)
    cub = adapt(to, cft.bias)
    if cft.fftPlan isa Tuple
        cuf = map(thisPlan -> adapt(to, thisPlan), cft.fftPlan)
    else
        cuf = adapt(to, cft.fftPlan)
    end
    return ConvFFT{D,OT,F,typeof(cuw),typeof(cub),PD,typeof(cuf),T,An}(cft.σ, cuw, cub, cft.bc, cuf, cft.analytic)
end
export adapt

import CUDA.cu
function cu(cft::ConvFFT{D,OT,F,A,V,PD,P,T,An}) where {D,OT,F,A,V,PD,P,T,An}
    cuw = cu(cft.weight)
    cub = cu(cft.bias)
    if cft.fftPlan isa Tuple
        cuf = cu.(cft.fftPlan)
    else
        cuf = cu(cft.fftPlan)
    end
    return ConvFFT{D,OT,F,typeof(cuw),typeof(cub),PD,typeof(cuf),T,An}(cft.σ, cuw, cub, cft.bc, cuf, cft.analytic)
end

# TODO this is somewhat kludgy, not sure why cu was converting these back
function CUDA.cu(P::FFTW.rFFTWPlan)
    return plan_rfft(cu(zeros(real(eltype(P)), P.sz)), P.region)
end
CUDA.cu(P::CUFFT.rCuFFTPlan) = P

function CUDA.cu(P::FFTW.cFFTWPlan)
    return plan_fft(cu(zeros(eltype(P), P.sz)), P.region)
end
CUDA.cu(P::CUFFT.cCuFFTPlan) = P

Adapt.adapt(::Type{Array{T}}, P::FFTW.FFTWPlan{T}) where T = P
function Adapt.adapt(::Type{Array{T}}, P::FFTW.rFFTWPlan) where T
    plan_rfft(zeros(real(T), P.sz), P.region)
end
Adapt.adapt(::Type{<:Array}, P::AbstractFFTs.Plan) = P
Adapt.adapt(::Type{<:CuArray}, P::AbstractFFTs.Plan) = cu(P)

# come back from the gpu to the cpu
import Flux.cpu
function cpu(cft::ConvFFT{D,OT,F,A,V,PD,P,T,An}) where {D,OT,F,A,V,PD,P,T,An}
    w = cpu(cft.weight)
    b = cpu(cft.bias)
    f = cpu(cft.fftPlan)
    return ConvFFT{D,OT,F,typeof(w),typeof(b),PD,typeof(f),T,An}(cft.σ, w, b, cft.bc, f, cft.analytic)
end

function Flux.trainable(CFT::ConvFFT{A,B,C,D,E,F,G,true}) where {A,B,C,D,E,F,G}
    (CFT.weight, CFT.bias)
end
function Flux.trainable(CFT::ConvFFT{A,B,C,D,E,F,G,false}) where {A,B,C,D,E,F,G}
    tuple()
end

"""
jld doesn't like the pointers required by FFTW or CuArray for fft plans, so
this creates a version which can be saved via jld. The jank format I'm using is
a tuple listing the typeo of the input (eg CuArray{Float32,3}), the input size,
and fft region.

"""
function formatJLD(cft::ConvFFT{D,OT,F,A,V,PD,P,T,An}) where {D,OT,F,A,V,PD,P,T,An}
    newPlan = formatJLD(cft.fftPlan)
    newWeight = cft.weight |> cpu
    newBias = cft.bias |> cpu
    ConvFFT{D,OT,F,typeof(newWeight),typeof(newBias),PD,typeof(newPlan),T,An}(cft.σ, newWeight, newBias, cft.bc,
                                        newPlan, cft.analytic)
end

function formatJLD(pl::Tuple)
    return ([formatJLD(x) for x in pl]...,)
end
function formatJLD(pl::AbstractFFTs.Plan)
    ArrayType = (typeof(pl) <: CUFFT.CuFFTPlan) ? CuArray : Array
    return (ArrayType{eltype(pl),ndims(pl)}, size(pl), pl.region)
end
formatJLD(p) = p
"""
    weights = originalDomain()
given a ConvFFT, get the weights as represented in the time domain. optionally, apply a function σ to each pointwise afterward
"""
function originalDomain(cv::ConvFFT; σ=identity)
    listOfWeights = eachslice(cpu(cv.weight), dims=2)
    λorig(x, an) = originalDomain(x, cv.fftPlan, an)
    σ.(cat(map(λorig, listOfWeights, cv.analytic)..., dims=2))
end
function originalDomain(cv::ConvFFT{2}; σ=identity)
    σ.(irfft(cpu(cv.weight), size(cv.fftPlan, 1), (1, 2)))
end

originalDomain(wav, fftPlan, An::NonAnalyticMatching) = irfft(wav, 2 * (size(wav, 1) - 1))

function originalDomain(wav, fftPlan, An::AnalyticWavelet)
    isSourceOdd = mod(size(fftPlan, 1) + 1, 2)
    return ifft([wav; zeros(eltype(wav), size(wav, 1) - 1 - isSourceOdd)], 1)
end

function originalDomain(wav, fftPlan, An::Union{RealWaveletComplexSignal,RealWaveletRealSignal})
    isSourceOdd = mod(size(fftPlan, 1) + 1, 2)
    return ifft([wav; reverse(conj.(wav[2:end - isSourceOdd]))], 1)
end

function getBatchSize(c::C) where {C <: ConvFFT}
    if typeof(c.fftPlan) <: Tuple
        return c.fftPlan[2][end]
    else
        return c.fftPlan.sz[end]
    end
end

function getBatchSize(c::ConvFFT{D,OT,A,B,C,PD,P}) where {D,OT,A,B,C,PD,P <: Tuple}
    if typeof(c.fftPlan[1]) <: Tuple
        return c.fftPlan[1][2][end]
    else
        return c.fftPlan[1].sz[end]
    end
end

# is actually converting
function adapt(::Type{<:Array}, x::T) where T <: CUDA.CUFFT.CuFFTPlan
    transformSize = x.osz
    dataSize = x.sz
    if dataSize != transformSize
        # this is an rfft, since the dimension isn't preserved
        return plan_rfft(zeros(dataSize), x.region)
    else
        return plan_fft(zeros(dataSize), x.region)
    end
end
# reredundant
adapt(::Type{<:CuArray}, x::T) where T <: CUDA.CUFFT.CuFFTPlan = x

function fromRestrictLocs(restrict, z, i)
    if typeof(restrict[i]) <: Colon
        return 1:size(z, i)
    else
        return restrict[i]
    end
end
@recipe function f(x,y,cv::ConvFFT{2}; vis=1, dispReal=false,
                   apply=abs, restrict=(Colon(), Colon()))
    restrict = (restrict..., vis)
    w = cv.weight
    z = dispReal ?
        apply.(irfft(cpu(w), size(cv.fftPlan, 1),
                     (1, 2)))[restrict...] :
                         apply.(ifftshift(cpu(w), 2))[restrict...]
    (x, y, z)
end
@recipe function f(cv::ConvFFT{2}; vis=1, dispReal=false,
                   apply=abs, restrict=(Colon(), Colon()))
    restrict = (restrict..., vis)
    w = cv.weight
    z = dispReal ?
                apply.(ifftshift(irfft(cpu(w), size(cv.fftPlan, 1),
                                  (1, 2)), (1, 2)))[restrict...] :
                apply.(cpu(w))[restrict...]
    xSz = fromRestrictLocs(restrict, z, 2)
    x = dispReal ? xSz :
               range(xSz[1] - size(w, 2) / 2, stop=xSz[2] + size(w, 2) / 2,
                     length=length(xSz))
    y = fromRestrictLocs(restrict, z, 1)
    (x, y, z)
end

@recipe function f(x, cv::ConvFFT{1}; vis=1, dispReal=false,
                   apply=abs, restrict=(Colon(), vis))
    w = cv.weight
    if typeof(cv.fftPlan) <: Tuple
        origSize = size(cv.fftPlan[1], 1)
    else
        origSize = size(cv.fftPlan, 1)
    end

    z = dispReal ?
                apply.(irfft(cpu(w), origSize,
                             (1,)))[restrict...] :
                apply.(cpu(w))[restrict...]
    (x, z)
end
@recipe function f(cv::ConvFFT{1}; vis=1, dispReal=false,
                   apply=abs, restrict=(Colon(), vis))
    w = cv.weight
    if typeof(cv.fftPlan) <: Tuple
        origSize = size(cv.fftPlan[1], 1)
    else
        origSize = size(cv.fftPlan, 1)
    end

    z = dispReal ?
                apply.(irfft(cpu(w), origSize,
                             (1,)))[restrict...] :
                apply.(cpu(w))[restrict...]

    x = 1:size(z, 1)
    (x, z)
end

"""
    positive_glorot_uniform(dims...)
same idea as a glorot_uniform, but limited to strictly positive entries.
"""
positive_glorot_uniform(dims...) =
    (rand(Float32, dims...) .* sqrt(2.0f0 / sum(Flux.nfan(dims...))))

"""
    uniform_perturbed_gaussian(dims...)
If there are ``n`` total entries in the matrix, each entry is gaussian distributed with a mean of ``¹/ₙ`` and a standard deviation of ``\\frac{1}{10·n}``
"""
function uniform_perturbed_gaussian(dims...)
    netSize = prod(dims)
    A = 1 ./ netSize  .+ randn(dims) ./ netSize / 10;
    A = Float32.(A ./ norm(A))
end
"""
    iden_perturbed_gaussian(dims...)
an identity along the diagonal with Gaussian deviations of standard deviation ``\\frac 1 {100}`` everywhere
"""
function iden_perturbed_gaussian(dims...) # only works for the 2d case
    m = minimum(dims)
    netSize = prod(dims)
    if m == dims[2]
        return [I; zeros(Float32, dims[1] - m, dims[2])] .+ randn(Float32, dims) ./ 100
    else
        return [I zeros(Float32, dims[1], dims[2] - m)] .+ randn(Float32, dims) ./ 100
    end
end
# doubly stochastic matrix (Probably more work than it's worth)


text:
using Test
using Random

import RoadefRenaultBrazil
const RRB = RoadefRenaultBrazil

@testset "move_exchange.jl" begin
    type_fichier = "A"
    nom_fichier = "024_38_3_EP_ENP_RAF"
    instance = RRB.parser(nom_fichier, type_fichier)
    solution = RRB.greedy(instance)
    @testset "move_exchange!" begin
        solution_test = deepcopy(solution)
        RRB.move_exchange!(solution_test, 1, instance.nb_cars, instance)
        RRB.move_exchange!(solution_test, 1, instance.nb_cars, instance)
        # This shouldn't have changed anything
        @test solution.sequence == solution_test.sequence
        @test solution.M1 == solution_test.M1
        @test solution.M2 == solution_test.M2
        @test solution.M3 == solution_test.M3
    end;

    @testset "move_exchange! and update_matrices!" begin
        solution_test = deepcopy(solution)
        RRB.move_exchange!(solution_test, 1, instance.nb_cars, instance)
        solution_test_test = deepcopy(solution_test)
        RRB.update_matrices!(solution_test_test, instance)
        # This shouldn't have changed anything
        @test solution_test.sequence == solution_test_test.sequence
        @test solution_test.M1 == solution_test_test.M1
        @test solution_test.M2 == solution_test_test.M2
        @test solution_test.M3 == solution_test_test.M3

        solution_test = deepcopy(solution)
        pos = round(Int, instance.nb_cars / 2, RoundUp)
        RRB.move_exchange!(solution_test, pos-1, pos, instance)
        solution_test_test = deepcopy(solution_test)
        RRB.update_matrices!(solution_test_test, instance)
        @test solution_test.sequence == solution_test_test.sequence
        @test solution_test.M1 == solution_test_test.M1
        @test solution_test.M2 == solution_test_test.M2
        @test solution_test.M3 == solution_test_test.M3
    end;

    @testset "cost_move_exchange!" begin
        for i in 1:5
            solution_test = deepcopy(solution)
            car_pos_a = rand((instance.nb_late_prec_day+1):instance.nb_cars)
            car_pos_b = rand((instance.nb_late_prec_day+1):instance.nb_cars)
            vector_cost = RRB.cost_move_exchange(solution_test, car_pos_a, car_pos_b, instance, 3)
            costs = RRB.cost(solution, instance, 3)
            RRB.move_exchange!(solution_test, car_pos_a, car_pos_b, instance)
            costs_bis = RRB.cost(solution_test, instance, 3)
            @test vector_cost == (costs_bis .- costs)
            vector_cost_bis = RRB.cost_move_exchange(solution_test, car_pos_a, car_pos_b, instance, 3)
            @test vector_cost == -vector_cost_bis
        end
    end;
end;

@testset "move_insertion.jl" begin
    type_fichier = "A"
    nom_fichier = "024_38_3_EP_ENP_RAF"
    instance = RRB.parser(nom_fichier, type_fichier)
    solution = RRB.greedy(instance)
    @testset "move_insertion!" begin
        solution_test = deepcopy(solution)
        RRB.move_insertion!(solution_test, 1, 1, instance)
        # This shouldn't have changed anything
        @test solution.sequence == solution_test.sequence
        @test solution.M1 == solution_test.M1
        @test solution.M2 == solution_test.M2
        @test solution.M3 == solution_test.M3

        solution_test = deepcopy(solution)
        RRB.move_insertion!(solution_test, instance.nb_cars, instance.nb_cars, instance)
        # This shouldn't have changed anything
        @test solution.sequence == solution_test.sequence
        @test solution.M1 == solution_test.M1
        @test solution.M2 == solution_test.M2
        @test solution.M3 == solution_test.M3

        solution_test = deepcopy(solution)
        pos = round(Int, instance.nb_cars / 2, RoundUp)
        RRB.move_insertion!(solution_test, pos, pos, instance)
        # This shouldn't have changed anything
        @test solution.sequence == solution_test.sequence
        @test solution.M1 == solution_test.M1
        @test solution.M2 == solution_test.M2
        @test solution.M3 == solution_test.M3
    end;

    @testset "move_insertion! and update_matrices!" begin
        solution_test = deepcopy(solution)
        for _ in 1:5
            RRB.move_insertion!(solution_test, rand(1:instance.nb_cars), rand(1:instance.nb_cars), instance)
        end
        solution_test_test = deepcopy(solution_test)
        RRB.update_matrices!(solution_test_test, instance)
        @test solution_test.sequence == solution_test_test.sequence
        @test solution_test.M1 == solution_test_test.M1
        @test solution_test.M2 == solution_test_test.M2
        @test solution_test.M3 == solution_test_test.M3

        solution_test = deepcopy(solution)
        RRB.move_insertion!(solution_test, rand(1:instance.nb_cars), instance.nb_cars, instance)
        solution_test_test = deepcopy(solution_test)
        RRB.update_matrices!(solution_test_test, instance)
        @test solution_test.sequence == solution_test_test.sequence
        @test solution_test.M1 == solution_test_test.M1
        @test solution_test.M2 == solution_test_test.M2
        @test solution_test.M3 == solution_test_test.M3

        solution_test = deepcopy(solution)
        RRB.move_insertion!(solution_test, rand(1:instance.nb_cars), 1, instance)
        solution_test_test = deepcopy(solution_test)
        RRB.update_matrices!(solution_test_test, instance)
        @test solution_test.sequence == solution_test_test.sequence
        @test solution_test.M1 == solution_test_test.M1
        @test solution_test.M2 == solution_test_test.M2
        @test solution_test.M3 == solution_test_test.M3
    end;

    @testset "cost_move_insertion!" begin
        for i in 1:3
            position = rand(1:instance.nb_cars)
            vector_cost = RRB.cost_move_insertion(solution, position, instance, 3)
            costs = RRB.cost(solution, instance, 3)
            for j in 1:3
                solution_test = deepcopy(solution)
                insertion_position = rand((instance.nb_late_prec_day+1):instance.nb_cars)
                RRB.move_insertion!(solution_test, position, insertion_position, instance)
                costs_bis = RRB.cost(solution_test, instance, 3)
                @test vector_cost[insertion_position,:] == (costs_bis .- costs)
            end
        end
    end;
end;

@testset "solution.jl" begin
    type_fichier = "A"
    nom_fichier = "024_38_3_EP_ENP_RAF"
    instance = RRB.parser(nom_fichier, type_fichier)
    solution = RRB.greedy(instance)
    @testset "update_matrices!" begin
        solution_test = deepcopy(solution)
        RRB.update_matrices!(solution_test, instance)
        @test solution.sequence == solution_test.sequence
        @test solution.M1 == solution_test.M1
        @test solution.M2 == solution_test.M2
        @test solution.M3 == solution_test.M3
    end;
end;


text:
# This file provides most of the implementation of multi-robot target *tracking*
# problems.
#
# The tracking problems involve estimation of target positions and optimization
# via maximizing mutual information.

using LinearAlgebra

export MultiRobotTargetTrackingConfigs, MultiRobotTargetTrackingProblem

# Automatically switch to sparse filters for large numbers of robots
const num_robots_sparse_filtering_threshold = 15
const sparsity_threshold=1e-3

# Stores configuration variables for multi-robot target tracking
struct MultiRobotTargetTrackingConfigs
  grid::Grid
  sensor::RangingSensor
  horizon::Int64

  # Configuration for the MCTS solver
  solver_iterations::Int64
  solver_information_samples::Int64

  # We will generally use a larger number of samples when querying actual
  # solution values
  objective_information_samples::Int64

  robot_target_range_limit::Float64

  function MultiRobotTargetTrackingConfigs(;
                                           grid,
                                           horizon=default_horizon,
                                           sensor=RangingSensor(),
                                           solver_iterations =
                                           default_num_iterations[horizon],
                                           solver_information_samples =
                                             default_solver_information_samples,
                                           objective_information_samples =
                                             default_num_information_samples,
                                           robot_target_range_limit = Inf
                                          )

    new(grid, sensor, horizon, solver_iterations, solver_information_samples,
        objective_information_samples, robot_target_range_limit)
  end
end

# Constructor that can infer parameters based on the number of robots
#
# Note that inferred parameters can still be overriden, but we probably don't
# want to do so
function MultiRobotTargetTrackingConfigs(num_robots;
                                         kwargs...)
  grid=Grid(num_robots=num_robots)
  MultiRobotTargetTrackingConfigs(;grid=grid, kwargs...)
end

# Copy constructor with overrides
function
  MultiRobotTargetTrackingConfigs(configs::MultiRobotTargetTrackingConfigs;
                                  kwargs...)

  fields = fieldnames(MultiRobotTargetTrackingConfigs)
  old_settings = Dict(field => getfield(configs, field) for field in fields)
  settings = merge(old_settings, kwargs)

  MultiRobotTargetTrackingConfigs(; settings...)
end

# Solution elements consist of the robot index and the associated trajectory
# We construct solutions as such because the output may not have the same
# ordering as the robots
#
# Warning: this "problem" object may become invalid if any of the underlying
# objects change and may copy some but not of the inputs.
struct MultiRobotTargetTrackingProblem{F<:AbstractFilter} <: AbstractMultiRobotProblem
  # Target tracking problems are defined by vectors of robot states
  partition_matroid::Vector{State}

  target_filters::Vector{F}
  filter_means::Vector{Tuple{Float64, Float64}}

  configs::MultiRobotTargetTrackingConfigs

  function MultiRobotTargetTrackingProblem(robot_states::Vector{State},
                 target_filters::Vector{F},
                 configs::MultiRobotTargetTrackingConfigs) where F <: AbstractFilter

    if length(robot_states) > num_robots_sparse_filtering_threshold
      sparse_filters = map(x->SparseFilter(x, threshold=sparsity_threshold),
                           target_filters)

      filter_means = compute_filter_means(sparse_filters)

      new{SparseFilter{Int64}}(robot_states, sparse_filters, filter_means,
                               configs)
    else
      filter_means = compute_filter_means(target_filters)

      new{F}(robot_states, target_filters, filter_means, configs)
    end
  end
end

# Construct a target tracking problem with configs. I may or may not need this
function MultiRobotTargetTrackingProblem(robot_states::Vector{State},
                                         target_filters::Vector{<:AbstractFilter};
                                         kwargs...)
  configs = MultiRobotTargetTrackingConfigs(;kwargs...)
  MultiRobotTargetTrackingProblem(robot_states, target_filters, configs)
end

function objective(p::MultiRobotTargetTrackingProblem, X)
  configs = p.configs

  trajectories = map(last, X)

  sum(p.target_filters) do filter
    finite_horizon_information(configs.grid, filter, configs.sensor,
                               trajectories;
                               num_samples=configs.objective_information_samples
                              ).reward
  end
end

# Returns distance from robot state to target mean
# (due to convexity and Jensen's inequality, this is a lower bound on the range)
function target_mean_distance(p::MultiRobotTargetTrackingProblem,
                     robot_state::State, target_index::Integer)
  norm(robot_state .- p.filter_means[target_index])
end
function target_mean_distance(p::MultiRobotTargetTrackingProblem,
                     robot_index::Integer, target_index::Integer)
  target_mean_distance(p, p.partition_matroid[robot_index], target_index)
end

function filter_targets_in_range(p::MultiRobotTargetTrackingProblem,
                                 robot_index::Integer;
                                 range_limit=p.configs.robot_target_range_limit
                                )

  # filter by range, producing tuples
  in_bounds = filter(1:length(p.filter_means)) do target_index
    target_mean_distance(p, robot_index, target_index) < range_limit
  end

  map(x->p.target_filters[x[1]], in_bounds)
end

function solve_block(p::MultiRobotTargetTrackingProblem, block::Integer,
                     selections::Vector)
  configs = p.configs

  trajectories = map(last, selections)

  # Select filters in range
  local targets_in_range
  if p.configs.robot_target_range_limit == Inf
    targets_in_range = p.target_filters
  else
    targets_in_range = filter_targets_in_range(p, block)
  end

  problem = SingleRobotTargetTrackingProblem(configs.grid, configs.sensor,
                                             configs.horizon, targets_in_range,
                                             prior_trajectories=trajectories,
                                             num_information_samples=
                                             configs.solver_information_samples
                                            )

  state = get_state(p, block)

  solution = solve_single_robot(problem, state,
                                n_iterations=configs.solver_iterations)

  (block, solution.trajectory)
end

function compute_filter_means(target_filters::Vector{<:AbstractFilter})
  map(target_filters) do x
    Tuple{Float64, Float64}(weighted_average(x))
  end
end

max_stars_repo_name: mcorah/MultiAgentSensing

max_issues_repo_name: mcorah/MultiAgentSensing

max_forks_repo_name: mcorah/MultiAgentSensing


text:
"""
    visualize_mesh(mmesh::Mesh3; filename::Union{Missing, String} = missing)

Visualizes the unstructured mesh `mmesh`. When `filename` is provided, a screenshot is saved to `filename`
"""
function visualize_mesh(mmesh::Mesh3;
        filename::Union{Missing, String} = missing,
        scalars::Union{Nothing, Array{Float64, 1}} = nothing)
    points = mmesh.nodes
    cells = Int32.(mmesh.elems) .- 1
    cell_dict = Dict(10=>cells)
    mesh = pv.UnstructuredGrid(cell_dict, points)
    p = pv.Plotter()
    p.add_mesh(mesh, show_edges=true, color="white", scalars = scalars)
    p.add_mesh(pv.PolyData(mesh.points), color="red",
        point_size=10, render_points_as_spheres=true)
    p.show_axes()
    if ismissing(filename)
        p.show()
    else
        p.show(screenshot=filename)
    end
    p
end

"""
    visualize_scalar_on_fem_points(u::Array{Float64,1}, mmesh::Mesh3; filename::Union{Missing, String} = missing)

Visualizes the unstructured mesh `mmesh` with nodes colored with values `u`. When `filename` is provided, a screenshot is saved to `filename`
"""
function visualize_scalar_on_fem_points(u::Array{Float64,1}, mmesh::Mesh3; filename::Union{Missing, String} = missing)
    @assert length(u) == mmesh.nnode
    visualize_mesh(mmesh; filename = filename, scalars = u)
end

"""
    visualize_scalar_on_fvm_points(u::Array{Float64,1}, mmesh::Mesh3; filename::Union{Missing, String} = missing)

Visualizes the unstructured mesh `mmesh` with elements colored with values `u`. When `filename` is provided, a screenshot is saved to `filename`
"""
function visualize_scalar_on_fvm_points(u::Array{Float64,1}, mmesh::Mesh3; filename::Union{Missing, String} = missing)
    @assert length(u) == mmesh.nelem
    visualize_mesh(mmesh; filename = filename, scalars = u)
end

"""
    visualize_scalar_on_fvm_points(u::Array{Float64,1}, mmesh::Mesh3; filename::Union{Missing, String} = missing)

Visualizes the unstructured mesh `mmesh` with elements colored with values `u`. When `filename` is provided, a screenshot is saved to `filename`
"""
function visualize_scalar_on_gauss_points(u::Array{Float64,1}, mmesh::Mesh3; filename::Union{Missing, String} = missing)
    @assert length(u) == get_ngauss(mmesh)
    ngauss_per_element = length(u)÷mmesh.nelem
    z = zeros(mmesh.nelem)
    for i = 1:ngauss_per_element
        z += u[i:ngauss_per_element:end]
    end
    z/=ngauss_per_element
    visualize_scalar_on_fvm_points(z, mmesh; filename = filename)
end


text:
import Base.Broadcast: BroadcastStyle
using Base.Broadcast: AbstractArrayStyle, Broadcasted, DefaultArrayStyle

# Add a new BroadcastStyle for StaticArrays, derived from AbstractArrayStyle
# A constructor that changes the style parameter N (array dimension) is also required
struct HybridArrayStyle{N} <: AbstractArrayStyle{N} end
HybridArrayStyle{M}(::Val{N}) where {M,N} = HybridArrayStyle{N}()
BroadcastStyle(::Type{<:HybridArray{<:Tuple, <:Any, N}}) where {N} = HybridArrayStyle{N}()
# Precedence rules
BroadcastStyle(::HybridArray{M}, ::DefaultArrayStyle{N}) where {M,N} =
    DefaultArrayStyle(Val(max(M, N)))
BroadcastStyle(::HybridArray{M}, ::DefaultArrayStyle{0}) where {M} =
    HybridArrayStyle{M}()

BroadcastStyle(::HybridArray{M}, ::StaticArrays.StaticArrayStyle{N}) where {M,N} =
    StaticArrays.Hybrid(Val(max(M, N)))
BroadcastStyle(::HybridArray{M}, ::StaticArrays.StaticArrayStyle{0}) where {M} =
    HybridArrayStyle{M}()

# copy overload
@inline function Base.copy(B::Broadcasted{HybridArrayStyle{M}}) where M
    flat = Broadcast.flatten(B); as = flat.args; f = flat.f
    argsizes = StaticArrays.broadcast_sizes(as...)
    destsize = StaticArrays.combine_sizes(argsizes)
    if Length(destsize) === Length{StaticArrays.Dynamic()}()
        # destination dimension cannot be determined statically; fall back to generic broadcast
        return HybridArray{StaticArrays.size_tuple(destsize)}(copy(convert(Broadcasted{DefaultArrayStyle{M}}, B)))
    end
    _broadcast(f, destsize, argsizes, as...)
end
# copyto! overloads
@inline Base.copyto!(dest, B::Broadcasted{<:HybridArrayStyle}) = _copyto!(dest, B)
@inline Base.copyto!(dest::AbstractArray, B::Broadcasted{<:HybridArrayStyle}) = _copyto!(dest, B)
@inline function _copyto!(dest, B::Broadcasted{HybridArrayStyle{M}}) where M
    flat = Broadcast.flatten(B); as = flat.args; f = flat.f
    argsizes = StaticArrays.broadcast_sizes(as...)
    destsize = StaticArrays.combine_sizes((Size(dest), argsizes...))
    if Length(destsize) === Length{StaticArrays.Dynamic()}()
        # destination dimension cannot be determined statically; fall back to generic broadcast!
        return copyto!(dest, convert(Broadcasted{DefaultArrayStyle{M}}, B))
    end
    StaticArrays._broadcast!(f, destsize, dest, argsizes, as...)
end

@generated function _broadcast(f, ::Size{newsize}, s::Tuple{Vararg{Size}}, a...) where newsize
    first_staticarray = 0
    for i = 1:length(a)
        if a[i] <: StaticArray
            first_staticarray = a[i]
            break
        end
    end
    if first_staticarray == 0
        for i = 1:length(a)
            if a[i] <: HybridArray
                first_staticarray = a[i]
                break
            end
        end
    end

    exprs = Array{Expr}(undef, newsize)
    more = prod(newsize) > 0
    current_ind = ones(Int, length(newsize))
    sizes = [sz.parameters[1] for sz ∈ s.parameters]

    make_expr(i) = begin
        if !(a[i] <: AbstractArray)
            return :(StaticArrays.scalar_getindex(a[$i]))
        elseif hasdynamic(Tuple{sizes[i]...})
            return :(a[$i][$(current_ind...)])
        else
            :(a[$i][$(StaticArrays.broadcasted_index(sizes[i], current_ind))])
        end
    end

    while more
        exprs_vals = [make_expr(i) for i = 1:length(sizes)]
        exprs[current_ind...] = :(f($(exprs_vals...)))

        # increment current_ind (maybe use CartesianIndices?)
        current_ind[1] += 1
        for i ∈ 1:length(newsize)
            if current_ind[i] > newsize[i]
                if i == length(newsize)
                    more = false
                    break
                else
                    current_ind[i] = 1
                    current_ind[i+1] += 1
                end
            else
                break
            end
        end
    end

    return quote
        Base.@_inline_meta
        @inbounds elements = tuple($(exprs...))
        @inbounds return similar_type($first_staticarray, eltype(elements), Size(newsize))(elements)
    end
end