From 095de1052ad40e76676f5a5992aa5a03319998e6 Mon Sep 17 00:00:00 2001 From: Michael Broughton Date: Mon, 22 Feb 2021 10:58:47 -0800 Subject: [PATCH] Adds support for CCZ and CCX/TOF gates. --- .../core/ops/circuit_execution_ops_test.py | 5 +- .../core/serialize/serializer.py | 2 + .../core/serialize/serializer_test.py | 41 ++++ tensorflow_quantum/core/src/adj_util.cc | 31 +++ tensorflow_quantum/core/src/adj_util.h | 16 ++ tensorflow_quantum/core/src/adj_util_test.cc | 67 +++++++ .../core/src/circuit_parser_qsim.cc | 67 +++++++ .../core/src/circuit_parser_qsim.h | 5 + .../core/src/circuit_parser_qsim_test.cc | 184 ++++++++++++++++++ 9 files changed, 417 insertions(+), 1 deletion(-) diff --git a/tensorflow_quantum/core/ops/circuit_execution_ops_test.py b/tensorflow_quantum/core/ops/circuit_execution_ops_test.py index fabab43f7..cfa02b74d 100644 --- a/tensorflow_quantum/core/ops/circuit_execution_ops_test.py +++ b/tensorflow_quantum/core/ops/circuit_execution_ops_test.py @@ -185,7 +185,10 @@ def test_supported_gates_consistent(self, op_and_sim): for qubit in qubits: c += cirq.Circuit(cirq.Y(qubit)**0.125) - if gate_ref[gate] == 2: + if gate_ref[gate] == 3: + op_qubits = np.random.choice(qubits, size=3, replace=False) + c += cirq.Circuit(gate(*op_qubits)) + elif gate_ref[gate] == 2: op_qubits = np.random.choice(qubits, size=2, replace=False) c += cirq.Circuit(gate(*op_qubits)) elif gate_ref[gate] == 1: diff --git a/tensorflow_quantum/core/serialize/serializer.py b/tensorflow_quantum/core/serialize/serializer.py index 80d6cdd1c..d9c9d907e 100644 --- a/tensorflow_quantum/core/serialize/serializer.py +++ b/tensorflow_quantum/core/serialize/serializer.py @@ -464,7 +464,9 @@ def _scalar_combiner(exponent, global_shift, exponent_scalar, cirq.ZZPowGate: "ZZP", cirq.HPowGate: "HP", cirq.CZPowGate: "CZP", + cirq.CCZPowGate: "CCZP", cirq.CNotPowGate: "CNP", + cirq.CCXPowGate: "CCXP", cirq.SwapPowGate: "SP", cirq.ISwapPowGate: "ISP", } diff --git a/tensorflow_quantum/core/serialize/serializer_test.py b/tensorflow_quantum/core/serialize/serializer_test.py index fcf094866..750c0dc72 100644 --- a/tensorflow_quantum/core/serialize/serializer_test.py +++ b/tensorflow_quantum/core/serialize/serializer_test.py @@ -140,6 +140,7 @@ def _make_controlled_circuit(circuit, control_qubits, control_values): def _get_circuit_proto_pairs(): q0 = cirq.GridQubit(0, 0) q1 = cirq.GridQubit(0, 1) + q2 = cirq.GridQubit(0, 2) pairs = [ # HPOW and aliases. @@ -290,6 +291,26 @@ def _get_circuit_proto_pairs(): ['exponent', 'exponent_scalar', 'global_shift'], [1.0, 1.0, 0.0], ['0_0', '0_1'])), + # CCZPow and aliases + (cirq.Circuit(cirq.CCZPowGate(exponent=0.3)(q0, q1, q2)), + _build_gate_proto("CCZP", + ['exponent', 'exponent_scalar', 'global_shift'], + [0.3, 1.0, 0.0], ['0_0', '0_1', '0_2'])), + (cirq.Circuit( + cirq.CCZPowGate(exponent=sympy.Symbol('alpha'))(q0, q1, q2)), + _build_gate_proto("CCZP", + ['exponent', 'exponent_scalar', 'global_shift'], + ['alpha', 1.0, 0.0], ['0_0', '0_1', '0_2'])), + (cirq.Circuit( + cirq.CCZPowGate(exponent=3.1 * sympy.Symbol('alpha'))(q0, q1, q2)), + _build_gate_proto("CCZP", + ['exponent', 'exponent_scalar', 'global_shift'], + ['alpha', 3.1, 0.0], ['0_0', '0_1', '0_2'])), + (cirq.Circuit(cirq.CCZ(q0, q1, q2)), + _build_gate_proto("CCZP", + ['exponent', 'exponent_scalar', 'global_shift'], + [1.0, 1.0, 0.0], ['0_0', '0_1', '0_2'])), + # CNOTPow and aliases (cirq.Circuit(cirq.CNotPowGate(exponent=0.3)(q0, q1)), _build_gate_proto("CNP", @@ -309,6 +330,26 @@ def _get_circuit_proto_pairs(): ['exponent', 'exponent_scalar', 'global_shift'], [1.0, 1.0, 0.0], ['0_0', '0_1'])), + # CCXPow and aliases + (cirq.Circuit(cirq.CCXPowGate(exponent=0.3)(q0, q1, q2)), + _build_gate_proto("CCXP", + ['exponent', 'exponent_scalar', 'global_shift'], + [0.3, 1.0, 0.0], ['0_0', '0_1', '0_2'])), + (cirq.Circuit( + cirq.CCXPowGate(exponent=sympy.Symbol('alpha'))(q0, q1, q2)), + _build_gate_proto("CCXP", + ['exponent', 'exponent_scalar', 'global_shift'], + ['alpha', 1.0, 0.0], ['0_0', '0_1', '0_2'])), + (cirq.Circuit( + cirq.CCXPowGate(exponent=3.1 * sympy.Symbol('alpha'))(q0, q1, q2)), + _build_gate_proto("CCXP", + ['exponent', 'exponent_scalar', 'global_shift'], + ['alpha', 3.1, 0.0], ['0_0', '0_1', '0_2'])), + (cirq.Circuit(cirq.TOFFOLI(q0, q1, q2)), + _build_gate_proto("CCXP", + ['exponent', 'exponent_scalar', 'global_shift'], + [1.0, 1.0, 0.0], ['0_0', '0_1', '0_2'])), + # SWAPPow and aliases (cirq.Circuit(cirq.SwapPowGate(exponent=0.3)(q0, q1)), _build_gate_proto("SP", diff --git a/tensorflow_quantum/core/src/adj_util.cc b/tensorflow_quantum/core/src/adj_util.cc index aee01a377..afb1bb87b 100644 --- a/tensorflow_quantum/core/src/adj_util.cc +++ b/tensorflow_quantum/core/src/adj_util.cc @@ -75,6 +75,20 @@ void CreateGradientCircuit( grad_gates->push_back(grad); } + // Three qubit Eigen. + else if (circuit.gates[i].kind == qsim::Cirq::GateKind::kCCZPowGate || + circuit.gates[i].kind == qsim::Cirq::GateKind::kCCXPowGate) { + bool swapq = circuit.gates[i].swapped; + PopulateGradientThreeEigen( + metadata[i].create_f3, metadata[i].symbol_values[0], i, + swapq ? circuit.gates[i].qubits[2] : circuit.gates[i].qubits[0], + circuit.gates[i].qubits[1], + swapq ? circuit.gates[i].qubits[0] : circuit.gates[i].qubits[2], + metadata[i].gate_params[0], metadata[i].gate_params[1], + metadata[i].gate_params[2], &grad); + grad_gates->push_back(grad); + } + // PhasedX else if (circuit.gates[i].kind == qsim::Cirq::GateKind::kPhasedXPowGate) { // Process potentially several symbols. @@ -202,6 +216,23 @@ void PopulateGradientTwoEigen( grad->grad_gates.push_back(left); } +void PopulateGradientThreeEigen( + const std::function(unsigned int, unsigned int, + unsigned int, unsigned int, + float, float)>& create_f, + const std::string& symbol, unsigned int location, unsigned int qid, + unsigned int qid2, unsigned int qid3, float exp, float exp_s, float gs, + GradientOfGate* grad) { + grad->params.push_back(symbol); + grad->index = location; + auto left = create_f(0, qid, qid2, qid3, (exp + _GRAD_EPS) * exp_s, gs); + auto right = create_f(0, qid, qid2, qid3, (exp - _GRAD_EPS) * exp_s, gs); + Matrix8Diff(right.matrix, + left.matrix); // left's entries have right subtracted. + qsim::MatrixScalarMultiply(0.5 / _GRAD_EPS, left.matrix); + grad->grad_gates.push_back(left); +} + void PopulateGradientPhasedXPhasedExponent(const std::string& symbol, unsigned int location, unsigned int qid, float pexp, diff --git a/tensorflow_quantum/core/src/adj_util.h b/tensorflow_quantum/core/src/adj_util.h index 7cc383ad0..cd8ae6b01 100644 --- a/tensorflow_quantum/core/src/adj_util.h +++ b/tensorflow_quantum/core/src/adj_util.h @@ -65,6 +65,14 @@ void PopulateGradientTwoEigen( const std::string& symbol, unsigned int location, unsigned int qid, unsigned int qid2, float exp, float exp_s, float gs, GradientOfGate* grad); +void PopulateGradientThreeEigen( + const std::function(unsigned int, unsigned int, + unsigned int, unsigned int, + float, float)>& create_f, + const std::string& symbol, unsigned int location, unsigned int qid, + unsigned int qid2, unsigned int qid3, float exp, float exp_s, float gs, + GradientOfGate* grad); + // Note: all methods below expect gate qubit indices to have been swapped so // qid < qid2. void PopulateGradientPhasedXPhasedExponent(const std::string& symbol, @@ -116,6 +124,14 @@ void Matrix4Diff(Array2& source, Array2& dest) { } } +// does matrix elementiwse subtraction dest -= source. +template +void Matrix8Diff(Array2& source, Array2& dest) { + for (unsigned i = 0; i < 128; i++) { + dest[i] -= source[i]; + } +} + } // namespace tfq #endif // TFQ_CORE_SRC_ADJ_UTIL_H_ diff --git a/tensorflow_quantum/core/src/adj_util_test.cc b/tensorflow_quantum/core/src/adj_util_test.cc index 526efc175..3a87bcd6b 100644 --- a/tensorflow_quantum/core/src/adj_util_test.cc +++ b/tensorflow_quantum/core/src/adj_util_test.cc @@ -42,6 +42,13 @@ void Matrix4Equal(const std::vector& v, } } +void Matrix8Equal(const std::vector& v, + const std::vector& expected, float eps) { + for (int i = 0; i < 128; i++) { + EXPECT_NEAR(v[i], expected[i], eps); + } +} + typedef absl::flat_hash_map> SymbolMap; typedef qsim::Cirq::GateCirq QsimGate; typedef qsim::Circuit QsimCircuit; @@ -167,6 +174,66 @@ INSTANTIATE_TEST_CASE_P( &qsim::Cirq::ISwapPowGate::Create, &qsim::Cirq::SwapPowGate::Create)); +class ThreeQubitEigenFixture + : public ::testing::TestWithParam< + std::function> {}; + +TEST_P(ThreeQubitEigenFixture, CreateGradientThreeEigen) { + QsimCircuit circuit; + std::vector metadata; + std::vector>> fuses; + std::vector grad_gates; + + // Create a symbolized gate. + std::function + given_f = GetParam(); + + circuit.num_qubits = 3; + circuit.gates.push_back(given_f(0, 0, 1, 2, 1.0, 2.0)); + GateMetaData meta; + meta.index = 0; + meta.symbol_values.push_back("TheSymbol"); + meta.placeholder_names.push_back(GateParamNames::kExponent); + meta.gate_params = {1.0, 1.0, 2.0}; + meta.create_f3 = given_f; + metadata.push_back(meta); + + CreateGradientCircuit(circuit, metadata, &fuses, &grad_gates); + EXPECT_EQ(grad_gates.size(), 1); + EXPECT_EQ(grad_gates[0].index, 0); + EXPECT_EQ(grad_gates[0].params.size(), 1); + EXPECT_EQ(grad_gates[0].params[0], "TheSymbol"); + + // fuse everything into 2 gates. One fuse before this gate and one after. + // both wind up being identity since this is the only gate. + EXPECT_EQ(fuses.size(), 2); + + GradientOfGate tmp; + PopulateGradientThreeEigen(given_f, "TheSymbol", 0, 0, 1, 2, 1.0, 1.0, 2.0, + &tmp); + + Matrix8Equal(tmp.grad_gates[0].matrix, grad_gates[0].grad_gates[0].matrix, + 1e-4); + + // Test with NO symbol. + metadata.clear(); + meta.symbol_values.clear(); + meta.placeholder_names.clear(); + fuses.clear(); + grad_gates.clear(); + + CreateGradientCircuit(circuit, metadata, &fuses, &grad_gates); + EXPECT_EQ(grad_gates.size(), 0); + EXPECT_EQ(fuses.size(), 1); // fuse everything into 1 gate. +} + +INSTANTIATE_TEST_CASE_P( + ThreeQubitEigenTests, ThreeQubitEigenFixture, + ::testing::Values(&qsim::Cirq::CCZPowGate::Create, + &qsim::Cirq::CCXPowGate::Create)); + TEST(AdjUtilTest, CreateGradientPhasedX) { QsimCircuit circuit; std::vector metadata; diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.cc b/tensorflow_quantum/core/src/circuit_parser_qsim.cc index 94799a6b2..2d73aff48 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.cc @@ -295,6 +295,58 @@ inline Status TwoEigenGate( return Status::OK(); } +// three qubit eigen -> Create(time, q0, q1, q2, exp, gs) +inline Status ThreeEigenGate( + const Operation& op, const SymbolMap& param_map, + const std::function& create_f, + const unsigned int num_qubits, const unsigned int time, + QsimCircuit* circuit, std::vector* metadata) { + unsigned int q0, q1, q2; + float exp, exp_s, gs; + bool unused; + Status u; + unused = absl::SimpleAtoi(op.qubits(0).id(), &q0); + unused = absl::SimpleAtoi(op.qubits(1).id(), &q1); + unused = absl::SimpleAtoi(op.qubits(2).id(), &q2); + + absl::optional exponent_symbol; + u = ParseProtoArg(op, "exponent", param_map, &exp, &exponent_symbol); + if (!u.ok()) { + return u; + } + u = ParseProtoArg(op, "exponent_scalar", param_map, &exp_s); + if (!u.ok()) { + return u; + } + u = ParseProtoArg(op, "global_shift", param_map, &gs); + if (!u.ok()) { + return u; + } + auto gate = create_f(time, num_qubits - q0 - 1, num_qubits - q1 - 1, + num_qubits - q2 - 1, exp * exp_s, gs); + + Status s = OptionalInsertControls(op, num_qubits, &gate); + if (!s.ok()) { + return s; + } + circuit->gates.push_back(gate); + + // check for symbols and track metadata if needed. + if (metadata != nullptr) { + GateMetaData info; + info.index = circuit->gates.size() - 1; + info.gate_params = {exp, exp_s, gs}; + info.create_f3 = create_f; + if (exponent_symbol.has_value()) { + info.symbol_values = {exponent_symbol.value()}; + info.placeholder_names = {GateParamNames::kExponent}; + } + metadata->push_back(info); + } + return Status::OK(); +} + Status IGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { @@ -365,6 +417,13 @@ Status CZGate(const Operation& op, const SymbolMap& param_map, num_qubits, time, circuit, metadata); } +Status CCZGate(const Operation& op, const SymbolMap& param_map, + const unsigned int num_qubits, const unsigned int time, + QsimCircuit* circuit, std::vector* metadata) { + return ThreeEigenGate(op, param_map, &qsim::Cirq::CCZPowGate::Create, + num_qubits, time, circuit, metadata); +} + Status CXGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { @@ -372,6 +431,13 @@ Status CXGate(const Operation& op, const SymbolMap& param_map, num_qubits, time, circuit, metadata); } +Status CCXGate(const Operation& op, const SymbolMap& param_map, + const unsigned int num_qubits, const unsigned int time, + QsimCircuit* circuit, std::vector* metadata) { + return ThreeEigenGate(op, param_map, &qsim::Cirq::CCXPowGate::Create, + num_qubits, time, circuit, metadata); +} + Status SwapGate(const Operation& op, const SymbolMap& param_map, const unsigned int num_qubits, const unsigned int time, QsimCircuit* circuit, std::vector* metadata) { @@ -579,6 +645,7 @@ tensorflow::Status ParseAppendGate(const Operation& op, {"ZP", &ZGate}, {"ZZP", &ZZGate}, {"CZP", &CZGate}, {"I2", &I2Gate}, {"CNP", &CXGate}, {"SP", &SwapGate}, + {"CCZP", &CCZGate}, {"CCXP", &CCXGate}, {"ISP", &ISwapGate}, {"PXP", &PhasedXGate}, {"FSIM", &FsimGate}, {"PISP", &PhasedISwapGate}}; diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim.h b/tensorflow_quantum/core/src/circuit_parser_qsim.h index 493dd3546..60a4d0055 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim.h +++ b/tensorflow_quantum/core/src/circuit_parser_qsim.h @@ -61,6 +61,11 @@ struct GateMetaData { std::function(unsigned int, unsigned int, unsigned int, float, float)> create_f2; + + // set only if gate is Three qubit Eigen gate. + std::function( + unsigned int, unsigned int, unsigned int, unsigned int, float, float)> + create_f3; }; // parse a serialized Cirq program into a qsim representation. diff --git a/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc b/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc index 84cd7bf28..87dd1372e 100644 --- a/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc +++ b/tensorflow_quantum/core/src/circuit_parser_qsim_test.cc @@ -65,6 +65,16 @@ inline void AssertControlEqual(const QsimGate& a, const QsimGate& b) { ASSERT_EQ(a.cmask, b.cmask); } +inline void AssertThreeQubitEqual(const QsimGate& a, const QsimGate& b) { + for (int i = 0; i < 128; i++) { + ASSERT_NEAR(a.matrix[i], b.matrix[i], 1e-5); + } + ASSERT_EQ(a.qubits[0], b.qubits[0]); + ASSERT_EQ(a.qubits[1], b.qubits[1]); + ASSERT_EQ(a.qubits[2], b.qubits[2]); + AssertControlEqual(a, b); +} + inline void AssertTwoQubitEqual(const QsimGate& a, const QsimGate& b) { for (int i = 0; i < 32; i++) { ASSERT_NEAR(a.matrix[i], b.matrix[i], 1e-5); @@ -82,6 +92,180 @@ inline void AssertOneQubitEqual(const QsimGate& a, const QsimGate& b) { AssertControlEqual(a, b); } +class ThreeQubitEigenFixture + : public ::testing::TestWithParam>> {}; + +TEST_P(ThreeQubitEigenFixture, ThreeEigenGate) { + float exp = 1.1234; + float gs = 2.2345; + + // Get gate name and reference qsim gate. + std::string name = std::get<0>(GetParam()); + auto ref_gate = std::get<1>(GetParam())(0, 2, 1, 0, exp, gs); + 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(); + + // Add gate. + Operation* operations_proto = moments_proto->add_operations(); + Gate* gate_proto = operations_proto->mutable_gate(); + gate_proto->set_id(name); + + // Set args. + google::protobuf::Map* args_proto = + operations_proto->mutable_args(); + (*args_proto)["global_shift"] = MakeArg(gs); + (*args_proto)["exponent"] = MakeArg("placeholder"); + (*args_proto)["exponent_scalar"] = MakeArg(0.5); + + // Set the control args. + (*args_proto)["control_qubits"] = MakeControlArg(""); + (*args_proto)["control_values"] = MakeControlArg(""); + + // Set the qubits. + Qubit* qubits_proto = operations_proto->add_qubits(); + qubits_proto->set_id("0"); + qubits_proto = operations_proto->add_qubits(); + qubits_proto->set_id("1"); + qubits_proto = operations_proto->add_qubits(); + qubits_proto->set_id("2"); + + QsimCircuit test_circuit; + std::vector> fused_circuit; + SymbolMap symbol_map = {{"placeholder", std::pair(1, 2 * exp)}}; + std::vector metadata; + + // Test case where we have a placeholder. + ASSERT_EQ(QsimCircuitFromProgram(program_proto, symbol_map, 3, &test_circuit, + &fused_circuit, &metadata), + tensorflow::Status::OK()); + + AssertThreeQubitEqual(test_circuit.gates[0], ref_gate); + EXPECT_EQ(metadata[0].index, 0); + EXPECT_EQ(metadata[0].symbol_values[0], "placeholder"); + EXPECT_EQ(metadata[0].placeholder_names[0], GateParamNames::kExponent); + EXPECT_NEAR(metadata[0].gate_params[0], 2 * exp, 1e-5); + EXPECT_NEAR(metadata[0].gate_params[1], 0.5, 1e-5); + EXPECT_NEAR(metadata[0].gate_params[2], gs, 1e-5); + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(metadata[0].gate_params.size(), 3); + EXPECT_EQ(metadata[0].symbol_values.size(), 1); + EXPECT_EQ(metadata[0].placeholder_names.size(), 1); + + test_circuit.gates.clear(); + fused_circuit.clear(); + metadata.clear(); + (*args_proto)["exponent"] = MakeArg(exp); + (*args_proto)["exponent_scalar"] = MakeArg(1.0); + symbol_map.clear(); + + // Test case where we have all float values. + ASSERT_EQ(QsimCircuitFromProgram(program_proto, symbol_map, 3, &test_circuit, + &fused_circuit, &metadata), + tensorflow::Status::OK()); + AssertThreeQubitEqual(test_circuit.gates[0], ref_gate); + EXPECT_EQ(metadata[0].index, 0); + EXPECT_NEAR(metadata[0].gate_params[0], exp, 1e-5); + EXPECT_NEAR(metadata[0].gate_params[1], 1.0, 1e-5); + EXPECT_NEAR(metadata[0].gate_params[2], gs, 1e-5); + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(metadata[0].gate_params.size(), 3); + EXPECT_EQ(metadata[0].symbol_values.size(), 0); + EXPECT_EQ(metadata[0].placeholder_names.size(), 0); + + test_circuit.gates.clear(); + fused_circuit.clear(); + (*args_proto).erase(args_proto->find("exponent")); + symbol_map.clear(); + + // Test case where proto arg missing. + ASSERT_EQ(QsimCircuitFromProgram(program_proto, symbol_map, 3, &test_circuit, + &fused_circuit), + tensorflow::Status(tensorflow::error::INVALID_ARGUMENT, + "Could not find arg: exponent in op.")); + + test_circuit.gates.clear(); + fused_circuit.clear(); + (*args_proto)["exponent"] = MakeArg("alpha"); + symbol_map.clear(); + + // Test case where symbol value not present in resolver. + ASSERT_EQ( + QsimCircuitFromProgram(program_proto, symbol_map, 3, &test_circuit, + &fused_circuit), + tensorflow::Status(tensorflow::error::INVALID_ARGUMENT, + "Could not find symbol in parameter map: alpha")); +} + +TEST_P(ThreeQubitEigenFixture, ThreeEigenGateControlled) { + float exp = 1.1234; + float gs = 2.2345; + + // Get gate name and reference qsim gate. + std::string name = std::get<0>(GetParam()); + auto ref_gate = + std::get<1>(GetParam())(0, 2, 1, 0, exp, gs).ControlledBy({3, 4}, {0, 0}); + 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(); + + // Add gate. + Operation* operations_proto = moments_proto->add_operations(); + Gate* gate_proto = operations_proto->mutable_gate(); + gate_proto->set_id(name); + + // Set args. + google::protobuf::Map* args_proto = + operations_proto->mutable_args(); + (*args_proto)["global_shift"] = MakeArg(gs); + (*args_proto)["exponent"] = MakeArg("placeholder"); + (*args_proto)["exponent_scalar"] = MakeArg(0.5); + + // Set the control args. + (*args_proto)["control_qubits"] = MakeControlArg("1,0"); + (*args_proto)["control_values"] = MakeControlArg("0,0"); + + // Set the qubits. + Qubit* qubits_proto = operations_proto->add_qubits(); + qubits_proto->set_id("2"); + qubits_proto = operations_proto->add_qubits(); + qubits_proto->set_id("3"); + qubits_proto = operations_proto->add_qubits(); + qubits_proto->set_id("4"); + + QsimCircuit test_circuit; + std::vector> fused_circuit; + SymbolMap symbol_map = {{"placeholder", std::pair(1, 2 * exp)}}; + std::vector metadata; + + // Test case where we have a placeholder. + ASSERT_EQ(QsimCircuitFromProgram(program_proto, symbol_map, 5, &test_circuit, + &fused_circuit, &metadata), + tensorflow::Status::OK()); + AssertThreeQubitEqual(test_circuit.gates[0], ref_gate); + EXPECT_EQ(metadata[0].index, 0); + EXPECT_EQ(metadata[0].symbol_values[0], "placeholder"); + EXPECT_EQ(metadata[0].placeholder_names[0], GateParamNames::kExponent); + EXPECT_NEAR(metadata[0].gate_params[0], 2 * exp, 1e-5); + EXPECT_NEAR(metadata[0].gate_params[1], 0.5, 1e-5); + EXPECT_NEAR(metadata[0].gate_params[2], gs, 1e-5); + EXPECT_EQ(metadata.size(), 1); + EXPECT_EQ(metadata[0].gate_params.size(), 3); + EXPECT_EQ(metadata[0].symbol_values.size(), 1); + EXPECT_EQ(metadata[0].placeholder_names.size(), 1); +} + +INSTANTIATE_TEST_CASE_P( + ThreeQubitEigenTests, ThreeQubitEigenFixture, + ::testing::Values( + std::make_tuple("CCXP", &qsim::Cirq::CCXPowGate::Create), + std::make_tuple("CCZP", &qsim::Cirq::CCZPowGate::Create))); + class TwoQubitEigenFixture : public ::testing::TestWithParam