XanaduAI/MrMustard

Channels can only be applied at the end of a circuit

Closed this issue · 3 comments

Before posting a bug report

  • I have searched exisisting GitHub issues to make sure the issue does not already exist.

Expected behavior

An Attenuator or Amplifier gate is applied on a single mode of the circuit.

Actual behavior

An Attenuator or Amplifier gates can only be applied at the end of the circuit, otherwise it raises the reported error.

Reproduces how often

Every time an Attenuator or Amplifier is applied somewhere other than the end of the circuit.

System information

Python version:            3.8.5
Platform info:             Linux-5.4.72-microsoft-standard-WSL2-x86_64-with-glibc2.29
Installation path:         /home/sduquemesa/xanadu/MrMustard/mrmustard
Mr Mustard version:        0.2.0-dev
Numpy version:             1.19.5
Numba version:             0.53.1
Scipy version:             1.7.3
The Walrus version:        0.17.0
TensorFlow version:        2.6.2
Torch version:             None

Source code

import mrmustard.lab as mml

circ = mml.Circuit()
circ = circ >> mml.Attenuator(0.932)[1] >> mml.BSgate(theta=0.627, phi=1.15)[0, 1]

S = circ.XYd[0]

Tracebacks

Input In [4], in <module>
      1 circ = mml.Circuit()
      2 circ = circ >> mml.Attenuator(0.932)[1] >> mml.BSgate(theta=0.627, phi=1.15)[0, 1]
----> 4 circ.XYd[0]

File ~/xanadu/MrMustard/mrmustard/lab/circuit.py:83, in Circuit.XYd(self)
     81         opd = opd.clone(len(op.modes), modes=op.modes)
     82     X = opX @ X
---> 83     Y = opX @ Y @ opX.T + opY
     84     d = opX @ d + opd
     85 return X.to_xxpp(), Y.to_xxpp(), d.to_xxpp()

File ~/xanadu/MrMustard/mrmustard/utils/xptensor.py:253, in XPTensor.__matmul__(self, other)
    251 if self.isMatrix and other.isMatrix:
    252     tensor, modes = self._mode_aware_matmul(other)
--> 253     return XPMatrix(tensor, like_1=self.like_1 and other.like_1, modes=modes)
    254 elif self.isMatrix and other.isVector:
    255     tensor, modes = self._mode_aware_matmul(other)

File ~/xanadu/MrMustard/mrmustard/utils/xptensor.py:541, in XPMatrix.__init__(self, tensor, like_0, like_1, modes)
    537     modes = tuple(
    538         list(range(s)) for s in tensor.shape[:2]
    539     )  # NOTE assuming that it isn't a coherence block
    540 like_0 = like_0 if like_0 is not None else not like_1
--> 541 super().__init__(tensor, like_0, isVector=False, modes=modes)

File ~/xanadu/MrMustard/mrmustard/utils/xptensor.py:85, in XPTensor.__init__(self, tensor, like_0, isVector, modes)
     83 self.tensor = tensor
     84 if not (set(modes[0]) == set(modes[1]) or set(modes[0]).isdisjoint(modes[1])):
---> 85     raise ValueError(
     86         "The inmodes and outmodes should either contain the same modes or be disjoint"
     87     )
     88 self.modes = modes

ValueError: The inmodes and outmodes should either contain the same modes or be disjoint

Additional information

Notice that the following code where the `Attenuator` is located at the end of the circuit will run successfully.


import mrmustard.lab as mml

# %%
circ = mml.Circuit()
circ = circ >> mml.BSgate(theta=0.627, phi=1.15)[0, 1] >> mml.Attenuator(0.932)[1] 

S = circ.XYd[0]

The issue is resolved if the Attenuator is applied explicitly to all the circuit modes, where only the mode of interest have a parameter value different than one (a trivial attenuator).

import mrmustard.lab as mml

circ = mml.Circuit()
circ = circ >> mml.Attenuator([0.932,1], modes = [0,1]) >> mml.BSgate(theta=0.627, phi=1.15)[0, 1]

S = circ.XYd[0]

In this case there is an overhead of adding trivial operations to modes, which in turn can impact calculation and circuit optimization performance.

All in all this seems to be an issue coming from the way in which modes are handled by the inner workings of Mr Mustard. In specific, when doing mode-aware math multiplication.

what is happening is that an empty circuit has no modes, so if you concatenate other operations without specifying the modes (using __getitem__) then XPTensor doesn't know where to apply them (hence the error message).

in principle one shouldn't create a Circuit on its own, but rather a Circuit is returned by concatenating operations:

circ =  mml.Attenuator(0.932)[1] >> mml.BSgate(theta=0.627, phi=1.15)[0, 1]

Alternatively, specify all the modes and things should work just fine

circ = mml.Circuit()
circ = circ[0,1] >> mml.Attenuator(0.932)[1] >> mml.BSgate(theta=0.627, phi=1.15)[0, 1]

Thanks @filippo for your suggestion, however it does not seem to solve the issue. I've tried with this simple circuit:

circ = mml.Sgate(r) >> mml.Attenuator(eta)[1] >> mml.BSgate(theta=alpha[t], phi=np.pi / 2)[1, 0]
np.array(circ.XYd[0])

and also directly specifying the modes as you suggested

circ = mml.Circuit()
circ = mml.Sgate(r) >> mml.Attenuator(eta)[1] >> mml.BSgate(theta=alpha[t], phi=np.pi / 2)[1, 0]
np.array(circ.XYd[0])

In both cases the execution fails with the same error.

The issue seem to come from the way in which _mode_aware_matmul checks for valid modes (for example here and here). Notice this check is applied in several different places in Mr Mustard.

For the sake of illustration, consider the following check

if not (set(modes[0]) == set(modes[1]) or set(modes[0]).isdisjoint(modes[1])):
    raise ValueError(
        "The inmodes and outmodes should either contain the same modes or be disjoint"
    )

and the following circuit

circ = Sgate(0.4)[0,1] >> Attenuator(t, nbar)[0] >> BSgate(theta, 0)[0,1]

When executing circ.XYd the calculation, the updates related to the BSgate to the value Y (on circuit.py:L83) will have

  • inmodes = [0] this is the mode in which the last op acted on, meaning the Attenuator
  • outmodes = [0,1] this are the output modes of the BSgate op;

hence the _mode_aware_matmul operation Y = X_bs[0,1] @ Y_attenuator[0] @ X_bs.T[0,1] fails (values between brackets are annotation of the modes on which the ops acts on, this is not runnable code) due to the check mentioned above.