anchor.diff:          a.conv2d(b, [|stride; stride|], [|padding; padding|], [|dilation; dilation|])
   
       member a.conv2d(b:Tensor) = a.conv2d(b, [1; 1], [0; 0], [1; 1])
  +
  +    // a: input, NxCxHxW (batchSize x inputChannels x inputHeight x inputWidth)
  +    // b: filters, KxCxFxG (outputChannels x inputChannels x kernelHeight x kernelWidth)
  +    // t: output, NxKxLxM (batchSize x outputChannels x outputHeight x outputLength)
  +    member internal t.conv2dReverseDiff(a: Tensor, b:Tensor, aConst:bool, bConst:bool, stride:int[], padding:int[]) =
  +        let a = if aConst then a else a.primal
  +        let b = if bConst then b else b.primal
  +        let batchSize = t.shape.[0]
  +        let outputChannels = t.shape.[1]
  +        // let outputHeight = t.shape.[2]
  +        // let outputWidth = t.shape.[3]
  +        let inputChannels = a.shape.[1]
  +        let inputHeight = a.shape.[2]
  +        let inputWidth = a.shape.[3]
  +        let kernelHeight = b.shape.[2]
  +        let kernelWidth = b.shape.[3]
  +        let mutable tderivative = t.derivative
  +        if stride.[0] > 1 || stride.[1] > 1 then
  +            tderivative <- tderivative.dilate([|1;1;stride.[0];stride.[1]|])
  +        let mutable aderivative = a.zeroLike()
  +        let mutable bderivative = b.zeroLike()
  +        if not aConst then
  +            // propagate to a
  +            aderivative <- a.zerosLike()
  +            let bFlipped = b.flip([|2;3|])
  +            for k=0 to outputChannels-1 do
  +                let b = bFlipped.[k].view([|inputChannels; 1; kernelHeight; kernelWidth|])
  +                let dBounds = array2D [[0; batchSize-1; 1]; [k; k; 1]; [0; tderivative.shape.[2]-1; 1]; [0; tderivative.shape.[3]-1; 1]]
  +                let d = tderivative.GetSlice(dBounds).view([|batchSize; 1; tderivative.shape.[2]; tderivative.shape.[3]|])
  +                let mutable c : Tensor = d.conv2d(b, padding=[|kernelHeight-1; kernelWidth-1|])
  +                if padding.[0] > 0 || padding.[1] > 0 then
  +                    let cBounds = array2D [[0; batchSize-1; 1]; [0; inputChannels-1; 1]; [padding.[0]; c.shape.[2]-1-padding.[0]; 1]; [padding.[1]; c.shape.[3]-1-padding.[1]; 1]]
  +                    c <- c.GetSlice(cBounds).view([|batchSize; inputChannels; c.shape.[2]-2*padding.[0]; c.shape.[3]-2*padding.[1]|])
  +                aderivative <- aderivative + c
  +        if not bConst then
  +            // propagate to b
  +            bderivative <- b.zerosLike()
  +            for n=0 to batchSize-1 do
  +                let aa = a.primal.[n].view([|inputChannels; 1; inputHeight; inputWidth|]) // treat size-one batch of a c-channel image as a size-c batch of one-channel images
  +                let d = tderivative.[n]
  +                for k=0 to outputChannels-1 do
  +                    let dd = d.[k].view([|1; 1; tderivative.shape.[2]; tderivative.shape.[3]|])
  +                    let c = aa.conv2d(dd, padding=padding).view([|1; inputChannels; kernelHeight; kernelWidth|])
  +                    bderivative <- bderivative.addSlice([|k; 0; 0; 0|], c)
  +        aderivative, bderivative
   
       member t.reverse(?value:Tensor, ?zeroDerivatives:bool) =
           let value = defaultArg value (t.onesLike())
  
positive.code:                         | MatMulT2T2Const(a,b) -> push ((t.derivative.matmul(b.transpose()), a) :: tt)
                          | MatMulT2ConstT2(a,b) -> push ((a.transpose().matmul(t.derivative), b) :: tt)
                          | Conv1DTT(a,b,stride,padding) -> 
                              let aderivative, bderivative = t.conv1ReverseDiff(a, b, stride, padding)
                              push ((aderivative, a) :: (bderivative, b) :: tt)
                          | Conv1DTTConst(a,b,stride,padding) ->
                              let aderivative = t.conv1ReverseDiffConstFilters(a, b, stride, padding)
                              push ((aderivative, a) :: tt)                        
                          | Conv1DTConstT(a,b,stride,padding) ->
                              let bderivative = t.conv1ReverseDiffConstInput(a, b, stride, padding)
                              push ((bderivative, b) :: tt)                        
                          | Conv2DTT(a,b,stride,padding) -> 
                              let aderivative, bderivative = t.conv2ReverseDiff(a, b, stride, padding)
                              push ((aderivative, a) :: (bderivative, b) :: tt)
                          | Conv2DTTConst(a,b,stride,padding) ->
                              let aderivative = t.conv2ReverseDiffConstFilters(a, b, stride, padding)
                              push ((aderivative, a) :: tt)
                          | Conv2DTConstT(a,b,stride,padding) ->
                              let bderivative = t.conv2ReverseDiffConstInput(a, b, stride, padding)
                              push ((bderivative, b) :: tt)
                          | NegT(a) -> push ((-t.derivative, a) :: tt)
                          | SumT(a) -> push ((t.derivative.expand(a.shape), a) :: tt)
  
negative.code:     // a: input, NxCxI (batchSize x inputChannels x inputLength)
      // b: filters, KxCxF (outputChannels x inputChannels x kernelLength)
      // t: output, NxKxL (batchSize x outputChannels x outputLength)
      member internal t.conv1dReverseDiff(a: Tensor, b:Tensor, aConst:bool, bConst:bool, stride:int, padding:int) =
          let a = if aConst then a else a.primal
          let b = if bConst then b else b.primal
          let batchSize = t.shape.[0]
          let outputChannels = t.shape.[1]
          let outputLength = t.shape.[2]
  