Skip to content

Commit

Permalink
Merge pull request #245 from imagoulas/simplify_phase_gates
Browse files Browse the repository at this point in the history
Simplification of T, S, Z, and R phase gates added.
  • Loading branch information
imagoulas authored May 10, 2024
2 parents e5eb9b7 + d25ec35 commit b730cfa
Show file tree
Hide file tree
Showing 6 changed files with 186 additions and 20 deletions.
25 changes: 25 additions & 0 deletions src/qforte/circuit.cc
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,25 @@ bool Circuit::is_pauli() const {
return true;
}

double Circuit::get_phase_gate_parameter(const Gate& gate) {
std::unordered_map<GateType, double> gate_parameters = {
{GateType::T, M_PI / 4},
{GateType::S, M_PI / 2},
{GateType::Z, M_PI}
};

if (gate.has_parameter()) {
return gate.parameter().value();
} else {
auto it = gate_parameters.find(gate.gate_type());
if (it != gate_parameters.end()) {
return it->second;
} else {
throw std::invalid_argument("Unknown single-qubit phase gate encountered: " + gate.gate_id());
}
}
}

void Circuit::simplify() {

const std::unordered_set<GateType> involutory_gates = {GateType::X, GateType::Y, GateType::Z,
Expand Down Expand Up @@ -292,6 +311,12 @@ void Circuit::simplify() {
break;
}
}
if (simplification_case == 3) {
gate_indices_to_remove.push_back(pos1);
gates_[pos2] =
make_gate("R", gates_[pos2].target(), gates_[pos2].control(), get_phase_gate_parameter(gate1) + get_phase_gate_parameter(gate2));
break;
}
} else {
break;
}
Expand Down
3 changes: 3 additions & 0 deletions src/qforte/circuit.h
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,9 @@ class Circuit {
private:
/// the list of gates
std::vector<Gate> gates_;

/// helper function to simplify phase gates
double get_phase_gate_parameter(const Gate& gate);
};

bool operator==(const Circuit& qc1, const Circuit& qc2);
Expand Down
16 changes: 16 additions & 0 deletions src/qforte/gate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ Gate Gate::adjoint() const {
// if the gate is self-adjoint then we return the gate itself
if (self_adjoint) {
return *this;
}

// To facilitate circuit simplification, the adjoints of S and T are expressed
// in terms of the R phase gate.

if (type_ == GateType::T) {
return Gate("R", target_, control_, adj_gate, std::make_pair(-M_PI / 4, true));
} else if (type_ == GateType::S) {
return Gate("R", target_, control_, adj_gate, std::make_pair(-M_PI / 2, true));
} else {
// check if label_ is of the form adj(x) and if it is then return Gate(x)
if (label_.size() > 4 and label_.substr(0, 4) == "adj(" and label_.back() == ')') {
Expand Down Expand Up @@ -248,6 +257,13 @@ std::pair<bool, int> evaluate_gate_interaction(const Gate& gate1, const Gate& ga
int product_nqubits = num_qubits_gate1 * num_qubits_gate2;

if (product_nqubits == 1) {
if (phase_1qubit_gates.find(gate1.gate_type()) != phase_1qubit_gates.end() && phase_1qubit_gates.find(gate2.gate_type()) != phase_1qubit_gates.end()) {
if (gate1.gate_type() == gate2.gate_type()) {
return {true, 1};
} else {
return {true, 3};
}
}
std::pair<GateType, GateType> pairGateType = std::make_pair(gate1.gate_type(), gate2.gate_type());
return {pairs_of_commuting_1qubit_gates.find(pairGateType) != pairs_of_commuting_1qubit_gates.end(), gate1.gate_type() == gate2.gate_type()};
}
Expand Down
18 changes: 18 additions & 0 deletions src/qforte/make_gate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,17 @@

#include "gate.h"

double normalize_angle(double angle, double period) {
// Normalize the angle to be within [0, period)
angle = std::fmod(angle, period);
if (angle < -period / 2) {
angle += period;
} else if (angle > period / 2) {
angle -= period;
}
return angle;
}

Gate make_gate(std::string type, size_t target, size_t control, double parameter) {
// using namespace std::complex_literals;
std::complex<double> onei(0.0, 1.0);
Expand Down Expand Up @@ -38,6 +49,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
return Gate(type, target, control, gate);
}
if (type == "R") {
parameter = normalize_angle(parameter, 2 * M_PI);
std::complex<double> tmp = onei * parameter;
std::complex<double> c = std::exp(tmp);
std::complex<double> gate[4][4]{
Expand All @@ -47,6 +59,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
return Gate(type, target, control, gate, std::make_pair(parameter, true));
}
if (type == "Rx") {
parameter = normalize_angle(parameter, 4 * M_PI);
std::complex<double> a = std::cos(0.5 * parameter);
std::complex<double> b = onei * std::sin(0.5 * parameter);
std::complex<double> gate[4][4]{
Expand All @@ -56,6 +69,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
return Gate(type, target, control, gate, std::make_pair(parameter, true));
}
if (type == "Ry") {
parameter = normalize_angle(parameter, 4 * M_PI);
std::complex<double> a = std::cos(0.5 * parameter);
std::complex<double> b = std::sin(0.5 * parameter);
std::complex<double> gate[4][4]{
Expand All @@ -65,6 +79,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
return Gate(type, target, control, gate, std::make_pair(parameter, true));
}
if (type == "Rz") {
parameter = normalize_angle(parameter, 4 * M_PI);
std::complex<double> tmp_a = -onei * 0.5 * parameter;
std::complex<double> a = std::exp(tmp_a);
std::complex<double> tmp_b = onei * 0.5 * parameter;
Expand Down Expand Up @@ -111,6 +126,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
if (type == "A") {
// The A gate is the particle-number preserving gate introduced in DOI: 10.1103/PhysRevA.98.022322.
// Its decomposition in elementary gates requires 3 CNOTs.
parameter = normalize_angle(parameter, 2 * M_PI);
std::complex<double> c = std::cos(parameter);
std::complex<double> s = std::sin(parameter);
std::complex<double> gate[4][4]{
Expand Down Expand Up @@ -158,6 +174,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
return Gate(type, target, control, gate);
}
if (type == "cR") {
parameter = normalize_angle(parameter, 2 * M_PI);
std::complex<double> tmp = onei * parameter;
std::complex<double> c = std::exp(tmp);
std::complex<double> gate[4][4]{
Expand All @@ -180,6 +197,7 @@ Gate make_gate(std::string type, size_t target, size_t control, double parameter
return Gate(type, target, control, gate);
}
if (type == "cRz") {
parameter = normalize_angle(parameter, 4 * M_PI);
std::complex<double> tmp_a = -onei * 0.5 * parameter;
std::complex<double> a = std::exp(tmp_a);
std::complex<double> tmp_b = onei * 0.5 * parameter;
Expand Down
95 changes: 75 additions & 20 deletions tests/test_circuit_simplify.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@

parametrized_gates = {'Rx','Ry','Rz', 'R', 'cR', 'cRz', 'A'}

parametrized_gate_periods = {'Rx':4*np.pi, 'Ry':4*np.pi, 'Rz':4*np.pi,
'R':2*np.pi, 'cR':2*np.pi, 'cRz':4*np.pi,
'A':2*np.pi}

diagonal_1qubit_gates = {'T', 'S', 'Z', 'Rz', 'R'}

phase_1qubit_gates = ['T', 'S', 'Z', 'R']

symmetrical_2qubit_gates = {'cZ', 'cR', 'SWAP'}

involutory_gates = {'X', 'Y', 'Z', 'H',
Expand Down Expand Up @@ -42,14 +48,16 @@ def test_disjoint_gates(self):
for i in range(len(one_qubit_gate_pool)):
gatetype1 = one_qubit_gate_pool[i]
if gatetype1 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype1]
parameter = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, 0, parameter)
else:
gate1 = qf.gate(gatetype1, 0)
for j in range(i, len(one_qubit_gate_pool)):
gatetype2 = one_qubit_gate_pool[j]
if gatetype2 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype2]
parameter = random.uniform(-period, period)
gate2 = qf.gate(gatetype2, 1, 1, parameter)
else:
gate2 = qf.gate(gatetype2, 1)
Expand All @@ -58,13 +66,15 @@ def test_disjoint_gates(self):
# single- and two-qubit gates
for gatetype1 in one_qubit_gate_pool:
if gatetype1 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype1]
parameter = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, 0, parameter)
else:
gate1 = qf.gate(gatetype1, 0)
for gatetype2 in two_qubit_gate_pool:
if gatetype2 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype2]
parameter = random.uniform(-period, period)
gate2 = qf.gate(gatetype2, 1, 2, parameter)
else:
gate2 = qf.gate(gatetype2, 1, 2)
Expand All @@ -74,14 +84,16 @@ def test_disjoint_gates(self):
for i in range(len(two_qubit_gate_pool)):
gatetype1 = two_qubit_gate_pool[i]
if gatetype1 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype1]
parameter = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, 1, parameter)
else:
gate1 = qf.gate(gatetype1, 0, 1)
for j in range(i, len(two_qubit_gate_pool)):
gatetype2 = two_qubit_gate_pool[j]
if gatetype2 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype2]
parameter = random.uniform(-period, period)
gate2 = qf.gate(gatetype2, 2, 3, parameter)
else:
gate2 = qf.gate(gatetype2, 2, 3)
Expand All @@ -92,22 +104,28 @@ def test_commuting_1qubit_gates(self):
simplifiable = i in pairs_of_simplifiable_1qubit_gates
gatetype1, gatetype2 = i[0], i[1]
if gatetype1 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype1]
parameter = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, 0, parameter)
else:
gate1 = qf.gate(gatetype1, 0)
if gatetype2 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype2]
parameter = random.uniform(-period, period)
gate2 = qf.gate(gatetype2, 0, 0, parameter)
else:
gate2 = qf.gate(gatetype2, 0)
if gatetype1 in phase_1qubit_gates and gatetype2 in phase_1qubit_gates and gatetype2 != gatetype1:
assert (qf.evaluate_gate_interaction(gate1, gate2) == (True, 3))
continue
assert (qf.evaluate_gate_interaction(gate1, gate2) == (True, simplifiable))

def test_non_commuting_1qubit_gates(self):
for i in range(len(one_qubit_gate_pool)):
gatetype1 = one_qubit_gate_pool[i]
if gatetype1 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype1]
parameter = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, 0, parameter)
else:
gate1 = qf.gate(gatetype1, 0)
Expand All @@ -116,7 +134,8 @@ def test_non_commuting_1qubit_gates(self):
if tuple(sorted((gatetype1, gatetype2))) in pairs_of_commuting_1qubit_gates:
continue
if gatetype2 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype2]
parameter = random.uniform(-period, period)
gate2 = qf.gate(gatetype2, 0, 0, parameter)
else:
gate2 = qf.gate(gatetype2, 0)
Expand All @@ -125,15 +144,17 @@ def test_non_commuting_1qubit_gates(self):
def test_1qubit_and_2qubit_gate(self):
for one_qubit_gate_type in one_qubit_gate_pool:
if one_qubit_gate_type in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[one_qubit_gate_type]
parameter = random.uniform(-period, period)
one_qubit_gate_target = qf.gate(one_qubit_gate_type, 0, 0, parameter)
one_qubit_gate_control = qf.gate(one_qubit_gate_type, 1, 1, parameter)
else:
one_qubit_gate_target = qf.gate(one_qubit_gate_type, 0)
one_qubit_gate_control = qf.gate(one_qubit_gate_type, 1)
for two_qubit_gate_type in two_qubit_gate_pool:
if two_qubit_gate_type in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[two_qubit_gate_type]
parameter = random.uniform(-period, period)
two_qubit_gate = qf.gate(two_qubit_gate_type, 0, 1, parameter)
else:
two_qubit_gate = qf.gate(two_qubit_gate_type, 0, 1)
Expand All @@ -154,14 +175,16 @@ def test_2qubit_gates(self):
for i in range(len(two_qubit_gate_pool)):
gatetype1 = two_qubit_gate_pool[i]
if gatetype1 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype1]
parameter = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, 1, parameter)
else:
gate1 = qf.gate(gatetype1, 0, 1)
for j in range(i, len(two_qubit_gate_pool)):
gatetype2 = two_qubit_gate_pool[j]
if gatetype2 in parametrized_gates:
parameter = random.uniform(0, 2*np.pi)
period = parametrized_gate_periods[gatetype2]
parameter = random.uniform(-period, period)
gate2a = qf.gate(gatetype2, 0, 2, parameter)
gate2b = qf.gate(gatetype2, 2, 1, parameter)
gate2c = qf.gate(gatetype2, 2, 0, parameter)
Expand Down Expand Up @@ -272,30 +295,30 @@ def test_simplify_parametrized_gates(self):
circ.add(qf.gate(gatetype, 0, 0, 0.5))
circ.add(qf.gate(gatetype, 0, 0, -0.2))
circ.simplify()
assert len(circ.gates()) == 1
assert circ.size() == 1
assert circ.gates()[0].gate_id() == gatetype
assert circ.gates()[0].parameter() == 0.3
elif gatetype in symmetrical_2qubit_gates:
circ = qf.Circuit()
circ.add(qf.gate(gatetype, 0, 1, 0.5))
circ.add(qf.gate(gatetype, 0, 1, -0.2))
circ.simplify()
assert len(circ.gates()) == 1
assert circ.size() == 1
assert circ.gates()[0].gate_id() == gatetype
assert circ.gates()[0].parameter() == 0.3
circ = qf.Circuit()
circ.add(qf.gate(gatetype, 0, 1, 0.5))
circ.add(qf.gate(gatetype, 1, 0, -0.2))
circ.simplify()
assert len(circ.gates()) == 1
assert circ.size() == 1
assert circ.gates()[0].gate_id() == gatetype
assert circ.gates()[0].parameter() == 0.3
else:
circ = qf.Circuit()
circ.add(qf.gate(gatetype, 0, 1, 0.5))
circ.add(qf.gate(gatetype, 0, 1, -0.2))
circ.simplify()
assert len(circ.gates()) == 1
assert circ.size() == 1
assert circ.gates()[0].gate_id() == gatetype
assert circ.gates()[0].parameter() == 0.3

Expand All @@ -307,16 +330,48 @@ def test_simplify_square_root_gates(self):
circ.add(qf.gate(gatetype, 0))
circ.add(qf.gate(gatetype, 0))
circ.simplify()
assert len(circ.gates()) == 1
assert circ.size() == 1
assert circ.gates()[0].gate_id() == simplify_square_root_gates[gatetype]
else:
circ = qf.Circuit()
circ.add(qf.gate(gatetype, 0, 1))
circ.add(qf.gate(gatetype, 0, 1))
circ.simplify()
assert len(circ.gates()) == 1
assert circ.size() == 1
assert circ.gates()[0].gate_id() == simplify_square_root_gates[gatetype]

def test_simplify_1qubit_phase_gates(slef):

for i in range(len(phase_1qubit_gates)):
gatetype1 = phase_1qubit_gates[i]
if gatetype1 in parametrized_gates:
period = parametrized_gate_periods[gatetype1]
parameter1 = random.uniform(-period, period)
gate1 = qf.gate(gatetype1, 0, parameter1)
else:
gate1 = qf.gate(gatetype1, 0)
for j in range(i+1, len(phase_1qubit_gates)):
gatetype2 = phase_1qubit_gates[j]
if gatetype2 in parametrized_gates:
period = parametrized_gate_periods[gatetype2]
parameter2 = random.uniform(-period, period)
gate2 = qf.gate(gatetype2, 0, parameter2)
else:
gate2 = qf.gate(gatetype2, 0)
circ1 = qf.Circuit()
circ1.add(gate1)
circ1.add(gate2)
circ1.simplify()
assert circ1.size() == 1
assert circ1.gates()[0].gate_id() == 'R'
circ2 = qf.Circuit()
circ2.add(gate2)
circ2.add(gate1)
circ2.simplify()
assert circ2.size() == 1
assert circ2.gates()[0].gate_id() == 'R'


def test_simplify_H6_STO6G_UCCSDT_ansatz_circuit(self):

Rhh = 2
Expand Down
Loading

0 comments on commit b730cfa

Please sign in to comment.