CQCL / tket

Source code for the TKET quantum compiler, Python bindings and utilities
https://tket.quantinuum.com/
Apache License 2.0
248 stars 48 forks source link

Invalid SliceIterator on Circuit with mid measurement #1357

Open lmondada opened 4 months ago

lmondada commented 4 months ago

After investigating #1324, it seems the bug is not in SquashRzPhasedX, but in the command iteration utility. In fact, consider the following circuit:

  Circuit c(2, 1);
  c.add_op<unsigned>(OpType::CZ, {0, 1});
  auto meas = c.add_measure(1, 0);

  auto cl_input = c.c_inputs()[0];
  EdgeVec preds;
  preds.push_back(c.get_nth_out_edge(cl_input, 0));
  preds.push_back(c.get_nth_out_edge(meas, 0));
  auto ptr =
      std::make_shared<Conditional>(get_op_ptr(OpType::Rz, 0.1, 1), 1, 0);
  auto new_v = c.add_vertex(ptr);
  op_signature_t sigs;
  sigs.push_back(EdgeType::Boolean);
  sigs.push_back(EdgeType::Quantum);
  c.rewire(new_v, preds, sigs);

This circuit definition is slightly awkward, as I don't think there is any add_op-type calls I can use to build the circuit as needed. The "trick" here is that a measurement is performed and a quantum gate is then added on the measured qubit post-measurement. However, the gate is classically controlled on the classical value of the classical bit before the measurement. It is a valid DAG, but there is no linear order of the "measure" and "add_conditional" operations that would yield this graph. As far as I can tell, though, it is valid. It looks as follows:

image

The following assertion then fails:

  auto slice_it = c.slice_begin();
  assert(slice_it.finished() || (*++slice_it).size() > 0);

AFAIK this is an invalid state for the iterator to be in.

Full MWE:

#include <iostream>
#include <tkassert/Assert.hpp>
#include <tket/Circuit/Circuit.hpp>

#include "tket/Circuit/DAGDefs.hpp"
#include "tket/Gate/OpPtrFunctions.hpp"
#include "tket/OpType/EdgeType.hpp"
#include "tket/Transformations/BasicOptimisation.hpp"
#include "tket/Transformations/Decomposition.hpp"

using namespace tket;

int main() {
  Circuit c(2, 1);
  c.add_op<unsigned>(OpType::CZ, {0, 1});
  auto meas = c.add_measure(1, 0);

  auto cl_input = c.c_inputs()[0];
  EdgeVec preds;
  preds.push_back(c.get_nth_out_edge(cl_input, 0));
  preds.push_back(c.get_nth_out_edge(meas, 0));
  auto ptr =
      std::make_shared<Conditional>(get_op_ptr(OpType::Rz, 0.1, 1), 1, 0);
  auto new_v = c.add_vertex(ptr);
  op_signature_t sigs;
  sigs.push_back(EdgeType::Boolean);
  sigs.push_back(EdgeType::Quantum);
  c.rewire(new_v, preds, sigs);

  auto slice_it = c.slice_begin();
  TKET_ASSERT(slice_it.finished() || (*++slice_it).size() > 0);
  return 0;
}
cqc-alec commented 4 months ago

I'm not sure what to conclude here. It looks valid as a DAG but not as a Circuit. What should be the output of c.get_commands()? The problem is that conditional gates can only be conditioned on bit values as they are at the time of gate application -- not on previous values.

I don't think compilation passes should end up with DAGs like this.

For reference, the SquashRzPhasedX pass transforms this circuit: Screenshot from 2024-04-18 10-51-32 into this: Screenshot from 2024-04-18 10-52-03 which has the same issue.

cqc-alec commented 4 months ago

I think the unwritten rule we are violating is that a Boolean wire must be the only path in the DAG from its source to its target node.

github-actions[bot] commented 3 weeks ago

This issue has been automatically marked as stale.