Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "ChainRulesCore"
uuid = "d360d2e6-b24c-11e9-a2a3-2a2ae2dbcce4"
version = "1.0.1"
version = "1.0.2"

[deps]
Compat = "34da2185-b29b-5c13-b0c7-acf172513d20"
Expand Down
31 changes: 22 additions & 9 deletions src/projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -103,9 +103,6 @@ julia> unthunk(pd(th))
1.0 ⋅ ⋅
⋅ 5.0 ⋅
⋅ ⋅ 9.0

julia> ProjectTo([1 2; 3 4]') # no special structure, integers are promoted to float(x)
ProjectTo{AbstractArray}(element = ProjectTo{Float64}(), axes = (Base.OneTo(2), Base.OneTo(2)))
```
"""
ProjectTo(::Any) # just to attach docstring
Expand Down Expand Up @@ -135,13 +132,29 @@ ProjectTo(::Real) = ProjectTo{Real}()
ProjectTo(::Complex) = ProjectTo{Complex}()
ProjectTo(::Number) = ProjectTo{Number}()
for T in (Float16, Float32, Float64, ComplexF16, ComplexF32, ComplexF64)
# Preserve low-precision floats as accidental promotion is a common perforance bug
# Preserve low-precision floats as accidental promotion is a common performance bug
@eval ProjectTo(::$T) = ProjectTo{$T}()
end
ProjectTo(x::Integer) = ProjectTo(float(x))
ProjectTo(x::Complex{<:Integer}) = ProjectTo(float(x))
(::ProjectTo{T})(dx::Number) where {T<:Number} = convert(T, dx)
(::ProjectTo{T})(dx::Number) where {T<:Real} = convert(T, real(dx))

# Preserve low-precision floats as accidental promotion is a common performance bug
(::ProjectTo{T})(dx::AbstractFloat) where T<:AbstractFloat = convert(T, dx)
# In this case we can just `convert` as we know we are dealing with boring `AbstractFloat`s
(::ProjectTo{T})(dx::Complex{<:AbstractFloat}) where {T<:Complex{<:AbstractFloat}} = convert(T, dx)
(::ProjectTo{T})(dx::AbstractFloat) where {T<:Complex{<:AbstractFloat}} = convert(T, dx)


# We asked for a number/real and they gave use one. We did ask for a particular concrete
# type, but that is just for the preserving low precision floats, which is handled above.
# Any Number/Real actually occupies the same subspace, so we can trust them.
# In particular, this makes weirder Real subtypes that are not simply the values like
# ForwardDiff.Dual and Symbolics.Sym work, because we stay out of their way.
(::ProjectTo{<:Number})(dx::Number) = dx
# If you remove the above julia sometimes can't find the (::ProjectTo{T})(::T) for complex T

(project::ProjectTo{<:Real})(dx::Complex) = project(real(dx))
(::ProjectTo{<:Complex})(dx::Real) = Complex(dx) # Complex is overloaded it insert zero imag
Comment thread
oxinabox marked this conversation as resolved.



# Arrays
# If we don't have a more specialized `ProjectTo` rule, we just assume that there is
Expand Down Expand Up @@ -341,7 +354,7 @@ ProjectTo(x::SymTridiagonal{T}) where {T<:Number} = generic_projector(x)
function (project::ProjectTo{SymTridiagonal})(dx::AbstractMatrix)
dv = project.dv(diag(dx))
ev = project.ev((diag(dx, 1) .+ diag(dx, -1)) ./ 2)
return SymTridiagonal(dv, ev)
return SymTridiagonal(promote(dv, ev)...)
end
(project::ProjectTo{SymTridiagonal})(dx::SymTridiagonal) = generic_projection(project, dx)

Expand Down
56 changes: 40 additions & 16 deletions test/projection.jl
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@ using ChainRulesCore, Test
using LinearAlgebra, SparseArrays
using OffsetArrays, BenchmarkTools

# Like ForwardDiff.jl's Dual
struct Dual{T<:Real} <: Real
value::T
partial::T
end
Base.real(x::Dual) = x
Base.float(x::Dual) = Dual(float(x.value), float(x.partial))
Base.zero(x::Dual) = Dual(zero(x.value), zero(x.partial))

@testset "projection" begin

#####
Expand All @@ -12,22 +21,37 @@ using OffsetArrays, BenchmarkTools
# real / complex
@test ProjectTo(1.0)(2.0 + 3im) === 2.0
@test ProjectTo(1.0 + 2.0im)(3.0) === 3.0 + 0.0im
@test ProjectTo(2.0+3.0im)(1+1im) === 1 + 1im
@test ProjectTo(2.0)(1+1im) === 1


# storage
@test ProjectTo(1)(pi) === Float64(pi)
@test ProjectTo(1 + im)(pi) === ComplexF64(pi)
@test ProjectTo(1)(pi) === pi
@test ProjectTo(1 + im)(pi) === Complex(pi)
@test ProjectTo(1//2)(3//4) === 3//4
@test ProjectTo(1.0f0)(1 / 2) === 0.5f0
@test ProjectTo(1.0f0 + 2im)(3) === 3.0f0 + 0im
@test ProjectTo(1.0f0 + 2im)(3) === 3 + 0im
@test ProjectTo(1.0f0 + 2im)(3.0) === 3f0 + 0f0im
@test ProjectTo(big(1.0))(2) === 2
@test ProjectTo(1.0)(2) === 2
end

@testset "Dual" begin # some weird Real subtype that we should basically leave alone
@test ProjectTo(1.0)(Dual(1.0, 2.0)) isa Dual
@test ProjectTo(1.0)(Dual(1, 2)) isa Dual
@test ProjectTo(1.0 + 1im)(Dual(1.0, 2.0)) isa Complex{<:Dual}
@test ProjectTo(1.0 + 1im)(
Complex(Dual(1.0, 2.0), Dual(1.0, 2.0))
) isa Complex{<:Dual}
@test ProjectTo(1.0)(Complex(Dual(1.0, 2.0), Dual(1.0, 2.0))) isa Dual
end

@testset "Base: arrays of numbers" begin
pvec3 = ProjectTo([1, 2, 3])
@test pvec3(1.0:3.0) === 1.0:3.0
@test pvec3(1:3) == 1.0:3.0 # would prefer ===, map(Float64, dx) would do that, not important
@test pvec3(1:3) == 1:3
@test pvec3([1, 2, 3 + 4im]) == 1:3
@test eltype(pvec3([1, 2, 3.0f0])) === Float64
@test eltype(pvec3([1, 2, 3.0f0])) === Float32

# reshape
@test pvec3(reshape([1, 2, 3], 3, 1)) isa Vector
Expand All @@ -36,7 +60,7 @@ using OffsetArrays, BenchmarkTools

pmat = ProjectTo(rand(2, 2) .+ im)
@test pmat([1 2; 3 4.0+5im]') isa Adjoint # pass-through
@test pmat([1 2; 3 4]') isa Matrix{ComplexF64} # broadcast type change
@test pmat([1 2; 3 4]') isa Matrix{<:Complex} # broadcast type change

pmat2 = ProjectTo(rand(2, 2)')
@test pmat2([1 2; 3 4.0+5im]) isa Matrix # adjoint matrices are not re-created
Expand Down Expand Up @@ -64,7 +88,7 @@ using OffsetArrays, BenchmarkTools
@test_throws MethodError ProjectTo(Any[:x, :y])

@test ProjectTo(Any[1, 2])(1:2) == [1.0, 2.0] # projects each number.
@test Tuple(ProjectTo(Any[1, 2 + 3im])(1:2)) === (1.0, 2.0 + 0.0im)
@test Tuple(ProjectTo(Any[1, 2 + 3im])(1:2)) === (1, 2 + 0im)
@test ProjectTo(Any[true, false]) isa ProjectTo{NoTangent}

# empty arrays
Expand All @@ -85,14 +109,14 @@ using OffsetArrays, BenchmarkTools
@test pzed isa ProjectTo{AbstractArray}

pref = ProjectTo(Ref(2.0))
@test pref(Ref(3 + im))[] === 3.0
@test pref(4)[] === 4.0 # also re-wraps scalars
@test pref(Ref(3 + im))[] === 3
@test pref(4)[] === 4 # also re-wraps scalars
@test pref(Ref{Any}(5.0)) isa Base.RefValue{Float64}
pref2 = ProjectTo(Ref{Any}(6 + 7im))
@test pref2(Ref(8))[] === 8.0 + 0.0im
@test pref2(Ref(8))[] === 8 + 0im

prefvec = ProjectTo(Ref([1, 2, 3 + 4im])) # recurses into contents
@test prefvec(Ref(1:3)) isa Base.RefValue{Vector{ComplexF64}}
@test prefvec(Ref(1:3)) isa Base.RefValue{<:Vector{<:Complex}}
@test_throws DimensionMismatch prefvec(Ref{Any}(1:5))
end

Expand All @@ -103,7 +127,7 @@ using OffsetArrays, BenchmarkTools
@testset "LinearAlgebra: $adj vectors" for adj in [transpose, adjoint]
# adjoint vectors
padj = ProjectTo(adj([1, 2, 3]))
adjT = typeof(adj([1, 2, 3.0]))
adjT = adj == transpose ? Transpose : Adjoint
@test padj(transpose(1:3)) isa adjT
@test padj([4 5 6 + 7im]) isa adjT
@test padj([4.0 5.0 6.0]) isa adjT
Expand Down Expand Up @@ -159,15 +183,15 @@ using OffsetArrays, BenchmarkTools
@test ProjectTo(UpperTriangular(randn(3, 3) .> 0))(randn(3, 3)) == NoTangent()

# an experiment with allowing subspaces which aren't subtypes
@test psymm(Diagonal([1, 2, 3])) isa Diagonal{Float64}
@test pupp(Diagonal([1, 2, 3 + 4im])) isa Diagonal{Float64}
@test psymm(Diagonal([1, 2, 3])) isa Diagonal{Int}
@test pupp(Diagonal([1, 2, 3 + 4im])) isa Diagonal{Int}
end

@testset "LinearAlgebra: sparse structured matrices" begin
pdiag = ProjectTo(Diagonal(1:3))
@test pdiag(reshape(1:9, 3, 3)) == Diagonal([1, 5, 9])
@test pdiag(pdiag(reshape(1:9, 3, 3))) == pdiag(reshape(1:9, 3, 3))
@test pdiag(rand(ComplexF32, 3, 3)) isa Diagonal{Float64}
@test pdiag(rand(ComplexF32, 3, 3)) isa Diagonal{Float32}
@test pdiag(Diagonal(1.0:3.0)) === Diagonal(1.0:3.0)
@test ProjectTo(Diagonal(randn(3) .> 0))(randn(3, 3)) == NoTangent()
@test ProjectTo(Diagonal(randn(3) .> 0))(Diagonal(rand(3))) == NoTangent()
Expand Down Expand Up @@ -272,7 +296,7 @@ using OffsetArrays, BenchmarkTools
th = @thunk 1 + 2 + 3
pth = ProjectTo(4 + 5im)(th)
@test pth isa Thunk
@test unthunk(pth) === 6.0 + 0.0im
@test unthunk(pth) === 6 + 0im
end

@testset "display" begin
Expand Down