JuliaDiff / JuliaDiff/ChainRulesCore.jl

writing rules for <:AbstractArray

Open
#582 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

documentation ProjectTo Structural Tangent
Dominant language
Julia
Stars
267
Forks
66
PR merge metrics
No merged PRs in 30d

Description

How should one write "proper" rules for methods that work for generic AbstractArray objects?

As an example, take this function:

function _setindex(a::AbstractArray,v,args...)
    b::typeof(a) = copy(a);
    b[args...] = v
    b
end

This method seems pretty tame, and I think should be generically correct for any abstractarray object. The backward rule looks simple:

function ChainRulesCore.rrule(::typeof(_setindex),a::AbstractArray,tv,args...) 
    t = _setindex(a,tv,args...);
    
    function toret(v)
        backwards_tv = v[args...];
        backwards_a = copy(v);
        backwards_a[args...] = zero.(backwards_a[args...])
        (NoTangent(),backwards_a,backwards_tv,fill(ZeroTangent(),length(args))...)
    end
    t,toret
end

This doesn't work of course, v can be a zerotangent! Let's correct for this case:

function ChainRulesCore.rrule(::typeof(_setindex),a::AbstractArray,tv,args...) 
    t = _setindex(a,tv,args...);
    
    function toret(v)
        if iszero(v)
            backwards_tv = ZeroTangent();
            backwards_a = ZeroTangent();
        else
            backwards_tv = v[args...];
            backwards_a = copy(v);
            backwards_a[args...] = zero.(backwards_a[args...])
        end
        (NoTangent(),backwards_a,backwards_tv,fill(ZeroTangent(),length(args))...)
    end
    t,toret
end

But this rule is still incorrect! When working with arrays, the tangent type can sometimes be a FillArray. FillArrays don't define setindex!, but they can be converted.

function ChainRulesCore.rrule(::typeof(_setindex),a::AbstractArray,tv,args...) 
    t = _setindex(a,tv,args...);
    
    function toret(v)
        if iszero(v)
            backwards_tv = ZeroTangent();
            backwards_a = ZeroTangent();
        else
            v = convert(typeof(a),v);
            backwards_tv = v[args...];
            backwards_a = copy(v);
            backwards_a[args...] = zero.(backwards_a[args...])
        end
        (NoTangent(),backwards_a,backwards_tv,fill(ZeroTangent(),length(args))...)
    end
    t,toret
end

Still wrong of course, as it can also be a Tangent, which cannot be copied or converted, but they can be constructed!

function ChainRulesCore.rrule(::typeof(_setindex),a::AbstractArray,tv,args...) 
    t = _setindex(a,tv,args...);
    
    function toret(v)
        if iszero(v)
            backwards_tv = ZeroTangent();
            backwards_a = ZeroTangent();
        else
            v = v isa Tangent ? construct(typeof(a),v) : v;
            v = convert(typeof(a),v);
            backwards_tv = v[args...];
            backwards_a = copy(v);
            backwards_a[args...] = zero.(backwards_a[args...])
        end
        (NoTangent(),backwards_a,backwards_tv,fill(ZeroTangent(),length(args))...)
    end
    t,toret
end

In short, my rrule essentially has to be a spaghetti of if statements, and at the end I will have no way of knowing whether my implementation will work in practice. There is no list of possible tangent types - or a formal interface that they should al satisfy, and so whatever operations I do may end up being undefined.

I have read the documentation, and I just don't understand how I am to write this backward rule. I also don't understand how I am to hook up my own types so that they play nice with chainrules.

This year old PR seems like a step in the right direction https://github.com/JuliaDiff/ChainRulesCore.jl/pull/446 but even that wouldn't solve the issue completely. ProjectTo is defined in such a way that - when faced with a type it doesn't know - it falls back to just returning the same Tangent type.

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Review the examples in the issue, then read PR #446 and the ProjectTo behavior it discusses. Define what a supported tangent interface should cover and how custom types can integrate with ChainRulesCore; done requires a maintainer-approved design or documentation path.

Written by the indexing model from the issue text.

Assessment

Tech stack
julia
Domain
tooling
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Stale
Clarity
Needs clarification
Newbie friendliness
18/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.