Skip to content

Commit

Permalink
Adjoint 3/n: Adding parsing functionality for adjoint needs.
Browse files Browse the repository at this point in the history
MichaelBroughton committed Aug 24, 2020
1 parent c41fa9f commit 3945cc7
Showing 4 changed files with 1,030 additions and 0 deletions.
28 changes: 28 additions & 0 deletions tensorflow_quantum/core/src/BUILD
Original file line number Diff line number Diff line change
@@ -5,6 +5,34 @@ licenses(["notice"])
# Export for the PIP package.
exports_files(["__init__.py"])

cc_library(
name = "adj_util",
srcs = ["adj_util.cc"],
hdrs = ["adj_util.h"],
deps = [
"@qsim//lib:circuit",
"@qsim//lib:gates_cirq",
"@qsim//lib:gate",
"@qsim//lib:fuser",
"@qsim//lib:fuser_basic",
"@qsim//lib:io",
"@qsim//lib:matrix",
":circuit_parser_qsim",
],
)

cc_test(
name = "adj_util_test",
srcs = ["adj_util_test.cc"],
deps = [
":adj_util",
"@qsim//lib:gates_cirq",
"@qsim//lib:matrix",
"@com_google_googletest//:gtest_main",
":circuit_parser_qsim",
]
)

cc_library(
name = "circuit",
srcs = ["circuit.cc"],
291 changes: 291 additions & 0 deletions tensorflow_quantum/core/src/adj_util.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,291 @@
/* Copyright 2020 The TensorFlow Quantum Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow_quantum/core/src/adj_util.h"

#include <functional>
#include <string>
#include <vector>

#include "../qsim/lib/circuit.h"
#include "../qsim/lib/fuser.h"
#include "../qsim/lib/fuser_basic.h"
#include "../qsim/lib/gate.h"
#include "../qsim/lib/gates_cirq.h"
#include "../qsim/lib/io.h"
#include "../qsim/lib/matrix.h"
#include "tensorflow_quantum/core/src/circuit_parser_qsim.h"

namespace tfq {

static const float _GRAD_EPS = 5e-3;

typedef qsim::Cirq::GateCirq<float> QsimGate;
typedef qsim::Circuit<QsimGate> QsimCircuit;

void CreateGradientCircuit(
const QsimCircuit& circuit, const std::vector<GateMetaData>& metadata,
std::vector<std::vector<qsim::GateFused<QsimGate>>>* partial_fuses,
std::vector<GradientOfGate>* grad_gates) {
for (int i = 0; i < metadata.size(); i++) {
if (metadata[i].symbol_values.size() == 0) {
continue;
}
// found a gate that was constructed with symbols.
GradientOfGate grad;

// Single qubit Eigen.
if (circuit.gates[i].kind == qsim::Cirq::GateKind::kXPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kYPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kZPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kHPowGate) {
PopulateGradientSingleEigen(
metadata[i].create_f1, metadata[i].symbol_values[0], i,
circuit.gates[i].qubits[0], metadata[i].gate_params[0],
metadata[i].gate_params[1], metadata[i].gate_params[2], &grad);
grad_gates->push_back(grad);
}

// Two qubit Eigen.
else if (circuit.gates[i].kind == qsim::Cirq::GateKind::kCZPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kCXPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kXXPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kYYPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kZZPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kISwapPowGate ||
circuit.gates[i].kind == qsim::Cirq::GateKind::kSwapPowGate) {
PopulateGradientTwoEigen(
metadata[i].create_f2, metadata[i].symbol_values[0], i,
circuit.gates[i].qubits[0], circuit.gates[i].qubits[1],
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.
for (int j = 0; j < metadata[i].symbol_values.size(); j++) {
if (metadata[i].placeholder_names[j] ==
GateParamNames::kPhaseExponent) {
PopulateGradientPhasedXPhasedExponent(
metadata[i].symbol_values[j], i, circuit.gates[i].qubits[0],
metadata[i].gate_params[0], metadata[i].gate_params[1],
metadata[i].gate_params[2], metadata[i].gate_params[3],
metadata[i].gate_params[4], &grad);
} else if (metadata[i].placeholder_names[j] ==
GateParamNames::kExponent) {
PopulateGradientPhasedXExponent(
metadata[i].symbol_values[j], i, circuit.gates[i].qubits[0],
metadata[i].gate_params[0], metadata[i].gate_params[1],
metadata[i].gate_params[2], metadata[i].gate_params[3],
metadata[i].gate_params[4], &grad);
}
}
grad_gates->push_back(grad);
}

// Fsim
else if (circuit.gates[i].kind == qsim::Cirq::GateKind::kFSimGate) {
// Process potentially several symbols.
for (int j = 0; j < metadata[i].symbol_values.size(); j++) {
if (metadata[i].placeholder_names[j] == GateParamNames::kTheta) {
PopulateGradientFsimTheta(
metadata[i].symbol_values[j], i, circuit.gates[i].qubits[0],
circuit.gates[i].qubits[1], metadata[i].gate_params[0],
metadata[i].gate_params[1], metadata[i].gate_params[2],
metadata[i].gate_params[3], &grad);
} else if (metadata[i].placeholder_names[j] == GateParamNames::kPhi) {
PopulateGradientFsimPhi(
metadata[i].symbol_values[j], i, circuit.gates[i].qubits[0],
circuit.gates[i].qubits[1], metadata[i].gate_params[0],
metadata[i].gate_params[1], metadata[i].gate_params[2],
metadata[i].gate_params[3], &grad);
}
}
grad_gates->push_back(grad);
}

// PhasedISwap
else if (circuit.gates[i].kind ==
qsim::Cirq::GateKind::kPhasedISwapPowGate) {
// Process potentially several symbols.
for (int j = 0; j < metadata[i].symbol_values.size(); j++) {
if (metadata[i].placeholder_names[j] ==
GateParamNames::kPhaseExponent) {
PopulateGradientPhasedISwapPhasedExponent(
metadata[i].symbol_values[j], i, circuit.gates[i].qubits[0],
circuit.gates[i].qubits[1], metadata[i].gate_params[0],
metadata[i].gate_params[1], metadata[i].gate_params[2],
metadata[i].gate_params[3], &grad);
} else if (metadata[i].placeholder_names[j] ==
GateParamNames::kExponent) {
PopulateGradientPhasedISwapExponent(
metadata[i].symbol_values[j], i, circuit.gates[i].qubits[0],
circuit.gates[i].qubits[1], metadata[i].gate_params[0],
metadata[i].gate_params[1], metadata[i].gate_params[2],
metadata[i].gate_params[3], &grad);
}
}
grad_gates->push_back(grad);
}
}

// Produce partial fuses around the gradient gates.
auto fuser = qsim::BasicGateFuser<qsim::IO, QsimGate>();
auto left = circuit.gates.begin();
auto right = left;

partial_fuses->assign(grad_gates->size() + 1,
std::vector<qsim::GateFused<QsimGate>>({}));
for (int i = 0; i < grad_gates->size(); i++) {
right = circuit.gates.begin() + (*grad_gates)[i].index;
(*partial_fuses)[i] = fuser.FuseGates(circuit.num_qubits, left, right);
left = right + 1;
}
right = circuit.gates.end();
(*partial_fuses)[grad_gates->size()] =
fuser.FuseGates(circuit.num_qubits, left, right);
}

void PopulateGradientSingleEigen(
const std::function<QsimGate(unsigned int, unsigned int, float, float)>&
create_f,
const std::string& symbol, unsigned int location, unsigned int qid,
float exp, float exp_s, float gs, GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = create_f(0, qid, (exp + _GRAD_EPS) * exp_s, gs);
auto right = create_f(0, qid, (exp - _GRAD_EPS) * exp_s, gs);
Matrix2Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix2ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

void PopulateGradientTwoEigen(
const std::function<QsimGate(unsigned int, unsigned int, unsigned int,
float, float)>& create_f,
const std::string& symbol, unsigned int location, unsigned int qid,
unsigned int qid2, float exp, float exp_s, float gs, GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = create_f(0, qid, qid2, (exp + _GRAD_EPS) * exp_s, gs);
auto right = create_f(0, qid, qid2, (exp - _GRAD_EPS) * exp_s, gs);
Matrix4Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix4ScalarMultiply(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,
float pexp_s, float exp, float exp_s,
float gs, GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = qsim::Cirq::PhasedXPowGate<float>::Create(
0, qid, (pexp + _GRAD_EPS) * pexp_s, exp * exp_s, gs);
auto right = qsim::Cirq::PhasedXPowGate<float>::Create(
0, qid, (pexp - _GRAD_EPS) * pexp_s, exp * exp_s, gs);
Matrix2Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix2ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

void PopulateGradientPhasedXExponent(const std::string& symbol,
unsigned int location, unsigned int qid,
float pexp, float pexp_s, float exp,
float exp_s, float gs,
GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = qsim::Cirq::PhasedXPowGate<float>::Create(
0, qid, pexp * pexp_s, (exp + _GRAD_EPS) * exp_s, gs);
auto right = qsim::Cirq::PhasedXPowGate<float>::Create(
0, qid, pexp * pexp_s, (exp - _GRAD_EPS) * exp_s, gs);
Matrix2Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix2ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

void PopulateGradientFsimTheta(const std::string& symbol, unsigned int location,
unsigned int qid, unsigned qid2, float theta,
float theta_s, float phi, float phi_s,
GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = qsim::Cirq::FSimGate<float>::Create(
0, qid, qid2, (theta + _GRAD_EPS) * theta_s, phi * phi_s);
auto right = qsim::Cirq::FSimGate<float>::Create(
0, qid, qid2, (theta - _GRAD_EPS) * theta_s, phi * phi_s);
Matrix4Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix4ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

void PopulateGradientFsimPhi(const std::string& symbol, unsigned int location,
unsigned int qid, unsigned qid2, float theta,
float theta_s, float phi, float phi_s,
GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = qsim::Cirq::FSimGate<float>::Create(0, qid, qid2, theta * theta_s,
(phi + _GRAD_EPS) * phi_s);
auto right = qsim::Cirq::FSimGate<float>::Create(
0, qid, qid2, theta * theta_s, (phi - _GRAD_EPS) * phi_s);
Matrix4Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix4ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

void PopulateGradientPhasedISwapPhasedExponent(
const std::string& symbol, unsigned int location, unsigned int qid,
unsigned int qid2, float pexp, float pexp_s, float exp, float exp_s,
GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = qsim::Cirq::PhasedISwapPowGate<float>::Create(
0, qid, qid2, (pexp + _GRAD_EPS) * pexp_s, exp * exp_s);
auto right = qsim::Cirq::PhasedISwapPowGate<float>::Create(
0, qid, qid2, (pexp - _GRAD_EPS) * pexp_s, exp * exp_s);
Matrix4Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix4ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

void PopulateGradientPhasedISwapExponent(const std::string& symbol,
unsigned int location,
unsigned int qid, unsigned int qid2,
float pexp, float pexp_s, float exp,
float exp_s, GradientOfGate* grad) {
grad->params.push_back(symbol);
grad->index = location;
auto left = qsim::Cirq::PhasedISwapPowGate<float>::Create(
0, qid, qid2, pexp * pexp_s, (exp + _GRAD_EPS) * exp_s);
auto right = qsim::Cirq::PhasedISwapPowGate<float>::Create(
0, qid, qid2, pexp * pexp_s, (exp - _GRAD_EPS) * exp_s);
Matrix4Diff(right.matrix,
left.matrix); // left's entries have right subtracted.
qsim::Matrix4ScalarMultiply(0.5 / _GRAD_EPS, left.matrix);
grad->grad_gates.push_back(left);
}

} // namespace tfq
121 changes: 121 additions & 0 deletions tensorflow_quantum/core/src/adj_util.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/* Copyright 2020 The TensorFlow Quantum Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/

#ifndef TFQ_CORE_SRC_ADJ_UTIL_H_
#define TFQ_CORE_SRC_ADJ_UTIL_H_

#include <functional>
#include <string>
#include <vector>

#include "../qsim/lib/circuit.h"
#include "../qsim/lib/fuser.h"
#include "../qsim/lib/fuser_basic.h"
#include "../qsim/lib/gates_cirq.h"
#include "../qsim/lib/io.h"
#include "../qsim/lib/matrix.h"
#include "tensorflow_quantum/core/src/circuit_parser_qsim.h"

namespace tfq {

struct GradientOfGate {
// name of parameters used by gate.
std::vector<std::string> params;

// index of gate in original circuit.
// Not if multiple calls to Populate* are used
// on the same object ensure that the index is
// the same for all of them!
int index;

// Gates for gradients. Has a 1:1 mapping with params.
std::vector<qsim::Cirq::GateCirq<float>> grad_gates;
};

// Computes all gates who's gradient will need to be taken, in addition
// fuses all gates around those gates for faster circuit execution.
void CreateGradientCircuit(
const qsim::Circuit<qsim::Cirq::GateCirq<float>>& circuit,
const std::vector<GateMetaData>& metadata,
std::vector<std::vector<qsim::GateFused<qsim::Cirq::GateCirq<float>>>>*
partial_fuses,
std::vector<GradientOfGate>* grad_gates);

void PopulateGradientSingleEigen(
const std::function<qsim::Cirq::GateCirq<float>(unsigned int, unsigned int,
float, float)>& create_f,
const std::string& symbol, unsigned int location, unsigned int qid,
float exp, float exp_s, float gs, GradientOfGate* grad);

void PopulateGradientTwoEigen(
const std::function<qsim::Cirq::GateCirq<float>(
unsigned int, unsigned int, unsigned int, float, float)>& create_f,
const std::string& symbol, unsigned int location, unsigned int qid,
unsigned int qid2, 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,
unsigned int location,
unsigned int qid, float pexp,
float pexp_s, float exp, float exp_s,
float gs, GradientOfGate* grad);

void PopulateGradientPhasedXExponent(const std::string& symbol,
unsigned int location, unsigned int qid,
float pexp, float pexp_s, float exp,
float exp_s, float gs,
GradientOfGate* grad);

void PopulateGradientFsimTheta(const std::string& symbol, unsigned int location,
unsigned int qid, unsigned qid2, float theta,
float theta_s, float phi, float phi_s,
GradientOfGate* grad);

void PopulateGradientFsimPhi(const std::string& symbol, unsigned int location,
unsigned int qid, unsigned qid2, float theta,
float theta_s, float phi, float phi_s,
GradientOfGate* grad);

void PopulateGradientPhasedISwapPhasedExponent(
const std::string& symbol, unsigned int location, unsigned int qid,
unsigned int qid2, float pexp, float pexp_s, float exp, float exp_s,
GradientOfGate* grad);

void PopulateGradientPhasedISwapExponent(const std::string& symbol,
unsigned int location,
unsigned int qid, unsigned int qid2,
float pexp, float pexp_s, float exp,
float exp_s, GradientOfGate* grad);

// does matrix elementiwse subtraction dest -= source.
template <typename Array2>
void Matrix2Diff(Array2& source, Array2& dest) {
for (unsigned i = 0; i < 8; i++) {
dest[i] -= source[i];
}
}

// does matrix elementiwse subtraction dest -= source.
template <typename Array2>
void Matrix4Diff(Array2& source, Array2& dest) {
for (unsigned i = 0; i < 32; i++) {
dest[i] -= source[i];
}
}

} // namespace tfq

#endif // TFQ_CORE_SRC_ADJ_UTIL_H_
590 changes: 590 additions & 0 deletions tensorflow_quantum/core/src/adj_util_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,590 @@
/* Copyright 2020 The TensorFlow Quantum Authors. All Rights Reserved.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
==============================================================================*/
#include "tensorflow_quantum/core/src/adj_util.h"

#include <string>
#include <vector>

#include "../qsim/lib/circuit.h"
#include "../qsim/lib/fuser.h"
#include "../qsim/lib/fuser_basic.h"
#include "../qsim/lib/gates_cirq.h"
#include "../qsim/lib/io.h"
#include "../qsim/lib/matrix.h"
#include "gtest/gtest.h"

namespace tfq {
namespace {

void Matrix2Equal(const std::vector<float>& v,
const std::vector<float>& expected, float eps) {
for (int i = 0; i < 8; i++) {
EXPECT_NEAR(v[i], expected[i], eps);
}
}

void Matrix4Equal(const std::vector<float>& v,
const std::vector<float>& expected, float eps) {
for (int i = 0; i < 32; i++) {
EXPECT_NEAR(v[i], expected[i], eps);
}
}

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

class OneQubitEigenFixture
: public ::testing::TestWithParam<
std::function<QsimGate(unsigned int, unsigned int, float, float)>> {};

TEST_P(OneQubitEigenFixture, CreateGradientSingleEigen) {
QsimCircuit circuit;
std::vector<GateMetaData> metadata;
std::vector<std::vector<qsim::GateFused<QsimGate>>> fuses;
std::vector<GradientOfGate> grad_gates;

// Create a symbolized gate.
std::function<QsimGate(unsigned int, unsigned int, float, float)> given_f =
GetParam();

circuit.num_qubits = 2;
circuit.gates.push_back(given_f(0, 1, 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_f1 = 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;
PopulateGradientSingleEigen(given_f, "TheSymbol", 0, 1, 1.0, 1.0, 2.0, &tmp);

Matrix2Equal(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);
}

INSTANTIATE_TEST_CASE_P(
OneQubitEigenTests, OneQubitEigenFixture,
::testing::Values(&qsim::Cirq::XPowGate<float>::Create,
&qsim::Cirq::YPowGate<float>::Create,
&qsim::Cirq::ZPowGate<float>::Create,
&qsim::Cirq::HPowGate<float>::Create));

class TwoQubitEigenFixture
: public ::testing::TestWithParam<std::function<QsimGate(
unsigned int, unsigned int, unsigned int, float, float)>> {};

TEST_P(TwoQubitEigenFixture, CreateGradientTwoEigen) {
QsimCircuit circuit;
std::vector<GateMetaData> metadata;
std::vector<std::vector<qsim::GateFused<QsimGate>>> fuses;
std::vector<GradientOfGate> grad_gates;

// Create a symbolized gate.
std::function<QsimGate(unsigned int, unsigned int, unsigned int, float,
float)>
given_f = GetParam();

circuit.num_qubits = 2;
circuit.gates.push_back(given_f(0, 0, 1, 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_f2 = 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;
PopulateGradientTwoEigen(given_f, "TheSymbol", 0, 0, 1, 1.0, 1.0, 2.0, &tmp);

Matrix4Equal(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(
TwoQubitEigenTests, TwoQubitEigenFixture,
::testing::Values(&qsim::Cirq::CZPowGate<float>::Create,
&qsim::Cirq::CXPowGate<float>::Create,
&qsim::Cirq::XXPowGate<float>::Create,
&qsim::Cirq::YYPowGate<float>::Create,
&qsim::Cirq::ZZPowGate<float>::Create,
&qsim::Cirq::ISwapPowGate<float>::Create,
&qsim::Cirq::SwapPowGate<float>::Create));

TEST(AdjUtilTest, CreateGradientPhasedX) {
QsimCircuit circuit;
std::vector<GateMetaData> metadata;
std::vector<std::vector<qsim::GateFused<QsimGate>>> fuses;
std::vector<GradientOfGate> grad_gates;

// Create a symbolized gate.
circuit.num_qubits = 2;
circuit.gates.push_back(
qsim::Cirq::PhasedXPowGate<float>::Create(0, 0, 1.0, 2.0, 3.0));
GateMetaData meta;
meta.index = 0;
meta.symbol_values.push_back("TheSymbol");
meta.placeholder_names.push_back(GateParamNames::kPhaseExponent);
meta.symbol_values.push_back("TheSymbol2");
meta.placeholder_names.push_back(GateParamNames::kExponent);
meta.gate_params = {1.0, 1.0, 2.0, 1.0, 3.0};
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(), 2);
EXPECT_EQ(grad_gates[0].params[0], "TheSymbol");
EXPECT_EQ(grad_gates[0].params[1], "TheSymbol2");

// 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;
PopulateGradientPhasedXPhasedExponent("TheSymbol", 0, 0, 1.0, 1.0, 2.0, 1.0,
3.0, &tmp);

Matrix2Equal(tmp.grad_gates[0].matrix, grad_gates[0].grad_gates[0].matrix,
1e-4);

GradientOfGate tmp2;
PopulateGradientPhasedXExponent("TheSymbol2", 0, 0, 1.0, 1.0, 2.0, 1.0, 3.0,
&tmp2);

Matrix2Equal(tmp2.grad_gates[0].matrix, grad_gates[0].grad_gates[1].matrix,
1e-4);

metadata.clear();
meta.symbol_values.clear();
meta.placeholder_names.clear();
grad_gates.clear();
fuses.clear();

metadata.push_back(meta);

CreateGradientCircuit(circuit, metadata, &fuses, &grad_gates);
EXPECT_EQ(grad_gates.size(), 0);
EXPECT_EQ(fuses.size(), 1);
}

TEST(AdjUtilTest, CreateGradientPhasedISwap) {
QsimCircuit circuit;
std::vector<GateMetaData> metadata;
std::vector<std::vector<qsim::GateFused<QsimGate>>> fuses;
std::vector<GradientOfGate> grad_gates;

// Create a symbolized gate.
circuit.num_qubits = 2;
circuit.gates.push_back(
qsim::Cirq::PhasedISwapPowGate<float>::Create(0, 0, 1, 1.0, 2.0));
GateMetaData meta;
meta.index = 0;
meta.symbol_values.push_back("TheSymbol");
meta.placeholder_names.push_back(GateParamNames::kPhaseExponent);
meta.symbol_values.push_back("TheSymbol2");
meta.placeholder_names.push_back(GateParamNames::kExponent);
meta.gate_params = {1.0, 1.0, 2.0, 1.0};
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(), 2);
EXPECT_EQ(grad_gates[0].params[0], "TheSymbol");
EXPECT_EQ(grad_gates[0].params[1], "TheSymbol2");

// 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;
PopulateGradientPhasedISwapPhasedExponent("TheSymbol", 0, 0, 1, 1.0, 1.0, 2.0,
1.0, &tmp);

Matrix4Equal(tmp.grad_gates[0].matrix, grad_gates[0].grad_gates[0].matrix,
1e-4);

GradientOfGate tmp2;
PopulateGradientPhasedISwapExponent("TheSymbol2", 0, 0, 1, 1.0, 1.0, 2.0, 1.0,
&tmp2);

Matrix4Equal(tmp2.grad_gates[0].matrix, grad_gates[0].grad_gates[1].matrix,
1e-4);

metadata.clear();
meta.symbol_values.clear();
meta.placeholder_names.clear();
grad_gates.clear();
fuses.clear();

metadata.push_back(meta);

CreateGradientCircuit(circuit, metadata, &fuses, &grad_gates);
EXPECT_EQ(grad_gates.size(), 0);
EXPECT_EQ(fuses.size(), 1);
}

TEST(AdjUtilTest, CreateGradientFSim) {
QsimCircuit circuit;
std::vector<GateMetaData> metadata;
std::vector<std::vector<qsim::GateFused<QsimGate>>> fuses;
std::vector<GradientOfGate> grad_gates;

// Create a symbolized gate.
circuit.num_qubits = 2;
circuit.gates.push_back(
qsim::Cirq::FSimGate<float>::Create(0, 0, 1, 1.0, 2.0));
GateMetaData meta;
meta.index = 0;
meta.symbol_values.push_back("TheSymbol");
meta.placeholder_names.push_back(GateParamNames::kTheta);
meta.symbol_values.push_back("TheSymbol2");
meta.placeholder_names.push_back(GateParamNames::kPhi);
meta.gate_params = {1.0, 1.0, 2.0, 1.0};
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(), 2);
EXPECT_EQ(grad_gates[0].params[0], "TheSymbol");
EXPECT_EQ(grad_gates[0].params[1], "TheSymbol2");

// 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;
PopulateGradientFsimTheta("TheSymbol", 0, 0, 1, 1.0, 1.0, 2.0, 1.0, &tmp);

Matrix4Equal(tmp.grad_gates[0].matrix, grad_gates[0].grad_gates[0].matrix,
1e-4);

GradientOfGate tmp2;
PopulateGradientFsimPhi("TheSymbol2", 0, 0, 1, 1.0, 1.0, 2.0, 1.0, &tmp2);

Matrix4Equal(tmp2.grad_gates[0].matrix, grad_gates[0].grad_gates[1].matrix,
1e-4);

metadata.clear();
meta.symbol_values.clear();
meta.placeholder_names.clear();
grad_gates.clear();
fuses.clear();

metadata.push_back(meta);

CreateGradientCircuit(circuit, metadata, &fuses, &grad_gates);
EXPECT_EQ(grad_gates.size(), 0);
EXPECT_EQ(fuses.size(), 1);
}

TEST(AdjUtilTest, CreateGradientEmpty) {
QsimCircuit empty_circuit;
std::vector<GateMetaData> empty_metadata;
std::vector<std::vector<qsim::GateFused<QsimGate>>> fuses;
std::vector<GradientOfGate> grad_gates;

CreateGradientCircuit(empty_circuit, empty_metadata, &fuses, &grad_gates);

// Should create a single "empty fuse."
EXPECT_EQ(fuses.size(), 1);
EXPECT_EQ(fuses[0].size(), 0);

// No gradients.
EXPECT_EQ(grad_gates.size(), 0);
}

TEST(AdjUtilTest, SingleEigenGrad) {
GradientOfGate grad;

PopulateGradientSingleEigen(&qsim::Cirq::YPowGate<float>::Create, "hello", 5,
2, 0.125, 1.0, 0.0, &grad);

// Value verified from:
/*
(cirq.unitary(cirq.Y**(0.125 + 1e-4)) -
cirq.unitary(cirq.Y**(0.125 - 1e-4))) / 2e-4
array([[-0.60111772+1.45122655j, -1.45122655-0.60111772j],
[ 1.45122655+0.60111772j, -0.60111772+1.45122655j]])
*/
std::vector<float> expected{-0.60111, 1.45122, -1.45122, -0.60111,
1.45122, 0.60111, -0.60111, 1.45122};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "hello");
Matrix2Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, TwoEigenGrad) {
GradientOfGate grad;

PopulateGradientTwoEigen(&qsim::Cirq::XXPowGate<float>::Create, "hi", 5, 2, 3,
0.001, 1.0, 0.0, &grad);

// Value verified from:
/*
(cirq.unitary(cirq.XX**(0.001 + 1e-4)) -
cirq.unitary(cirq.XX**(0.001 - 1e-4))) / 2e-4
array([[-0.00493479+1.57078855j, 0. +0.j ,
0. +0.j , 0.00493479-1.57078855j],
[ 0. +0.j , -0.00493479+1.57078855j,
0.00493479-1.57078855j, 0. +0.j ],
[ 0. +0.j , 0.00493479-1.57078855j,
-0.00493479+1.57078855j, 0. +0.j ],
[ 0.00493479-1.57078855j, 0. +0.j ,
0. +0.j , -0.00493479+1.57078855j]])
*/
std::vector<float> expected{
-0.004934, 1.57078, 0.0, 0.0, 0.0, 0.0, 0.004934,
-1.57078, 0.0, 0.0, -0.004934, 1.57078, 0.004934, -1.57078,
0.0, 0.0, 0.0, 0.0, 0.004934, -1.57078, -0.004934,
1.57078, 0.0, 0.0, 0.004934, -1.57078, 0.0, 0.0,
0.0, 0.0, -0.004934, 1.57078};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "hi");
Matrix4Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, PhasedXPhasedExponent) {
GradientOfGate grad;

PopulateGradientPhasedXPhasedExponent("hello2", 5, 2, 10.123, 1.0, 1.0, 1.0,
0.0, &grad);
/*
(cirq.unitary(cirq.PhasedXPowGate(exponent=1.0,phase_exponent=0.001 + 1e-4)) -
cirq.unitary(cirq.PhasedXPowGate(exponent=1.0,phase_exponent=0.001 - 1e-4)))
/ 2e-4
array([[ 0. +0.j , -1.18397518-2.90994963j],
[-1.18397518+2.90994963j, 0. +0.j ]])
*/
std::vector<float> expected{0.0, 0.0, -1.18397, -2.9099,
-1.18397, 2.9099, 0.0, 0.0};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "hello2");
Matrix2Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, PhasedXExponent) {
GradientOfGate grad;

PopulateGradientPhasedXExponent("hello3", 5, 2, 10.123, 1.0, 0.789, 1.0, 0.0,
&grad);
/*
(cirq.unitary(cirq.PhasedXPowGate(exponent=0.789+1e-4,phase_exponent=10.123))
-
cirq.unitary(cirq.PhasedXPowGate(exponent=0.789-1e-4,phase_exponent=10.123)))
/ 2e-4 array([[-0.96664663-1.23814188j, 1.36199145+0.78254732j], [
0.42875189+1.51114951j, -0.96664663-1.23814188j]])
*/
std::vector<float> expected{-0.96664, -1.23814, 1.36199, 0.78254,
0.42875, 1.51114, -0.96664, -1.23814};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "hello3");
Matrix2Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, FSimThetaGrad) {
GradientOfGate grad;
PopulateGradientFsimTheta("hihi", 5, 2, 3, 0.5, 1.0, 1.2, 1.0, &grad);

/*
(cirq.unitary(cirq.FSimGate(theta=0.5 + 1e-4,phi=1.2)) -
cirq.unitary(cirq.FSimGate(theta=0.5-1e-4,phi=1.2))) / 2e-4
array([[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ],
[ 0. +0.j , -0.47942554+0.j ,
0. -0.87758256j, 0. +0.j ],
[ 0. +0.j , 0. -0.87758256j,
-0.47942554+0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ]])
*/
std::vector<float> expected{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.47942,
0.0, 0.0, -0.87758, 0.0, 0.0, 0.0, 0.0, 0.0, -0.87758, -0.47942, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0

};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "hihi");
Matrix4Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, FSimPhiGrad) {
GradientOfGate grad;
PopulateGradientFsimPhi("hihi2", 5, 2, 3, 0.5, 1.0, 1.2, 1.0, &grad);

/*
(cirq.unitary(cirq.FSimGate(theta=0.5,phi=1.2+1e-4)) -
cirq.unitary(cirq.FSimGate(theta=0.5,phi=1.2-1e-4))) / 2e-4
array([[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
0. +0.j , -0.93203908-0.36235775j]])
*/
std::vector<float> expected{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, -0.932039, -0.362357};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "hihi2");
Matrix4Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, PhasedISwapPhasedExponent) {
GradientOfGate grad;

PopulateGradientPhasedISwapPhasedExponent("h", 5, 3, 2, 8.9, 1.0, -3.2, 1.0,
&grad);

/*
(cirq.unitary(cirq.PhasedISwapPowGate(exponent=-3.2,phase_exponent=8.9+1e-4))
- cirq.unitary(cirq.PhasedISwapPowGate(exponent=3.2,phase_exponent=8.9-1e-4)))
/ 2e-4
array([[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
-4.83441368+3.51240713j, 0. +0.j ],
[ 0. +0.j , 4.83441368+3.51240713j,
0. +0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ]])
*/
std::vector<float> expected{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, -4.83441, 3.51238, 0.0, 0.0, 0.0, 0.0, 4.83441, 3.51238, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "h");
Matrix4Equal(grad.grad_gates[0].matrix, expected, 1e-3);
}

TEST(AdjUtilTest, PhasedISwapExponent) {
GradientOfGate grad;

PopulateGradientPhasedISwapExponent("h2", 5, 3, 2, 8.9, 1.0, -3.2, 1.0,
&grad);

/*
(cirq.unitary(cirq.PhasedISwapPowGate(exponent=-3.2+1e-3,phase_exponent=8.9))
-cirq.unitary(cirq.PhasedISwapPowGate(exponent=-3.2-1e-3,phase_exponent=8.9)))
/ 2e-3
array([[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ],
[ 0. +0.j , -1.49391547+0.j ,
0.28531247+0.39269892j, 0. +0.j ],
[ 0. +0.j , -0.28531247+0.39269892j,
-1.49391547+0.j , 0. +0.j ],
[ 0. +0.j , 0. +0.j ,
0. +0.j , 0. +0.j ]])
*/
std::vector<float> expected{
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0,
0.0, 0.0, -1.49391, 0.0, 0.285312, 0.392698, 0.0, 0.0,
0.0, 0.0, -0.285312, 0.392698, -1.49391, 0.0, 0.0, 0.0,
0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0};

EXPECT_EQ(grad.index, 5);
EXPECT_EQ(grad.params[0], "h2");
Matrix4Equal(grad.grad_gates[0].matrix, expected, 1e-4);
}

TEST(AdjUtilTest, Matrix2Diff) {
std::array<float, 8> u{1, 2, 3, 4, 5, 6, 7, 8};
std::array<float, 8> u2{0, 1, 2, 3, 4, 5, 6, 7};
Matrix2Diff(u, u2);
for (int i = 0; i < 8; i++) {
EXPECT_EQ(u2[i], -1);
EXPECT_EQ(u[i], i + 1);
}
}

TEST(AdjUtilTest, Matrix4Diff) {
std::array<float, 32> u;
std::array<float, 32> u2;

for (int i = 0; i < 32; i++) {
u2[i] = i;
u[i] = i + 1;
}

Matrix4Diff(u, u2);
for (int i = 0; i < 32; i++) {
EXPECT_EQ(u2[i], -1);
EXPECT_EQ(u[i], i + 1);
}
}

} // namespace
} // namespace tfq

0 comments on commit 3945cc7

Please sign in to comment.