Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make qml.math.block_diag fully differentiable with autograd #1816

Merged
merged 5 commits into from
Oct 28, 2021

Conversation

dwierichs
Copy link
Contributor

Context:
Previously, results of qml.math.block_diag were only differentiable by indexing and differentiating single entries of the resulting matrix. This was due to the Autograd single dispatch using the vanilla scipy function block_diag.

Description of the Change:
There now is a custom method that implements block_diag for Autograd, supporting differentiation of the full output matrix.

Benefits:
More diffability of qml.math.block_diag (as far as I can tell only used in metric_tensor so far).

Possible Drawbacks:
(More custom code.)

Related GitHub Issues:
#1814

@codecov
Copy link

codecov bot commented Oct 28, 2021

Codecov Report

Merging #1816 (90044a1) into master (a97ac88) will increase coverage by 0.00%.
The diff coverage is 100.00%.

Impacted file tree graph

@@           Coverage Diff           @@
##           master    #1816   +/-   ##
=======================================
  Coverage   98.92%   98.92%           
=======================================
  Files         209      209           
  Lines       15727    15737   +10     
=======================================
+ Hits        15558    15568   +10     
  Misses        169      169           
Impacted Files Coverage Δ
pennylane/math/single_dispatch.py 99.51% <100.00%> (+0.02%) ⬆️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update a97ac88...90044a1. Read the comment docs.

Copy link
Member

@josh146 josh146 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @dwierichs this is great!

Comment on lines 79 to 80
rsizes = _np.array([t.shape[0] for t in tensors])
csizes = _np.array([t.shape[1] for t in tensors])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will rsizes and csizes always be identical for block_diag to work?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nevermind, I just checked the scipy docs, and it looks like arbitrary shapes are allowed 👍

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose, if you assume that all tensors are 2D, you could do

rsizes, csizes = _np.array([t.shape for t in tensors]).T

to save on one for loop 😆

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(unless this blocks constructing ND block diagonal matrices, which it might?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, no, we only allow for 2d block diagonals :)
So yes, I'll incorporate the trick above, thanks!

Regarding non-square shapes, we are a bit inconsistent: I made this compatible with non-squares because the scipy version is, but for example TF does not allow for it anyways so if one wants to use all frameworks, there is a restriction to all-square tensors.

Comment on lines +83 to +88
res = _np.hstack([tensors[0], *all_zeros[0][1:]])
for i, t in enumerate(tensors[1:], start=1):
row = _np.hstack([*all_zeros[i][:i], t, *all_zeros[i][i + 1 :]])
res = _np.vstack([res, row])

return res
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice solution! I realize now that the torch approach below wouldn't work here, since res[row, col] = t uses tensor assignment that is not allowed by autograd.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly, that is what I tried first :D

Comment on lines +1442 to +1444
# Transposes in the following because autograd behaves strangely
assert fn.allclose(res[:, :, 0].T, exp[:, :, 0])
assert fn.allclose(res[:, :, 1].T, exp[:, :, 1])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, how come?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is it fixed if you swap to autograd.jacobian?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, because then one has to request the jacobian for one argnum anyways and a output-like tensor is returned.
qml.jacobian does the following:

>>> qml.jacobian(lambda x, y: np.array([[1*x, 2*x, 3*x],[4*y,5*y, 6*y]]))(0.3, 0.2)
[[[1. 0.]
  [0. 4.]]

 [[2. 0.]
  [0. 5.]]

 [[3. 0.]
  [0. 6.]]]

that is, for a mxn-shaped output function with k (scalar) arguments, the jacobian has the shape (n, m, k).
If instead there is a single argument with k entries, we get the shape (m, n, k):

>>> qml.jacobian(lambda x: np.array([[1*x[0], 2*x[0], 3*x[0]],[4*x[1],5*x[1], 6*x[1]]]))(np.array([0.3, 0.2]))
[[[1. 0.]
  [2. 0.]
  [3. 0.]]

 [[0. 4.]
  [0. 5.]
  [0. 6.]]]

tests/math/test_functions.py Show resolved Hide resolved
@josh146 josh146 linked an issue Oct 28, 2021 that may be closed by this pull request
1 task
@dwierichs dwierichs merged commit b9db331 into master Oct 28, 2021
@dwierichs dwierichs deleted the block-diag-diffable branch October 28, 2021 11:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

[BUG] qml.math.block_diag is not differentiable under autograd
2 participants