diff --git a/CHANGELOG.md b/CHANGELOG.md index c25397694a..82c7de27e6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ ### Improvements +* Inverse gates are now supported. + [(#89)](https://github.com/PennyLaneAI/pennylane-lightning/pull/89) + * Add new lightweight backend with performance improvements. [(#57)](https://github.com/PennyLaneAI/pennylane-lightning/pull/57) diff --git a/pennylane_lightning/lightning_qubit.py b/pennylane_lightning/lightning_qubit.py index 4e5ffadce5..dac0a34f2c 100644 --- a/pennylane_lightning/lightning_qubit.py +++ b/pennylane_lightning/lightning_qubit.py @@ -85,7 +85,7 @@ def capabilities(cls): capabilities.update( model="qubit", supports_reversible_diff=False, - supports_inverse_operations=False, + supports_inverse_operations=True, supports_analytic_computation=True, returns_state=True, ) @@ -133,10 +133,23 @@ def apply_lightning(self, state, operations): Returns: array[complex]: the output state tensor """ - op_names = [o.name for o in operations] + op_names = [self._remove_inverse_string(o.name) for o in operations] op_wires = [self.wires.indices(o.wires) for o in operations] op_param = [o.parameters for o in operations] + op_inverse = [o.inverse for o in operations] state_vector = np.ravel(state) - apply(state_vector, op_names, op_wires, op_param, self.num_wires) + apply(state_vector, op_names, op_wires, op_param, op_inverse, self.num_wires) return np.reshape(state_vector, state.shape) + + @staticmethod + def _remove_inverse_string(string): + """Removes the ``.inv`` appended to the end of inverse gates. + + Args: + string (str): name of operation + + Returns: + str: name of operation with ``.inv`` removed (if present) + """ + return string.replace(".inv", "") diff --git a/pennylane_lightning/src/Apply.cpp b/pennylane_lightning/src/Apply.cpp index 866ec6c39a..3198ea7690 100644 --- a/pennylane_lightning/src/Apply.cpp +++ b/pennylane_lightning/src/Apply.cpp @@ -53,6 +53,7 @@ void Pennylane::constructAndApplyOperation( const string& opLabel, const vector& opWires, const vector& opParams, + bool inverse, const unsigned int qubits ) { unique_ptr gate = constructGate(opLabel, opParams); @@ -64,7 +65,7 @@ void Pennylane::constructAndApplyOperation( vector externalWires = getIndicesAfterExclusion(opWires, qubits); vector externalIndices = generateBitPatterns(externalWires, qubits); - gate->applyKernel(state, internalIndices, externalIndices); + gate->applyKernel(state, internalIndices, externalIndices, inverse); } void Pennylane::apply( @@ -72,6 +73,7 @@ void Pennylane::apply( const vector& ops, const vector>& wires, const vector>& params, + const vector& inverse, const unsigned int qubits ) { if (qubits <= 0) @@ -86,7 +88,7 @@ void Pennylane::apply( throw std::invalid_argument("Invalid arguments: number of operations, wires, and parameters must all be equal"); for (int i = 0; i < numOperations; i++) { - constructAndApplyOperation(state, ops[i], wires[i], params[i], qubits); + constructAndApplyOperation(state, ops[i], wires[i], params[i], inverse[i], qubits); } } diff --git a/pennylane_lightning/src/Apply.hpp b/pennylane_lightning/src/Apply.hpp index 0ec1d79ca4..91d67778bc 100644 --- a/pennylane_lightning/src/Apply.hpp +++ b/pennylane_lightning/src/Apply.hpp @@ -62,6 +62,7 @@ namespace Pennylane { * @param opLabel unique string corresponding to a gate type * @param opWires index of qubits on which the gate acts * @param opParams defines the gate parameterisation (may be zero-length for some gates) + * @param inverse boolean indicating whether to apply the gate or its inverse * @param qubits number of qubits */ void constructAndApplyOperation( @@ -69,6 +70,7 @@ namespace Pennylane { const std::string& opLabel, const std::vector& opWires, const std::vector& opParams, + bool inverse, const unsigned int qubits ); @@ -79,12 +81,15 @@ namespace Pennylane { * @param ops list of unique string names corresponding to gate types, in the order they should be applied * @param wires list of wires on which each gate acts * @param params list of parameters that defines the gate parameterisation + * @param inverse list of booleans indicating whether a given gate or its inverse should be applied + * @param qubits number of qubits */ void apply( StateVector& state, const std::vector& ops, const std::vector>& wires, const std::vector>& params, + const std::vector& inverse, const unsigned int qubits ); diff --git a/pennylane_lightning/src/Bindings.cpp b/pennylane_lightning/src/Bindings.cpp index d05772ecc7..5dc66ee941 100644 --- a/pennylane_lightning/src/Bindings.cpp +++ b/pennylane_lightning/src/Bindings.cpp @@ -39,10 +39,11 @@ void apply( vector ops, vector> wires, vector> params, + vector inverse, const unsigned int qubits ) { StateVector state = create(&stateNumpyArray); - Pennylane::apply(state, ops, wires, params, qubits); + Pennylane::apply(state, ops, wires, params, inverse, qubits); } PYBIND11_MODULE(lightning_qubit_ops, m) diff --git a/pennylane_lightning/src/Gates.cpp b/pennylane_lightning/src/Gates.cpp index 9699481cbd..53c5a07271 100644 --- a/pennylane_lightning/src/Gates.cpp +++ b/pennylane_lightning/src/Gates.cpp @@ -28,6 +28,7 @@ using std::string; using std::swap; using std::unique_ptr; using std::vector; +using std::conj; using Pennylane::CplxType; @@ -44,7 +45,7 @@ Pennylane::AbstractGate::AbstractGate(int numQubits) , length(exp2(numQubits)) {} -void Pennylane::AbstractGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::AbstractGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { const vector& matrix = asMatrix(); assert(indices.size() == length); @@ -62,9 +63,17 @@ void Pennylane::AbstractGate::applyKernel(const StateVector& state, const std::v for (size_t i = 0; i < indices.size(); i++) { size_t index = indices[i]; shiftedState[index] = 0; - size_t baseIndex = i * indices.size(); - for (size_t j = 0; j < indices.size(); j++) { - shiftedState[index] += matrix[baseIndex + j] * v[j]; + + if (inverse == true) { + for (size_t j = 0; j < indices.size(); j++) { + size_t baseIndex = j * indices.size(); + shiftedState[index] += conj(matrix[baseIndex + i]) * v[j]; + } + } else { + size_t baseIndex = i * indices.size(); + for (size_t j = 0; j < indices.size(); j++) { + shiftedState[index] += matrix[baseIndex + j] * v[j]; + } } } } @@ -89,7 +98,8 @@ const vector Pennylane::XGate::matrix{ 0, 1, 1, 0 }; -void Pennylane::XGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::XGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; swap(shiftedState[indices[0]], shiftedState[indices[1]]); @@ -109,7 +119,8 @@ const vector Pennylane::YGate::matrix{ 0, -IMAG, IMAG, 0 }; -void Pennylane::YGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::YGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; CplxType v0 = shiftedState[indices[0]]; @@ -131,7 +142,8 @@ const std::vector Pennylane::ZGate::matrix{ 1, 0, 0, -1 }; -void Pennylane::ZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::ZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; shiftedState[indices[1]] *= -1; @@ -151,7 +163,8 @@ const vector Pennylane::HadamardGate::matrix{ SQRT2INV, SQRT2INV, SQRT2INV, -SQRT2INV }; -void Pennylane::HadamardGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::HadamardGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; CplxType v0 = shiftedState[indices[0]]; @@ -174,10 +187,12 @@ const vector Pennylane::SGate::matrix{ 1, 0, 0, IMAG }; -void Pennylane::SGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::SGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType shift = (inverse == true) ? -IMAG : IMAG; + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; - shiftedState[indices[1]] *= IMAG; + shiftedState[indices[1]] *= shift; } } @@ -196,10 +211,12 @@ const vector Pennylane::TGate::matrix{ 1, 0, 0, Pennylane::TGate::shift }; -void Pennylane::TGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::TGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType shift = (inverse == true) ? conj(Pennylane::TGate::shift) : Pennylane::TGate::shift; + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; - shiftedState[indices[1]] *= Pennylane::TGate::shift; + shiftedState[indices[1]] *= shift; } } @@ -220,6 +237,18 @@ Pennylane::RotationXGate::RotationXGate(double rotationAngle) js, c } {} +void Pennylane::RotationXGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType js_ = (inverse == true) ? -js : js; + + for (const size_t& externalIndex : externalIndices) { + CplxType* shiftedState = state.arr + externalIndex; + CplxType v0 = shiftedState[indices[0]]; + CplxType v1 = shiftedState[indices[1]]; + shiftedState[indices[0]] = c * v0 + js_ * v1; + shiftedState[indices[1]] = js_ * v0 + c * v1; + } +} + // ------------------------------------------------------------------------------------------------------------- const string Pennylane::RotationYGate::label = "RY"; @@ -237,6 +266,18 @@ Pennylane::RotationYGate::RotationYGate(double rotationAngle) s, c } {} +void Pennylane::RotationYGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType s_ = (inverse == true) ? -s : s; + + for (const size_t& externalIndex : externalIndices) { + CplxType* shiftedState = state.arr + externalIndex; + CplxType v0 = shiftedState[indices[0]]; + CplxType v1 = shiftedState[indices[1]]; + shiftedState[indices[0]] = c * v0 - s_ * v1; + shiftedState[indices[1]] = s_ * v0 + c * v1; + } +} + // ------------------------------------------------------------------------------------------------------------- const string Pennylane::RotationZGate::label = "RZ"; @@ -254,11 +295,19 @@ Pennylane::RotationZGate::RotationZGate(double rotationAngle) 0, second } {} -void Pennylane::RotationZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::RotationZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType shift1 = first; + CplxType shift2 = second; + + if (inverse == true) { + shift1 = conj(first); + shift2 = conj(second); + } + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; - shiftedState[indices[0]] *= first; - shiftedState[indices[1]] *= second; + shiftedState[indices[0]] *= shift1; + shiftedState[indices[1]] *= shift2; } } @@ -278,10 +327,12 @@ Pennylane::PhaseShiftGate::PhaseShiftGate(double rotationAngle) 0, shift } {} -void Pennylane::PhaseShiftGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::PhaseShiftGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType s = (inverse == true) ? conj(shift) : shift; + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; - shiftedState[indices[1]] *= shift; + shiftedState[indices[1]] *= s; } } @@ -306,6 +357,28 @@ Pennylane::GeneralRotationGate::GeneralRotationGate(double phi, double theta, do r3, r4 } {} +void Pennylane::GeneralRotationGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType t1 = r1; + CplxType t2 = r2; + CplxType t3 = r3; + CplxType t4 = r4; + + if (inverse == true) { + t1 = conj(r1); + t2 *= -1; + t3 *= -1; + t4 = conj(t4); + } + + for (const size_t& externalIndex : externalIndices) { + CplxType* shiftedState = state.arr + externalIndex; + CplxType v0 = shiftedState[indices[0]]; + CplxType v1 = shiftedState[indices[1]]; + shiftedState[indices[0]] = t1 * v0 + t2 * v1; + shiftedState[indices[1]] = t3 * v0 + t4 * v1; + } +} + // ------------------------------------------------------------------------------------------------------------- Pennylane::TwoQubitGate::TwoQubitGate() @@ -327,7 +400,8 @@ const std::vector Pennylane::CNOTGate::matrix{ 0, 0, 0, 1, 0, 0, 1, 0 }; -void Pennylane::CNOTGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CNOTGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; swap(shiftedState[indices[2]], shiftedState[indices[3]]); @@ -349,7 +423,8 @@ const std::vector Pennylane::SWAPGate::matrix{ 0, 1, 0, 0, 0, 0, 0, 1 }; -void Pennylane::SWAPGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::SWAPGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; swap(shiftedState[indices[1]], shiftedState[indices[2]]); @@ -371,7 +446,8 @@ const std::vector Pennylane::CZGate::matrix{ 0, 0, 1, 0, 0, 0, 0, -1 }; -void Pennylane::CZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; shiftedState[indices[3]] *= -1; @@ -397,13 +473,15 @@ Pennylane::CRotationXGate::CRotationXGate(double rotationAngle) 0, 0, js, c } {} -void Pennylane::CRotationXGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CRotationXGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType js_ = (inverse == true) ? -js : js; + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; CplxType v0 = shiftedState[indices[2]]; CplxType v1 = shiftedState[indices[3]]; - shiftedState[indices[2]] = c * v0 + js * v1; - shiftedState[indices[3]] = js * v0 + c * v1; + shiftedState[indices[2]] = c * v0 + js_ * v1; + shiftedState[indices[3]] = js_ * v0 + c * v1; } } @@ -426,13 +504,15 @@ Pennylane::CRotationYGate::CRotationYGate(double rotationAngle) 0, 0, s, c } {} -void Pennylane::CRotationYGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CRotationYGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType s_ = (inverse == true) ? -s : s; + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; CplxType v0 = shiftedState[indices[2]]; CplxType v1 = shiftedState[indices[3]]; - shiftedState[indices[2]] = c * v0 - s * v1; - shiftedState[indices[3]] = s * v0 + c * v1; + shiftedState[indices[2]] = c * v0 - s_ * v1; + shiftedState[indices[3]] = s_ * v0 + c * v1; } } @@ -455,11 +535,19 @@ Pennylane::CRotationZGate::CRotationZGate(double rotationAngle) 0, 0, 0, second } {} -void Pennylane::CRotationZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CRotationZGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType shift1 = first; + CplxType shift2 = second; + + if (inverse == true) { + shift1 = conj(first); + shift2 = conj(second); + } + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; - shiftedState[indices[2]] *= first; - shiftedState[indices[3]] *= second; + shiftedState[indices[2]] *= shift1; + shiftedState[indices[3]] *= shift2; } } @@ -486,13 +574,25 @@ Pennylane::CGeneralRotationGate::CGeneralRotationGate(double phi, double theta, 0, 0, r3, r4 } {} -void Pennylane::CGeneralRotationGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CGeneralRotationGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse) { + CplxType t1 = r1; + CplxType t2 = r2; + CplxType t3 = r3; + CplxType t4 = r4; + + if (inverse == true) { + t1 = conj(r1); + t2 *= -1; + t3 *= -1; + t4 = conj(t4); + } + for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; CplxType v0 = shiftedState[indices[2]]; CplxType v1 = shiftedState[indices[3]]; - shiftedState[indices[2]] = r1 * v0 + r2 * v1; - shiftedState[indices[3]] = r3 * v0 + r4 * v1; + shiftedState[indices[2]] = t1 * v0 + t2 * v1; + shiftedState[indices[3]] = t3 * v0 + t4 * v1; } } @@ -521,7 +621,8 @@ const std::vector Pennylane::ToffoliGate::matrix{ 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0 }; -void Pennylane::ToffoliGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::ToffoliGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; swap(shiftedState[indices[6]], shiftedState[indices[7]]); @@ -547,7 +648,8 @@ const std::vector Pennylane::CSWAPGate::matrix{ 0, 0, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1 }; -void Pennylane::CSWAPGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices) { +void Pennylane::CSWAPGate::applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool) { + // gate is its own inverse for (const size_t& externalIndex : externalIndices) { CplxType* shiftedState = state.arr + externalIndex; swap(shiftedState[indices[5]], shiftedState[indices[6]]); diff --git a/pennylane_lightning/src/Gates.hpp b/pennylane_lightning/src/Gates.hpp index d5f1d43ece..4d6b3e7341 100644 --- a/pennylane_lightning/src/Gates.hpp +++ b/pennylane_lightning/src/Gates.hpp @@ -43,7 +43,7 @@ namespace Pennylane { /** * Generic matrix-multiplication kernel */ - virtual void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + virtual void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; // Single-qubit gates: @@ -62,7 +62,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class YGate : public SingleQubitGate { @@ -74,7 +74,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class ZGate : public SingleQubitGate { @@ -86,7 +86,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class HadamardGate : public SingleQubitGate { @@ -98,7 +98,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class SGate : public SingleQubitGate { @@ -110,7 +110,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class TGate : public SingleQubitGate { @@ -123,7 +123,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class RotationXGate : public SingleQubitGate { @@ -137,6 +137,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class RotationYGate : public SingleQubitGate { @@ -150,6 +151,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class RotationZGate : public SingleQubitGate { @@ -163,7 +165,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class PhaseShiftGate : public SingleQubitGate { @@ -177,7 +179,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class GeneralRotationGate : public SingleQubitGate { @@ -191,6 +193,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; // Two-qubit gates @@ -209,7 +212,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class SWAPGate : public TwoQubitGate { @@ -221,7 +224,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class CZGate : public TwoQubitGate { @@ -233,7 +236,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class CRotationXGate : public TwoQubitGate { @@ -247,7 +250,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class CRotationYGate : public TwoQubitGate { @@ -261,7 +264,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class CRotationZGate : public TwoQubitGate { @@ -275,7 +278,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class CGeneralRotationGate : public TwoQubitGate { @@ -289,7 +292,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; // Three-qubit gates @@ -308,7 +311,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; class CSWAPGate : public ThreeQubitGate { @@ -320,7 +323,7 @@ namespace Pennylane { inline const std::vector& asMatrix() { return matrix; } - void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices); + void applyKernel(const StateVector& state, const std::vector& indices, const std::vector& externalIndices, bool inverse); }; /** diff --git a/tests/test_gates.py b/tests/test_gates.py new file mode 100644 index 0000000000..6b6e53374c --- /dev/null +++ b/tests/test_gates.py @@ -0,0 +1,163 @@ +# Copyright 2021 Xanadu Quantum Technologies Inc. + +# 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. +""" +Unit tests for the correct application of gates with lightning.qubit. +""" +import itertools + +import numpy as np +import pennylane as qml +import pytest + +from pennylane_lightning import LightningQubit + + +@pytest.fixture +def op(op_name): + return getattr(qml, op_name) + + +@pytest.mark.parametrize("op_name", LightningQubit.operations) +def test_gate_unitary_correct(op, op_name): + """Test if lightning.qubit correctly applies gates by reconstructing the unitary matrix and + comparing to the expected version""" + + if op_name in ("BasisState", "QubitStateVector"): + pytest.skip("Skipping operation because it is a state preparation") + + wires = op.num_wires + dev = qml.device("lightning.qubit", wires=wires) + num_params = op.num_params + p = [0.1] * num_params + + @qml.qnode(dev) + def output(input): + qml.BasisState(input, wires=range(wires)) + op(*p, wires=range(wires)) + return qml.state() + + unitary = np.zeros((2 ** wires, 2 ** wires), dtype=np.complex128) + + for i, input in enumerate(itertools.product([0, 1], repeat=wires)): + out = output(input) + unitary[:, i] = out + + unitary_expected = op(*p, wires=range(wires)).matrix + + assert np.allclose(unitary, unitary_expected) + + +@pytest.mark.parametrize("op_name", LightningQubit.operations) +def test_inverse_unitary_correct(op, op_name): + """Test if lightning.qubit correctly applies inverse gates by reconstructing the unitary matrix + and comparing to the expected version""" + + if op_name in ("BasisState", "QubitStateVector"): + pytest.skip("Skipping operation because it is a state preparation") + + wires = op.num_wires + dev = qml.device("lightning.qubit", wires=wires) + num_params = op.num_params + p = [0.1] * num_params + + @qml.qnode(dev) + def output(input): + qml.BasisState(input, wires=range(wires)) + op(*p, wires=range(wires)).inv() + return qml.state() + + unitary = np.zeros((2 ** wires, 2 ** wires), dtype=np.complex128) + + for i, input in enumerate(itertools.product([0, 1], repeat=wires)): + out = output(input) + unitary[:, i] = out + + unitary_expected = op(*p, wires=range(wires)).inv().matrix + + assert np.allclose(unitary, unitary_expected) + + +random_unitary = np.array( + [ + [ + -0.48401572 - 0.11012304j, + -0.44806504 + 0.46775911j, + -0.36968281 + 0.19235993j, + -0.37561358 + 0.13887962j, + ], + [ + -0.12838047 + 0.13992187j, + 0.14531831 + 0.45319438j, + 0.28902175 - 0.71158765j, + -0.24333677 - 0.29721109j, + ], + [ + 0.26400811 - 0.72519269j, + 0.13965687 + 0.35092711j, + 0.09141515 - 0.14367072j, + 0.14894673 + 0.45886629j, + ], + [ + -0.04067799 + 0.34681783j, + -0.45852968 - 0.03214391j, + -0.10528164 - 0.4431247j, + 0.50251451 + 0.45476965j, + ], + ] +) + + +@pytest.mark.xfail(strict=True) # needs support for QubitUnitary +def test_arbitrary_unitary_correct(): + """Test if lightning.qubit correctly applies an arbitrary unitary by reconstructing its + matrix""" + wires = 2 + dev = qml.device("lightning.qubit", wires=wires) + + @qml.qnode(dev) + def output(input): + qml.BasisState(input, wires=range(wires)) + qml.QubitUnitary(random_unitary, wires=range(2)) + return qml.state() + + unitary = np.zeros((2 ** wires, 2 ** wires), dtype=np.complex128) + + for i, input in enumerate(itertools.product([0, 1], repeat=wires)): + out = output(input) + unitary[:, i] = out + + assert np.allclose(unitary, random_unitary) + + +@pytest.mark.xfail(strict=True) # needs support for QubitUnitary +def test_arbitrary_inv_unitary_correct(): + """Test if lightning.qubit correctly applies the inverse of an arbitrary unitary by + reconstructing its matrix""" + wires = 2 + dev = qml.device("lightning.qubit", wires=wires) + + @qml.qnode(dev) + def output(input): + qml.BasisState(input, wires=range(wires)) + qml.QubitUnitary(random_unitary, wires=range(2)).inv() + return qml.state() + + unitary = np.zeros((2 ** wires, 2 ** wires), dtype=np.complex128) + + for i, input in enumerate(itertools.product([0, 1], repeat=wires)): + out = output(input) + unitary[:, i] = out + + random_unitary_inv = random_unitary.conj().T + assert np.allclose(unitary, random_unitary_inv)