Repository: qutip/qutip

Issue:
Unable to add 2-qudit gates to a quantum circuit of 4-level qudits.
### Describe the Issue!

I'm having issues with running a quantum circuit that consists of 4-level qudits. I want to define my custom 2-qudit gate.

I have written a gate function that returns the following object:

`return Qobj(mat, dims=[[4,4],[4,4]], shape = [16, 16], type='oper')`

Since this is a gate acting on two 4-level qudits, I think that these dims are correct.

I then create an initial ket state for my circuit, let's say with three qudits, and create the circuit object:

```
zero_state=tensor(basis(4, 0),basis(4, 0),basis(4, 0))
qc = QubitCircuit(N=3, dims=zero_state.dims)
```

the dims are: [[4, 4, 4], [1, 1, 1]]

when I try to add a gate between qudits 0 and 1:

`qc.add_gate("Ucomm", targets=[0,1])`

I get the following error:

`ValueError: The operator dims [4, 4] do not match the target dims [[4, 4, 4], [1, 1, 1]].`

If I instead try to add a gate between qudits 1 and 2:
`qc.add_gate("Ucomm", targets=[1,2])`

I get:

```
targ_dims = [dims[t] for t in targets]
IndexError: list index out of range
```

Do I understand correctly how dims work in Qutip? Or is this an error? The above example works perfectly fine for qubits, replacing 4->2 in the above.



Discussion:
@ALasek If I pass `dims=[4, 4, 4]` I can add the gates successfully. The documentation does indicate that the dims should be a list of integers, rather than exactly the dims of an associated ket, but I can also see that it might be confusing.

@BoxiLi Should qutip.qip perhaps raise an exception if the dims are something other than a list of integers? Or is having dimensions like `[[2, 2], [2,2], [2, 2]]` something that might be useful / correct for someone to do? If so, maybe it is fine as it is.
@hodgestar Thanks for you help!

However, If I set `dims=[4, 4, 4]`, an issue with the initial state arises.

I'm setting an initial state by converting from a regular array to a Qobj:

`psi_zero=Qobj(psi_initial,dims=[4,4,4],type='ket')`

If I set `dims=[4, 4, 4]` instead of  ` [[4, 4, 4], [1, 1, 1]]`, I will get an error for the ket:

 ```
File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\IPython\core\formatters.py", line 345, in __call__
    return method()

  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\qobj.py", line 831, in _repr_latex_
    t = self.type

  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\qobj.py", line 2058, in type
    self._type = type_from_dims(self.dims)

  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\dimensions.py", line 65, in type_from_dims
    bra_like, ket_like = map(is_scalar, dims)

ValueError: too many values to unpack (expected 2)
```

It seems that qutip requires dims to be a list of two lists, giving two dimensions for each particle.



Hi @ALasek, have you tried
```
zero_state=tensor(basis(4, 0),basis(4, 0),basis(4, 0))
qc = QubitCircuit(N=3, dims=[4, 4, 4])
```

Indeed there is some confusion in the names. The parameter `dims` in `QubitCircuit` represents the dimension of the Hilbert space, hence `[2, 2, 2]` for three qubits and `[4, 4, 4]` for 3 qudits. The `dims` in `Qobj` represents the dimension of the operator, a linear map from one Hilbert space to another. Hence it is `[[4, 4, 4], [1, 1, 1]]` for a qudits ket and `[[4, 4, 4], [4, 4, 4]]` for a qudits gate.
Hi @BoxiLi 

If I mix dimensions like that, I can add the gates, but I get an error about wrong dimensions when trying to run the circuit:

`psi_final_Q=qc.run(state=psi_zero)`

```
Traceback (most recent call last):

  File "C:\Users\OlekAdmin\Documents\GitHub\NATS\ManojExperiment\Huse\David\RUC_David_comm.py", line 461, in <module>
    psi_final=qc.run(state=psi_zero)

  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\qip\circuit.py", line 1182, in run
    sim = CircuitSimulator(self, state, cbits, U_list, measure_results,

  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\qip\circuit.py", line 2004, in __init__
    self.initialize(state, cbits, measure_results)

  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\qip\circuit.py", line 2114, in initialize
    raise ValueError("dimension of state is incorrect")

ValueError: dimension of state is incorrect
```

PS This all works fine when state dimension is 2 instead of 4.

PPS I can sidestep this error by manually constructing my 2-qudit gate as a 3-qudit (N-qudit in general) operator by tensoring it with the appropriate identity, and then applying this operator on my initial ket, but I'm afraid I'm losing a lot of efficiency by not using the quantum circuit functionality.
Hi @ALasek 

You are right, there is a bug in the error handling.
```
  File "C:\Users\OlekAdmin\anaconda3\lib\site-packages\qutip\qip\circuit.py", line 2114, in initialize
    raise ValueError("dimension of state is incorrect")
```
This check is wrong. It assumes that the state is a qubits state. Our test didn't cover the `run` method with multi-level systems.

Here are the ways around:
- If you know how to hack the package, you can simply find the file and delete this error handling (lines 2113 and 2114).
- If you don't know how to do that, you can also use
```
gate_sequence_product(qc.propagators())
```
to compute the unitary representation of the circuit first and then apply it to the input state.

I'll make a bug fix soon. But the next release may still take a while.

Using `QubitCirucuit` for qutrits and qudits should be fine, it is rarely used though. So there might be bugs that are uncovered by our test. If you encounter any further questions, feel free to ask!

@BoxiLi 

Thanks, I've commented this error check  out of circuit.py, and am able to run my program now!
PS feel free to close this issue if you consider it resolved on you side.

Resolving patch:
diff --git a/qutip/qip/circuit.py b/qutip/qip/circuit.py
--- a/qutip/qip/circuit.py
+++ b/qutip/qip/circuit.py
@@ -2080,8 +2080,6 @@ def initialize(self, state=None, cbits=None, measure_results=None):
         self.state = None
 
         if state is not None:
-            if state.shape[0] != 2 ** self.qc.N:
-                raise ValueError("dimension of state is incorrect")
             if self.mode == "density_matrix_simulator" and state.isket:
                 self.state = ket2dm(state)
             else:


Test patch:
diff --git a/qutip/tests/test_qubitcircuit.py b/qutip/tests/test_qubitcircuit.py
--- a/qutip/tests/test_qubitcircuit.py
+++ b/qutip/tests/test_qubitcircuit.py
@@ -8,7 +8,8 @@
     QubitCircuit, CircuitSimulator, Gate, Measurement, _ctrl_gates,
     _single_qubit_gates, _swap_like, _toffoli_like, _fredkin_like, _para_gates)
 from qutip import (tensor, Qobj, ptrace, rand_ket, fock_dm, basis,
-                   rand_dm, bell_state, ket2dm)
+                   rand_unitary_haar, bell_state, ket2dm, fidelity,
+                   average_gate_fidelity)
 from qutip.qip.qasm import read_qasm
 from qutip.qip.operations.gates import gate_sequence_product
 
@@ -461,7 +462,7 @@ def test_N_level_system(self):
         """
         Test for circuit with N-level system.
         """
-        mat3 = rand_dm(3, density=1.)
+        mat3 = rand_unitary_haar(3)
 
         def controlled_mat3(arg_value):
             """
@@ -476,7 +477,13 @@ def controlled_mat3(arg_value):
         qc.user_gates = {"CTRLMAT3": controlled_mat3}
         qc.add_gate("CTRLMAT3", targets=[1, 0], arg_value=1)
         props = qc.propagators()
-        np.testing.assert_allclose(mat3, ptrace(props[0], 0) - 1)
+        final_fid = average_gate_fidelity(mat3, ptrace(props[0], 0) - 1)
+        assert pytest.approx(final_fid, 1.0e-6) == 1
+
+        init_state = basis([3, 2], [0, 1])
+        result = qc.run(init_state)
+        final_fid = fidelity(result, props[0] * init_state)
+        assert pytest.approx(final_fid, 1.0e-6) == 1.
 
     @pytest.mark.repeat(10)
     def test_run_teleportation(self):