diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index d9f8d80b3..d95c64f66 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -85,10 +85,23 @@ inline Status ParseProtoControls(const Operation& op, const unsigned int num_qubits, std::vector* control_qubits, std::vector* control_values) { + const auto control_qubits_it = op.args().find("control_qubits"); + const auto control_values_it = op.args().find("control_values"); + if (control_qubits_it == op.args().end() && + control_values_it == op.args().end()) { + return ::tensorflow::Status(); + } + if (control_qubits_it == op.args().end() || + control_values_it == op.args().end()) { + return Status(absl::StatusCode::kInvalidArgument, + "Both control_qubits and control_values must be specified if " + "either is present."); + } + absl::string_view control_str = - op.args().at("control_qubits").arg_value().string_value(); + control_qubits_it->second.arg_value().string_value(); absl::string_view control_v_str = - op.args().at("control_values").arg_value().string_value(); + control_values_it->second.arg_value().string_value(); if (control_str == "" && control_v_str == "") { // empty default value set in serializer.py @@ -108,12 +121,18 @@ inline Status ParseProtoControls(const Operation& op, return ::tensorflow::Status(); } bool valid; - unsigned int tmp; + unsigned int tmp = 0; control_qubits->reserve(control_toks.size()); for (auto tok : control_toks) { - // don't bother error checking since this is done earlier - // in program_resolution. valid = absl::SimpleAtoi(tok, &tmp); + if (!valid) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable control qubit index: " + std::string(tok)); + } + if (tmp >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Control qubit index out of range: " + std::to_string(tmp)); + } control_qubits->push_back(num_qubits - tmp - 1); } control_values->reserve(control_v_toks.size()); @@ -146,9 +165,6 @@ inline Status OptionalInsertControls(const Operation& op, } // series of fixed signature gate builders. -// there is no need to error check for unparseable symbols -// or proto args not being present. Those errors are caught -// upstream. // single qubit gate Create(time, q0) inline Status SingleConstantGate( @@ -156,8 +172,19 @@ inline Status SingleConstantGate( const std::function& create_f, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - unsigned int q0; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); + unsigned int q0 = 0; + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "SingleConstantGate requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } auto gate = create_f(time, num_qubits - q0 - 1); Status s = OptionalInsertControls(op, num_qubits, &gate); if (!s.ok()) { @@ -181,9 +208,32 @@ inline Status TwoConstantGate( create_f, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - unsigned int q0, q1; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); - (void)absl::SimpleAtoi(op.qubits(1).id(), &q1); + unsigned int q0 = 0, q1 = 0; + if (op.qubits_size() < 2) { + return Status(absl::StatusCode::kInvalidArgument, + "TwoConstantGate requires at least 2 qubits."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (!absl::SimpleAtoi(op.qubits(1).id(), &q1)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(1).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } + if (q1 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q1)); + } + if (q0 == q1) { + return Status( + absl::StatusCode::kInvalidArgument, + "TwoConstantGate requires distinct qubits: " + std::to_string(q0)); + } auto gate = create_f(time, num_qubits - q0 - 1, num_qubits - q1 - 1); Status s = OptionalInsertControls(op, num_qubits, &gate); if (!s.ok()) { @@ -207,10 +257,21 @@ inline Status SingleEigenGate( create_f, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - unsigned int q0; + unsigned int q0 = 0; float exp, exp_s, gs; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "SingleEigenGate requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } absl::optional exponent_symbol; u = ParseProtoArg(op, "exponent", param_map, &exp, &exponent_symbol); @@ -255,11 +316,34 @@ inline Status TwoEigenGate( float, float)>& create_f, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - unsigned int q0, q1; + unsigned int q0 = 0, q1 = 0; float exp, exp_s, gs; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); - (void)absl::SimpleAtoi(op.qubits(1).id(), &q1); + if (op.qubits_size() < 2) { + return Status(absl::StatusCode::kInvalidArgument, + "TwoEigenGate requires at least 2 qubits."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (!absl::SimpleAtoi(op.qubits(1).id(), &q1)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(1).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } + if (q1 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q1)); + } + if (q0 == q1) { + return Status( + absl::StatusCode::kInvalidArgument, + "TwoEigenGate requires distinct qubits: " + std::to_string(q0)); + } absl::optional exponent_symbol; u = ParseProtoArg(op, "exponent", param_map, &exp, &exponent_symbol); @@ -394,10 +478,21 @@ inline Status PhasedXGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - int q0; + unsigned int q0 = 0; float pexp, pexp_s, exp, exp_s, gs; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "PhasedXGate requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } absl::optional exponent_symbol; u = ParseProtoArg(op, "exponent", param_map, &exp, &exponent_symbol); @@ -453,11 +548,34 @@ inline Status FsimGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - int q0, q1; + unsigned int q0 = 0, q1 = 0; float theta, theta_s, phi, phi_s; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); - (void)absl::SimpleAtoi(op.qubits(1).id(), &q1); + if (op.qubits_size() < 2) { + return Status(absl::StatusCode::kInvalidArgument, + "FsimGate requires at least 2 qubits."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (!absl::SimpleAtoi(op.qubits(1).id(), &q1)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(1).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } + if (q1 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q1)); + } + if (q0 == q1) { + return Status( + absl::StatusCode::kInvalidArgument, + "FsimGate requires distinct qubits: " + std::to_string(q0)); + } absl::optional theta_symbol; u = ParseProtoArg(op, "theta", param_map, &theta, &theta_symbol); @@ -509,11 +627,34 @@ inline Status PhasedISwapGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { - int q0, q1; + unsigned int q0 = 0, q1 = 0; float pexp, pexp_s, exp, exp_s; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q0); - (void)absl::SimpleAtoi(op.qubits(1).id(), &q1); + if (op.qubits_size() < 2) { + return Status(absl::StatusCode::kInvalidArgument, + "PhasedISwapGate requires at least 2 qubits."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q0)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (!absl::SimpleAtoi(op.qubits(1).id(), &q1)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(1).id()); + } + if (q0 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q0)); + } + if (q1 >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q1)); + } + if (q0 == q1) { + return Status( + absl::StatusCode::kInvalidArgument, + "PhasedISwapGate requires distinct qubits: " + std::to_string(q0)); + } absl::optional exponent_symbol; u = ParseProtoArg(op, "exponent", param_map, &exp, &exponent_symbol); @@ -599,13 +740,30 @@ inline Status AsymmetricDepolarizingChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float p_x, p_y, p_z; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "p_x", {}, &p_x); + if (!u.ok()) { + return u; + } u = ParseProtoArg(op, "p_y", {}, &p_y); + if (!u.ok()) { + return u; + } u = ParseProtoArg(op, "p_z", {}, &p_z); if (!u.ok()) { return u; @@ -620,10 +778,21 @@ inline Status DepolarizingChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float p; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "p", {}, &p); if (!u.ok()) { @@ -637,10 +806,21 @@ inline Status DepolarizingChannel(const Operation& op, inline Status GADChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float p, gamma; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "p", {}, &p); if (!u.ok()) { @@ -660,8 +840,19 @@ inline Status GADChannel(const Operation& op, const unsigned int num_qubits, inline Status ResetChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + unsigned int q = 0; + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } auto chan = qsim::Cirq::ResetChannel::Create(time, num_qubits - q - 1); ncircuit->channels.push_back(chan); @@ -672,10 +863,21 @@ inline Status AmplitudeDampingChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float gamma; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "gamma", {}, &gamma); if (!u.ok()) { @@ -691,10 +893,21 @@ inline Status PhaseDampingChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float gamma; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "gamma", {}, &gamma); if (!u.ok()) { @@ -711,10 +924,21 @@ inline Status PhaseFlipChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float p; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "p", {}, &p); if (!u.ok()) { @@ -730,10 +954,21 @@ inline Status PhaseFlipChannel(const Operation& op, inline Status BitFlipChannel(const Operation& op, const unsigned int num_qubits, const unsigned int time, NoisyQsimCircuit* ncircuit) { - int q; + unsigned int q = 0; float p; Status u; - (void)absl::SimpleAtoi(op.qubits(0).id(), &q); + if (op.qubits_size() < 1) { + return Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit."); + } + if (!absl::SimpleAtoi(op.qubits(0).id(), &q)) { + return Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: " + op.qubits(0).id()); + } + if (q >= num_qubits) { + return Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: " + std::to_string(q)); + } u = ParseProtoArg(op, "p", {}, &p); if (!u.ok()) { diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc b/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc index a158d2ae3..e7b03e2b5 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc @@ -1706,5 +1706,155 @@ TEST(QsimCircuitParserTest, ZBasisCircuitFromPauliTermEmpty) { ASSERT_EQ(test_circuit.gates.size(), 0); } +TEST(QsimCircuitParserTest, InvalidCircuitInputs) { + Program program_proto; + Circuit* circuit_proto = program_proto.mutable_circuit(); + circuit_proto->set_scheduling_strategy(circuit_proto->MOMENT_BY_MOMENT); + Moment* moments_proto = circuit_proto->add_moments(); + + // 1. Single qubit gate HP (HGate) with NO qubits + Operation* op = moments_proto->add_operations(); + op->mutable_gate()->set_id("HP"); + (*op->mutable_args())["exponent"] = MakeArg(1.0); + (*op->mutable_args())["exponent_scalar"] = MakeArg(1.0); + (*op->mutable_args())["global_shift"] = MakeArg(0.0); + (*op->mutable_args())["control_qubits"] = MakeControlArg(""); + (*op->mutable_args())["control_values"] = MakeControlArg(""); + + QsimCircuit test_circuit; + std::vector> fused_circuit; + SymbolMap empty_map; + + // SingleEigenGate requires at least 1 qubit + ASSERT_EQ(QsimCircuitFromProgram(program_proto, empty_map, 1, &test_circuit, + &fused_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "SingleEigenGate requires at least 1 qubit.")); + + // 2. Clear operations, add a gate with unparseable qubit id + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("HP"); + (*op->mutable_args())["exponent"] = MakeArg(1.0); + (*op->mutable_args())["exponent_scalar"] = MakeArg(1.0); + (*op->mutable_args())["global_shift"] = MakeArg(0.0); + (*op->mutable_args())["control_qubits"] = MakeControlArg(""); + (*op->mutable_args())["control_values"] = MakeControlArg(""); + op->add_qubits()->set_id("invalid_index"); + + ASSERT_EQ(QsimCircuitFromProgram(program_proto, empty_map, 1, &test_circuit, + &fused_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: invalid_index")); + + // 3. Qubit index out of range (q0 >= num_qubits) + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("HP"); + (*op->mutable_args())["exponent"] = MakeArg(1.0); + (*op->mutable_args())["exponent_scalar"] = MakeArg(1.0); + (*op->mutable_args())["global_shift"] = MakeArg(0.0); + (*op->mutable_args())["control_qubits"] = MakeControlArg(""); + (*op->mutable_args())["control_values"] = MakeControlArg(""); + op->add_qubits()->set_id("5"); // num_qubits is 2, so 5 is out of range + + ASSERT_EQ(QsimCircuitFromProgram(program_proto, empty_map, 2, &test_circuit, + &fused_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: 5")); + + // 4. Two qubit gate with only 1 qubit + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("CZP"); + (*op->mutable_args())["exponent"] = MakeArg(1.0); + (*op->mutable_args())["exponent_scalar"] = MakeArg(1.0); + (*op->mutable_args())["global_shift"] = MakeArg(0.0); + (*op->mutable_args())["control_qubits"] = MakeControlArg(""); + (*op->mutable_args())["control_values"] = MakeControlArg(""); + op->add_qubits()->set_id("0"); + + ASSERT_EQ(QsimCircuitFromProgram(program_proto, empty_map, 2, &test_circuit, + &fused_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "TwoEigenGate requires at least 2 qubits.")); + + // 5. Unparseable control qubit + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("HP"); + (*op->mutable_args())["exponent"] = MakeArg(1.0); + (*op->mutable_args())["exponent_scalar"] = MakeArg(1.0); + (*op->mutable_args())["global_shift"] = MakeArg(0.0); + (*op->mutable_args())["control_qubits"] = MakeControlArg("bad"); + (*op->mutable_args())["control_values"] = MakeControlArg("0"); + op->add_qubits()->set_id("0"); + + ASSERT_EQ(QsimCircuitFromProgram(program_proto, empty_map, 2, &test_circuit, + &fused_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Unparseable control qubit index: bad")); + + // 6. Control qubit out of range + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("HP"); + (*op->mutable_args())["exponent"] = MakeArg(1.0); + (*op->mutable_args())["exponent_scalar"] = MakeArg(1.0); + (*op->mutable_args())["global_shift"] = MakeArg(0.0); + (*op->mutable_args())["control_qubits"] = + MakeControlArg("4"); // num_qubits is 2 + (*op->mutable_args())["control_values"] = MakeControlArg("0"); + op->add_qubits()->set_id("0"); + + ASSERT_EQ(QsimCircuitFromProgram(program_proto, empty_map, 2, &test_circuit, + &fused_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Control qubit index out of range: 4")); +} + +TEST(QsimCircuitParserTest, InvalidChannelInputs) { + Program program_proto; + Circuit* circuit_proto = program_proto.mutable_circuit(); + circuit_proto->set_scheduling_strategy(circuit_proto->MOMENT_BY_MOMENT); + Moment* moments_proto = circuit_proto->add_moments(); + + // 1. Bit flip channel with empty qubits list + Operation* op = moments_proto->add_operations(); + op->mutable_gate()->set_id("BF"); + (*op->mutable_args())["p"] = MakeArg(0.1); + + NoisyQsimCircuit test_circuit; + + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 2, false, &test_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Channel requires at least 1 qubit.")); + + // 2. Bit flip channel with unparseable qubit index + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("BF"); + (*op->mutable_args())["p"] = MakeArg(0.1); + op->add_qubits()->set_id("xyz"); + + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 2, false, &test_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Unparseable qubit index: xyz")); + + // 3. Bit flip channel with qubit index out of bounds + moments_proto->clear_operations(); + op = moments_proto->add_operations(); + op->mutable_gate()->set_id("BF"); + (*op->mutable_args())["p"] = MakeArg(0.1); + op->add_qubits()->set_id("3"); // num_qubits = 2 + + ASSERT_EQ( + NoisyQsimCircuitFromProgram(program_proto, {}, 2, false, &test_circuit), + tensorflow::Status(absl::StatusCode::kInvalidArgument, + "Qubit index out of range: 3")); +} + } // namespace } // namespace tfq