Skip to content

Commit

Permalink
Added NoisyQsimCircuit parsing in C++. (tensorflow#502)
Browse files Browse the repository at this point in the history
Co-authored-by: Jae H. Yoo <[email protected]>
  • Loading branch information
MichaelBroughton and jaeyoo authored Mar 10, 2021
1 parent 0737959 commit ef3a343
Show file tree
Hide file tree
Showing 5 changed files with 230 additions and 3 deletions.
6 changes: 3 additions & 3 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,9 @@ http_archive(

http_archive(
name = "qsim",
sha256 = "14659ff8c0a82058ca010e8276aa38d1841389828a821cab71e8ed6027206038",
strip_prefix = "qsim-0.9.0",
urls = ["https://github.com/quantumlib/qsim/archive/v0.9.0.zip"],
sha256 = "06c330960edf95d495c3686be9006fa11a5f87c8294d6ef4a2ad0a01660f2e49",
strip_prefix = "qsim-0.9.1",
urls = ["https://github.com/quantumlib/qsim/archive/v0.9.1.zip"],
)

# Added for crosstool in tensorflow.
Expand Down
3 changes: 3 additions & 0 deletions tensorflow_quantum/core/src/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ cc_library(
"@com_google_absl//absl/container:flat_hash_map",
"@local_config_tf//:libtensorflow_framework",
"@local_config_tf//:tf_header_lib",
"@qsim//lib:channel",
"@qsim//lib:channels_cirq",
"@qsim//lib:circuit",
"@qsim//lib:circuit_noisy",
"@qsim//lib:gates_cirq",
"@qsim//lib:fuser",
"@qsim//lib:fuser_basic",
Expand Down
78 changes: 78 additions & 0 deletions tensorflow_quantum/core/src/circuit_parser_qsim.cc
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@ limitations under the License.
#include <string>
#include <vector>

#include "../qsim/lib/channel.h"
#include "../qsim/lib/channels_cirq.h"
#include "../qsim/lib/circuit.h"
#include "../qsim/lib/circuit_noisy.h"
#include "../qsim/lib/fuser.h"
#include "../qsim/lib/fuser_basic.h"
#include "../qsim/lib/gates_cirq.h"
Expand All @@ -44,6 +47,7 @@ namespace {
typedef absl::flat_hash_map<std::string, std::pair<int, float>> SymbolMap;
typedef qsim::Cirq::GateCirq<float> QsimGate;
typedef qsim::Circuit<QsimGate> QsimCircuit;
typedef qsim::NoisyCircuit<QsimGate> NoisyQsimCircuit;

inline Status ParseProtoArg(
const Operation& op, const std::string& arg_name,
Expand Down Expand Up @@ -592,8 +596,82 @@ tensorflow::Status ParseAppendGate(const Operation& op,
return build_f->second(op, param_map, num_qubits, time, circuit, metadata);
}

inline Status DepolarizingChannel(const Operation& op,
const unsigned int num_qubits,
const unsigned int time,
NoisyQsimCircuit* ncircuit) {
int q;
bool unused;
float p;
Status u;
unused = absl::SimpleAtoi(op.qubits(0).id(), &q);

u = ParseProtoArg(op, "p", {}, &p);
if (!u.ok()) {
return u;
}
auto chan = qsim::Cirq::DepolarizingChannel<float>::Create(
time, num_qubits - q - 1, p);
ncircuit->push_back(chan);
return Status::OK();
}

tensorflow::Status ParseAppendChannel(const Operation& op,
const unsigned int num_qubits,
const unsigned int time,
NoisyQsimCircuit* ncircuit) {
// map channel name -> callable to build qsim channel from operation proto.
static const absl::flat_hash_map<
std::string, std::function<Status(const Operation&, const unsigned int,
const unsigned int, NoisyQsimCircuit*)>>
chan_func_map = {{"DP", &DepolarizingChannel}};

auto build_f = chan_func_map.find(op.gate().id());
if (build_f == chan_func_map.end()) {
return Status(tensorflow::error::INVALID_ARGUMENT,
absl::StrCat("Could not parse channel id: ", op.gate().id()));
}
return build_f->second(op, num_qubits, time, ncircuit);
}

} // namespace

tensorflow::Status NoisyQsimCircuitFromProgram(const Program& program,
const SymbolMap& param_map,
const int num_qubits,
NoisyQsimCircuit* ncircuit) {
// Special case empty.
if (num_qubits <= 0) {
return Status::OK();
}
int time = 0;
QsimCircuit placeholder;
placeholder.gates.reserve(2);

for (const Moment& moment : program.circuit().moments()) {
for (const Operation& op : moment.operations()) {
placeholder.gates.clear();
Status status = ParseAppendGate(op, param_map, num_qubits, time,
&placeholder, nullptr);
if (!status.ok()) {
// if failed to append gate, try appending channel.
status = ParseAppendChannel(op, num_qubits, time, ncircuit);
} else {
// succeeded in appending gate, convert to channel.
ncircuit->push_back(
std::move(qsim::MakeChannelFromGate(time, placeholder.gates[0])));
}

if (!status.ok()) {
return status;
}
}
time++;
}

return Status::OK();
}

tensorflow::Status QsimCircuitFromProgram(
const Program& program, const SymbolMap& param_map, const int num_qubits,
QsimCircuit* circuit, std::vector<qsim::GateFused<QsimGate>>* fused_circuit,
Expand Down
11 changes: 11 additions & 0 deletions tensorflow_quantum/core/src/circuit_parser_qsim.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ limitations under the License.
#include <vector>

#include "../qsim/lib/circuit.h"
#include "../qsim/lib/circuit_noisy.h"
#include "../qsim/lib/fuser.h"
#include "../qsim/lib/gates_cirq.h"
#include "absl/container/flat_hash_map.h"
Expand Down Expand Up @@ -73,6 +74,16 @@ tensorflow::Status QsimCircuitFromProgram(
std::vector<qsim::GateFused<qsim::Cirq::GateCirq<float>>>* fused_circuit,
std::vector<GateMetaData>* metdata = nullptr);

// parse a serialized Cirq program into a qsim representation.
// ingests a Cirq Circuit proto and produces a resolved Noisy qsim Circuit,
// Note: no metadata or fused circuits are produced as the qsim api for
// noisy simulation appears to take care of a lot of this for us.
tensorflow::Status NoisyQsimCircuitFromProgram(
const cirq::google::api::v2::Program& program,
const absl::flat_hash_map<std::string, std::pair<int, float>>& param_map,
const int num_qubits,
qsim::NoisyCircuit<qsim::Cirq::GateCirq<float>>* ncircuit);

// parse a serialized pauliTerm from a larger cirq.Paulisum proto
// into a qsim Circuit and fused circuit.
tensorflow::Status QsimCircuitFromPauliTerm(
Expand Down
135 changes: 135 additions & 0 deletions tensorflow_quantum/core/src/circuit_parser_qsim_test.cc
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ limitations under the License.

#include <string>

#include "../qsim/lib/channel.h"
#include "../qsim/lib/channels_cirq.h"
#include "../qsim/lib/circuit.h"
#include "../qsim/lib/circuit_noisy.h"
#include "../qsim/lib/gates_cirq.h"
#include "absl/container/flat_hash_map.h"
#include "absl/strings/numbers.h"
Expand All @@ -29,8 +32,10 @@ namespace tfq {
namespace {

typedef absl::flat_hash_map<std::string, std::pair<int, float>> SymbolMap;
typedef qsim::Cirq::Channel<float> QsimChannel;
typedef qsim::Cirq::GateCirq<float> QsimGate;
typedef qsim::Circuit<QsimGate> QsimCircuit;
typedef qsim::NoisyCircuit<QsimGate> NoisyQsimCircuit;

using ::cirq::google::api::v2::Arg;
using ::cirq::google::api::v2::Circuit;
Expand Down Expand Up @@ -82,6 +87,21 @@ inline void AssertOneQubitEqual(const QsimGate& a, const QsimGate& b) {
AssertControlEqual(a, b);
}

inline void AssertChannelEqual(const QsimChannel& a, const QsimChannel& b) {
ASSERT_EQ(a.size(), b.size());
for (int i = 0; i < a.size(); i++) {
ASSERT_EQ(a[i].kind, b[i].kind);
ASSERT_EQ(a[i].unitary, b[i].unitary);
ASSERT_NEAR(a[i].prob, b[i].prob, 1e-5);
auto a_k_ops = a[i].ops;
auto b_k_ops = b[i].ops;
EXPECT_EQ(a_k_ops.size(), b_k_ops.size());
for (int j = 0; j < a_k_ops.size(); j++) {
AssertOneQubitEqual(a_k_ops[j], b_k_ops[j]);
}
}
}

class TwoQubitEigenFixture
: public ::testing::TestWithParam<std::tuple<
std::string, std::function<QsimGate(unsigned int, unsigned int,
Expand Down Expand Up @@ -1160,6 +1180,121 @@ TEST(QsimCircuitParserTest, EmptyTest) {
ASSERT_EQ(metadata.size(), 0);
}

TEST(QsimCircuitParserTest, CompoundCircuit) {
float p = 0.1234;
auto ref_chan = qsim::Cirq::DepolarizingChannel<float>::Create(0, 0, p);
auto ref_gate = qsim::Cirq::I1<float>::Create(0, 1);
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 channel.
Operation* operations_proto = moments_proto->add_operations();
Gate* gate_proto = operations_proto->mutable_gate();
gate_proto->set_id("DP");

// Set the args.
google::protobuf::Map<std::string, Arg>* args_proto =
operations_proto->mutable_args();
(*args_proto)["p"] = MakeArg(p);

// 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("1");

// Add gate.
operations_proto = moments_proto->add_operations();
gate_proto = operations_proto->mutable_gate();
gate_proto->set_id("I");

// Set the args.
args_proto = operations_proto->mutable_args();

// Set the control args.
(*args_proto)["control_qubits"] = MakeControlArg("");
(*args_proto)["control_values"] = MakeControlArg("");

// Set the qubits.
qubits_proto = operations_proto->add_qubits();
qubits_proto->set_id("0");

NoisyQsimCircuit test_circuit;

ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 2, &test_circuit),
tensorflow::Status::OK());
AssertChannelEqual(test_circuit[0], ref_chan);
AssertOneQubitEqual(test_circuit[1][0].ops[0], ref_gate);
ASSERT_EQ(test_circuit.size(), 2);
}

TEST(QsimCircuitParserTest, Depolarizing) {
float p = 0.1234;
auto reference = qsim::Cirq::DepolarizingChannel<float>::Create(0, 0, p);
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 channel.
Operation* operations_proto = moments_proto->add_operations();
Gate* gate_proto = operations_proto->mutable_gate();
gate_proto->set_id("DP");

// Set the args.
google::protobuf::Map<std::string, Arg>* args_proto =
operations_proto->mutable_args();
(*args_proto)["p"] = MakeArg(p);

// 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");

NoisyQsimCircuit test_circuit;

ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 1, &test_circuit),
tensorflow::Status::OK());
AssertChannelEqual(test_circuit[0], reference);
ASSERT_EQ(test_circuit.size(), 1);
}

TEST(QsimCircuitParserTest, NoisyEmpty) {
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();

NoisyQsimCircuit test_circuit;
ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 0, &test_circuit),
tensorflow::Status::OK());
ASSERT_EQ(test_circuit.size(), 0);
}

TEST(QsimCircuitParserTest, NoisyBadProto) {
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 channel.
Operation* operations_proto = moments_proto->add_operations();
Gate* gate_proto = operations_proto->mutable_gate();
gate_proto->set_id("ABCDEFG");

NoisyQsimCircuit test_circuit;
ASSERT_EQ(NoisyQsimCircuitFromProgram(program_proto, {}, 1, &test_circuit),
tensorflow::Status(tensorflow::error::INVALID_ARGUMENT,
"Could not parse channel id: ABCDEFG"));
}

TEST(QsimCircuitParserTest, CircuitFromPauliTermPauli) {
tfq::proto::PauliTerm pauli_proto;
// The created circuit should not depend on the coefficient
Expand Down

0 comments on commit ef3a343

Please sign in to comment.