Repository: ProjectQ-Framework/ProjectQ

Issue:
Controlled tensors work inconsistently w.r.t. non-controlled tensors
When you apply:

    All(X) | targets

The engine receives X commands via the __or__ implementation in Tensor.

But if you apply:

    C(All(X)) | (control, targets)

Then then the __or__ operation is bypassed and the engine receives a Tensor command instead.

Comments:

Patch:
diff --git a/projectq/cengines/_replacer/__init__.py b/projectq/cengines/_replacer/__init__.py
index e55b34f..b35d31e 100755
--- a/projectq/cengines/_replacer/__init__.py
+++ b/projectq/cengines/_replacer/__init__.py
@@ -15,4 +15,3 @@ from ._decomposition_rule_set import DecompositionRuleSet
 from ._replacer import (AutoReplacer,
                         InstructionFilter,
                         NoGateDecompositionError)
-
diff --git a/projectq/ops/_basics.py b/projectq/ops/_basics.py
index bf0c225..8a37f4f 100755
--- a/projectq/ops/_basics.py
+++ b/projectq/ops/_basics.py
@@ -416,7 +416,8 @@ class BasicMathGate(BasicGate):
         gate, given the input to the gate (a tuple of quantum registers).
 
         Args:
-            qubits (tuple<Qureg>): Qubits to which the math gate is being applied.
+            qubits (tuple<Qureg>): Qubits to which the math gate is being
+                applied.
 
         Returns:
             math_fun (function): Python function describing the action of this
diff --git a/projectq/ops/_metagates.py b/projectq/ops/_metagates.py
index b3841c7..975483a 100755
--- a/projectq/ops/_metagates.py
+++ b/projectq/ops/_metagates.py
@@ -203,9 +203,9 @@ class ControlledGate(BasicGate):
             raise ControlQubitError("Wrong number of control qubits. "
                                     "First qureg(s) need to contain exactly "
                                     "the required number of control qubits.")
-        cmd = BasicGate.generate_command(self._gate, tuple(gate_quregs))
-        cmd.add_control_qubits(ctrl)
-        apply_command(cmd)
+        import projectq.meta
+        with projectq.meta.Control(gate_quregs[0][0].engine, ctrl):
+            self._gate | tuple(gate_quregs)
 
     def __eq__(self, other):
         """ Compare two ControlledGate objects (return True if equal). """


Test patch:
diff --git a/projectq/cengines/_replacer/_replacer_test.py b/projectq/cengines/_replacer/_replacer_test.py
index cd9266d..43bb487 100755
--- a/projectq/cengines/_replacer/_replacer_test.py
+++ b/projectq/cengines/_replacer/_replacer_test.py
@@ -15,7 +15,9 @@
 import pytest
 
 from projectq import MainEngine
-from projectq.cengines import DummyEngine, DecompositionRuleSet, DecompositionRule
+from projectq.cengines import (DummyEngine,
+                               DecompositionRuleSet,
+                               DecompositionRule)
 from projectq.ops import H, X, Command, S, Rx, NotInvertible, Ry, BasicGate
 
 from projectq.cengines._replacer import _replacer
@@ -59,14 +61,16 @@ def make_decomposition_rule_set():
         return True
 
     result.add_decomposition_rule(
-        DecompositionRule(TestGate.__class__, decompose_test1, recognize_test))
+        DecompositionRule(TestGate.__class__, decompose_test1,
+                          recognize_test))
 
     def decompose_test2(cmd):
         qb = cmd.qubits
         H | qb
 
     result.add_decomposition_rule(
-        DecompositionRule(TestGate.__class__, decompose_test2, recognize_test))
+        DecompositionRule(TestGate.__class__, decompose_test2,
+                          recognize_test))
 
     assert len(result.decompositions[TestGate.__class__.__name__]) == 2
     return result
@@ -141,22 +145,28 @@ def test_auto_replacer_use_inverse_decomposition():
     # Create test gate and inverse
     class NoMagicGate(BasicGate):
         pass
+
     class MagicGate(BasicGate):
         def get_inverse(self):
             return NoMagicGate()
+
     def decompose_no_magic_gate(cmd):
         qb = cmd.qubits
         Rx(0.6) | qb
         H | qb
+
     def recognize_no_magic_gate(cmd):
         return True
+
     rule_set.add_decomposition_rule(DecompositionRule(NoMagicGate,
                                                       decompose_no_magic_gate,
                                                       recognize_no_magic_gate))
+
     def magic_filter(self, cmd):
         if cmd.gate == MagicGate():
             return False
         return True
+
     backend = DummyEngine(save_commands=True)
     eng = MainEngine(backend=backend,
                      engine_list=[_replacer.AutoReplacer(rule_set),
@@ -181,7 +191,7 @@ def test_auto_replacer_adds_tags(fixture_gate_filter):
     assert len(rule_set.decompositions[TestGate.__class__.__name__]) == 2
     assert len(backend.received_commands) == 0
     qb = eng.allocate_qubit()
-    cmd = Command(eng, TestGate, (qb,) )
+    cmd = Command(eng, TestGate, (qb,))
     cmd.tags = ["AddedTag"]
     eng.send([cmd])
     eng.flush()
diff --git a/projectq/ops/_metagates_test.py b/projectq/ops/_metagates_test.py
index d8defc4..8fa94fa 100755
--- a/projectq/ops/_metagates_test.py
+++ b/projectq/ops/_metagates_test.py
@@ -21,12 +21,27 @@ from projectq.types import Qubit, Qureg
 from projectq import MainEngine
 from projectq.cengines import DummyEngine
 from projectq.ops import (T, Y, NotInvertible, Entangle, Rx,
-                          FastForwardingGate, Command,
-                          ClassicalInstructionGate)
+                          FastForwardingGate, Command, C,
+                          ClassicalInstructionGate, All)
 
 from projectq.ops import _metagates
 
 
+def test_tensored_controlled_gate():
+    saving_backend = DummyEngine(save_commands=True)
+    main_engine = MainEngine(backend=saving_backend,
+                             engine_list=[DummyEngine()])
+    gate = Rx(0.6)
+    qubit0 = Qubit(main_engine, 0)
+    qubit1 = Qubit(main_engine, 1)
+    qubit2 = Qubit(main_engine, 2)
+    target_qubits = [qubit1, qubit2]
+    C(All(gate)) | (qubit0, target_qubits)
+
+    assert saving_backend.received_commands[-1].gate == gate
+    assert len(saving_backend.received_commands[-1].control_qubits) == 1
+
+
 def test_daggered_gate_init():
     # Choose gate which does not have an inverse gate:
     not_invertible_gate = T