diff --git a/test/DerivativeTest.jl b/test/DerivativeTest.jl index 4de1a6de..206a2d68 100644 --- a/test/DerivativeTest.jl +++ b/test/DerivativeTest.jl @@ -8,6 +8,7 @@ using Random using ForwardDiff using DiffTests +include(joinpath(dirname(@__FILE__), "Furlongs.jl")) include(joinpath(dirname(@__FILE__), "utils.jl")) Random.seed!(1) @@ -113,4 +114,16 @@ end @test ForwardDiff.derivative(x -> (1+im)*x, 0) == (1+im) end +@testset "non-standard numbers" begin + Base.exp(f::Furlongs.Furlong) = exp(f.val) + Base.cos(f::Furlongs.Furlong) = cos(f.val) + Base.sin(f::Furlongs.Furlong) = sin(f.val) + + furlong = Furlongs.Furlong{2}(1.0) + + f(x) = exp(x) + 4*sin(x)*oneunit(x) + + @test ForwardDiff.derivative(f, furlong) == exp(furlong) + 4*cos(furlong) +end + end # module diff --git a/test/Furlongs.jl b/test/Furlongs.jl new file mode 100644 index 00000000..3fef6683 --- /dev/null +++ b/test/Furlongs.jl @@ -0,0 +1,110 @@ +module Furlongs + +export Furlong + +const TNumber = Real # ForwardDiff only supports Real numbers + +# Here we implement a minimal dimensionful type Furlong, which is used +# to test dimensional correctness of various functions in Base. + +# represents a quantity in furlongs^p +struct Furlong{p,T<:TNumber} <: TNumber + val::T + Furlong{p,T}(v::TNumber) where {p,T} = new(v) +end +Furlong(x::T) where {T<:TNumber} = Furlong{1,T}(x) +Furlong(x::Furlong) = x +(::Type{T})(x::Furlong{0}) where {T<:TNumber} = T(x.val)::T +(::Type{T})(x::Furlong{0}) where {T<:Furlong{0}} = T(x.val)::T +(::Type{T})(x::Furlong{0}) where {T<:Furlong} = typeassert(x, T) +Furlong{p}(v::TNumber) where {p} = Furlong{p,typeof(v)}(v) +Furlong{p}(x::Furlong{q}) where {p,q} = (typeassert(x, Furlong{p}); Furlong{p,typeof(x.val)}(x.val)) +Furlong{p,T}(x::Furlong{q}) where {T,p,q} = (typeassert(x, Furlong{p}); Furlong{p,T}(T(x.val))) + +Base.promote_rule(::Type{Furlong{p,T}}, ::Type{Furlong{p,S}}) where {p,T,S} = + Furlong{p,promote_type(T,S)} +Base.promote_rule(::Type{Furlong{0,T}}, ::Type{S}) where {T,S<:Union{Real,Complex}} = + Furlong{0,promote_type(T,S)} +# only Furlong{0} forms a ring and isa TNumber +Base.convert(::Type{T}, y::TNumber) where {T<:Furlong{0}} = T(y)::T +Base.convert(::Type{Furlong}, y::TNumber) = Furlong{0}(y) +Base.convert(::Type{Furlong{<:Any,T}}, y::TNumber) where {T<:TNumber} = Furlong{0,T}(y) +Base.convert(::Type{T}, y::TNumber) where {T<:Furlong} = typeassert(y, T) # throws, since cannot convert a Furlong{0} to a Furlong{p} +# other Furlong{p} form a group +Base.convert(::Type{T}, y::Furlong) where {T<:Furlong{0}} = T(y)::T +Base.convert(::Type{Furlong}, y::Furlong) = y +Base.convert(::Type{Furlong{<:Any,T}}, y::Furlong{p}) where {p,T<:TNumber} = Furlong{p,T}(y) +Base.convert(::Type{T}, y::Furlong) where {T<:Furlong} = T(y)::T + +Base.one(::Furlong{p,T}) where {p,T} = one(T) +Base.one(::Type{Furlong{p,T}}) where {p,T} = one(T) +Base.oneunit(::Furlong{p,T}) where {p,T} = Furlong{p,T}(one(T)) +Base.oneunit(::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(one(T)) +Base.zero(::Furlong{p,T}) where {p,T} = Furlong{p,T}(zero(T)) +Base.zero(::Type{Furlong{p,T}}) where {p,T} = Furlong{p,T}(zero(T)) +Base.iszero(x::Furlong) = iszero(x.val) +Base.float(x::Furlong{p}) where {p} = Furlong{p}(float(x.val)) +Base.eps(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(eps(T)) +Base.eps(::Furlong{p,T}) where {p,T<:AbstractFloat} = eps(Furlong{p,T}) +Base.floatmin(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(floatmin(T)) +Base.floatmin(::Furlong{p,T}) where {p,T<:AbstractFloat} = floatmin(Furlong{p,T}) +Base.floatmax(::Type{Furlong{p,T}}) where {p,T<:AbstractFloat} = Furlong{p}(floatmax(T)) +Base.floatmax(::Furlong{p,T}) where {p,T<:AbstractFloat} = floatmax(Furlong{p,T}) +Base.conj(x::Furlong{p,T}) where {p,T} = Furlong{p,T}(conj(x.val)) + +# convert Furlong exponent p to a canonical form +canonical_p(p) = isinteger(p) ? Int(p) : Rational{Int}(p) + +Base.abs(x::Furlong{p}) where {p} = Furlong{p}(abs(x.val)) +Base.abs2(x::Furlong{p}) where {p} = Furlong{canonical_p(2p)}(abs2(x.val)) +Base.inv(x::Furlong{p}) where {p} = Furlong{canonical_p(-p)}(inv(x.val)) + +for f in (:isfinite, :isnan, :isreal, :isinf) + @eval Base.$f(x::Furlong) = $f(x.val) +end +for f in (:real,:imag,:complex,:+,:-) + @eval Base.$f(x::Furlong{p}) where {p} = Furlong{p}($f(x.val)) +end + +import Base: +, -, ==, !=, <, <=, isless, isequal, *, /, //, div, rem, mod, ^ +for op in (:+, :-) + @eval function $op(x::Furlong{p}, y::Furlong{p}) where {p} + v = $op(x.val, y.val) + Furlong{p}(v) + end +end +for op in (:(==), :(!=), :<, :<=, :isless, :isequal) + @eval $op(x::Furlong{p}, y::Furlong{p}) where {p} = $op(x.val, y.val)::Bool +end +for (f,op) in ((:_plus,:+),(:_minus,:-),(:_times,:*),(:_div,://)) + @eval function $f(v::T, ::Furlong{p}, ::Union{Furlong{q},Val{q}}) where {T,p,q} + s = $op(p, q) + Furlong{canonical_p(s),T}(v) + end +end +for (op,eop) in ((:*, :_plus), (:/, :_minus), (://, :_minus), (:div, :_minus)) + @eval begin + $op(x::Furlong{p}, y::Furlong{q}) where {p,q} = + $eop($op(x.val, y.val),x,y) + $op(x::Furlong{p}, y::S) where {p,S<:TNumber} = $op(x,Furlong{0,S}(y)) + $op(x::S, y::Furlong{p}) where {p,S<:TNumber} = $op(Furlong{0,S}(x),y) + end +end +# to fix an ambiguity +//(x::Furlong, y::Complex) = x // Furlong{0,typeof(y)}(y) +for op in (:rem, :mod) + @eval begin + $op(x::Furlong{p}, y::Furlong) where {p} = Furlong{p}($op(x.val, y.val)) + $op(x::Furlong{p}, y::TNumber) where {p} = Furlong{p}($op(x.val, y)) + end +end +Base.sqrt(x::Furlong) = _div(sqrt(x.val), x, Val(2)) +Base.muladd(x::Furlong, y::Furlong, z::Furlong) = x*y + z +Base.muladd(x::Furlong, y::TNumber, z::TNumber) = x*y + z +Base.muladd(x::Furlong, y::Furlong, z::TNumber) = x*y + z +Base.muladd(x::TNumber, y::Furlong, z::TNumber) = x*y + z +Base.muladd(x::TNumber, y::TNumber, z::Furlong) = x*y + z +Base.muladd(x::TNumber, y::Furlong, z::Furlong) = x*y + z +Base.muladd(x::Furlong, y::TNumber, z::Furlong) = x*y + z + +end