Repository: quantumlib/Qualtran

Issue:
[Bug Report] DecomposeTypeError for LCUBlockEncoding in QubitizationQPE
#### **Description**  
The following code works correctly when using `SelectBlockEncoding`, but throws a  
`DecomposeTypeError: Could not build call graph for C[And†]: And† is atomic.`  
when using `LCUBlockEncoding`

#### **Code Snippet**  
```python
prep_thc = PrepareTHC.from_hamiltonian_coeffs(t_l, eta, zeta, num_bits_state_prep=num_bits_state_prep)
sel_thc = SelectTHC(num_mu, num_spin_orb, num_bits_theta=num_bits_theta, keep_bitsize=prep_thc.keep_bitsize, kr1=16, kr2=16)

block_encoding_bloq = LCUBlockEncoding(
    select=sel_thc, prepare=prep_thc
)
walk = QubitizationWalkOperator(block_encoding=block_encoding_bloq)
algo_eps = 0.0016
qpe_eps = algo_eps / (walk.block_encoding.alpha * 2**0.5)

qubitization_qpe_chem_thc = QubitizationQPE(
        walk, LPResourceState.from_standard_deviation_eps(qpe_eps)
    )

def get_toffoli_counts(bloq: Bloq) -> SymbolicInt:
    return get_cost_value(bloq, QECGatesCost(), generalizer=generalize_cswap_approx).total_t_and_ccz_count(ts_per_rotation=0)['n_ccz']

num_toff = get_toffoli_counts(qubitization_qpe_chem_thc)
print(num_toff)

Discussion:
@vinayswamik: I would like to add something to this thread. I modified the `QubitizedQPE` `build_call_graph` method by removing a controlled operation, and it now works for both `LCUBlockEncoding` and `SelectBlockEncoding`.  

```diff
def build_call_graph(self, ssa: 'SympySymbolAllocator') -> 'BloqCountDictT':
    # Assumes self.unitary is not fast forwardable.
    M = 2**self.m_bits
    return {
        self.ctrl_state_prep: 1,
-       self.walk.controlled() : 1,  # Removed line
+       self.walk : 1,  # Modified line
        self.walk.reflect.controlled(ctrl_spec=CtrlSpec(cvs=0)): 2 * (self.m_bits - 1),
        self.walk: M - 2,
        self.qft_inv: 1,
    }


Solution patch:
diff --git a/qualtran/bloqs/block_encoding/lcu_block_encoding.py b/qualtran/bloqs/block_encoding/lcu_block_encoding.py
index 98af6a8f..679ac796 100644
--- a/qualtran/bloqs/block_encoding/lcu_block_encoding.py
+++ b/qualtran/bloqs/block_encoding/lcu_block_encoding.py
@@ -18,6 +18,7 @@ from typing import Dict, Optional, Tuple, Union
 import attrs
 
 from qualtran import (
+    AddControlledT,
     Bloq,
     bloq_example,
     BloqBuilder,
@@ -283,6 +284,16 @@ class LCUBlockEncoding(BlockEncoding):
         else:
             return TextBox('B[H]')
 
+    def get_ctrl_system(self, ctrl_spec: 'CtrlSpec') -> tuple['Bloq', 'AddControlledT']:
+        from qualtran.bloqs.mcmt.specialized_ctrl import get_ctrl_system_1bit_cv
+
+        return get_ctrl_system_1bit_cv(
+            self,
+            ctrl_spec,
+            current_ctrl_bit=self.control_val,
+            get_ctrl_bloq_and_ctrl_reg_name=lambda cv: (attrs.evolve(self, control_val=cv), 'ctrl'),
+        )
+
     def adjoint(self) -> 'Bloq':
         from qualtran.bloqs.mcmt.specialized_ctrl import (
             AdjointWithSpecializedCtrl,


Test patch:
diff --git a/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py b/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py
index 8612ab44..ad1d21a1 100644
--- a/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py
+++ b/qualtran/bloqs/block_encoding/lcu_block_encoding_test.py
@@ -12,15 +12,18 @@
 #  See the License for the specific language governing permissions and
 #  limitations under the License.
 
+import attrs
 import pytest
 
+import qualtran.testing as qlt_testing
+from qualtran import CtrlSpec
 from qualtran.bloqs.block_encoding.lcu_block_encoding import (
     _black_box_lcu_block,
     _black_box_select_block,
     _lcu_block,
     _select_block,
 )
-from qualtran.testing import execute_notebook
+from qualtran.resource_counting import GateCounts, get_cost_value, QECGatesCost
 
 
 def test_lcu_block_encoding(bloq_autotester):
@@ -39,6 +42,16 @@ def test_black_box_select_block_encoding(bloq_autotester):
     bloq_autotester(_black_box_select_block)
 
 
+def test_ctrl_lcu_be_cost():
+    bloq = _lcu_block()
+    assert bloq.controlled() == attrs.evolve(bloq, control_val=1)
+    assert bloq.controlled(CtrlSpec(cvs=0)) == attrs.evolve(bloq, control_val=0)
+
+    assert get_cost_value(bloq.controlled(), QECGatesCost()) == GateCounts(
+        cswap=28, and_bloq=77, clifford=438, rotation=16, measurement=77
+    )
+
+
 @pytest.mark.notebook
 def test_notebook():
-    execute_notebook('lcu_block_encoding')
+    qlt_testing.execute_notebook('lcu_block_encoding')