diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5c7b5d4..36aa96d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,9 +2,9 @@ name: QuantumSim CI on: push: - branches: [master] + branches: [main] pull_request: - branches: [master] + branches: [main] jobs: build-ubuntu: diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..5891d16 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,7 @@ +# Changelog + +## [UNRELEASED] - TBD + +### Added + +- Gate execution times. \ No newline at end of file diff --git a/src/QubitLayer.cpp b/src/QubitLayer.cpp index c0ddd2c..a104996 100644 --- a/src/QubitLayer.cpp +++ b/src/QubitLayer.cpp @@ -7,46 +7,47 @@ QubitLayer::QubitLayer(unsigned int numQubits, qubitLayer *qL) { // calculate the number of states - numStates = 1; + _numStates = 1; for (unsigned int i = 0; i < numQubits; i++) - numStates *= 2; + _numStates *= 2; // allocate memory for arrays - qEven_ = new qubitLayer[numStates]; - qOdd_ = new qubitLayer[numStates]; + _qEven = new qubitLayer[_numStates]; + _qOdd = new qubitLayer[_numStates]; // if input is provided then use that to fill the input qubit state if (!(qL == nullptr)) - for (unsigned int row = 0; row < numStates; row++) - qEven_[row] = qL[row]; + for (unsigned int row = 0; row < _numStates; row++) + _qEven[row] = qL[row]; else - qEven_[0] = {1, 0}; + // the default state is |000...0> + _qEven[0] = {1, 0}; } QubitLayer::~QubitLayer() { - delete[] qEven_; - delete[] qOdd_; + delete[] _qEven; + delete[] _qOdd; } void QubitLayer::updateLayer() { - parity ? std::fill(qEven_, qEven_ + numStates, zeroComplex) : std::fill(qOdd_, qOdd_ + numStates, zeroComplex); + _parity ? std::fill(_qEven, _qEven + _numStates, zeroComplex) : std::fill(_qOdd, _qOdd + _numStates, zeroComplex); toggleParity(); } bool QubitLayer::checkZeroState(int qubit) { - return parity ? qEven_[qubit].imag() || qEven_[qubit].real() : qOdd_[qubit].imag() || qOdd_[qubit].real(); + return _parity ? _qEven[qubit].imag() || _qEven[qubit].real() : _qOdd[qubit].imag() || _qOdd[qubit].real(); } void QubitLayer::applyPauliX(int target) { #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; state.flip(target); - parity ? qOdd_[state.to_ulong()] = qEven_[i] : qEven_[state.to_ulong()] = qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] = _qEven[i] : _qEven[state.to_ulong()] = _qOdd[i]; } updateLayer(); } @@ -54,7 +55,7 @@ void QubitLayer::applyPauliX(int target) void QubitLayer::applyPauliY(int target) { #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; @@ -62,10 +63,10 @@ void QubitLayer::applyPauliY(int target) state.flip(target); // add phase of -i if bit was 1 (i.e. set) and flip it if (!state.test(target)) - parity ? qOdd_[state.to_ulong()] = -complexImg * qEven_[i] : qEven_[state.to_ulong()] = -complexImg * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] = -complexImg * _qEven[i] : _qEven[state.to_ulong()] = -complexImg * _qOdd[i]; // add phase of i if bit was 0 (i.e. set) and flip it else - parity ? qOdd_[state.to_ulong()] = complexImg * qEven_[i] : qEven_[state.to_ulong()] = complexImg * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] = complexImg * _qEven[i] : _qEven[state.to_ulong()] = complexImg * _qOdd[i]; } updateLayer(); } @@ -73,15 +74,15 @@ void QubitLayer::applyPauliY(int target) void QubitLayer::applyPauliZ(int target) { #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; // add phase if bit is 1 (i.e. it is set) if (state.test(target)) - parity ? qOdd_[i] = -qEven_[i] : qEven_[i] = -qOdd_[i]; + _parity ? _qOdd[i] = -_qEven[i] : _qEven[i] = -_qOdd[i]; else - parity ? qOdd_[i] = qEven_[i] : qEven_[i] = qOdd_[i]; + _parity ? _qOdd[i] = _qEven[i] : _qEven[i] = _qOdd[i]; } updateLayer(); } @@ -90,18 +91,18 @@ void QubitLayer::applyHadamard(int target) { // map |1> to -hadamardCoef*|1> and |0> to hadamardCoef*|0> #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; if (state.test(target)) - parity ? qOdd_[i] -= hadamardCoef * qEven_[i] : qEven_[i] -= hadamardCoef * qOdd_[i]; + _parity ? _qOdd[i] -= hadamardCoef * _qEven[i] : _qEven[i] -= hadamardCoef * _qOdd[i]; else - parity ? qOdd_[i] += hadamardCoef * qEven_[i] : qEven_[i] += hadamardCoef * qOdd_[i]; + _parity ? _qOdd[i] += hadamardCoef * _qEven[i] : _qEven[i] += hadamardCoef * _qOdd[i]; if (!isParallel) { state.flip(target); - parity ? qOdd_[state.to_ulong()] += hadamardCoef * qEven_[i] : qEven_[state.to_ulong()] += hadamardCoef * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] += hadamardCoef * _qEven[i] : _qEven[state.to_ulong()] += hadamardCoef * _qOdd[i]; } } if (isParallel) @@ -109,12 +110,12 @@ void QubitLayer::applyHadamard(int target) #pragma omp barrier // map |0> to hadamardCoef*|1> and |1> to hadamardCoef*|0> #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; state.flip(target); - parity ? qOdd_[state.to_ulong()] += hadamardCoef * qEven_[i] : qEven_[state.to_ulong()] += hadamardCoef * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] += hadamardCoef * _qEven[i] : _qEven[state.to_ulong()] += hadamardCoef * _qOdd[i]; } } updateLayer(); @@ -127,15 +128,15 @@ void QubitLayer::applyRx(int target, precision theta) precision sinTheta = sin(theta / 2); // map |0> to cosTheta*|0> and |1> to cosTheta*|1> #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { - parity ? qOdd_[i] += cosTheta * qEven_[i] : qEven_[i] += cosTheta * qOdd_[i]; + _parity ? _qOdd[i] += cosTheta * _qEven[i] : _qEven[i] += cosTheta * _qOdd[i]; if (!isParallel) { std::bitset state = i; state.flip(target); - parity ? qOdd_[state.to_ulong()] += -complexImg * sinTheta * qEven_[i] : qEven_[state.to_ulong()] += -complexImg * sinTheta * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] += -complexImg * sinTheta * _qEven[i] : _qEven[state.to_ulong()] += -complexImg * sinTheta * _qOdd[i]; } } if (isParallel) @@ -143,12 +144,12 @@ void QubitLayer::applyRx(int target, precision theta) #pragma omp barrier // map |1> to -isinTheta*|0> and |0> to -isineTheta*|1> #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; state.flip(target); - parity ? qOdd_[state.to_ulong()] += -complexImg * sinTheta * qEven_[i] : qEven_[state.to_ulong()] += -complexImg * sinTheta * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] += -complexImg * sinTheta * _qEven[i] : _qEven[state.to_ulong()] += -complexImg * sinTheta * _qOdd[i]; } } updateLayer(); @@ -161,20 +162,20 @@ void QubitLayer::applyRy(int target, precision theta) precision sinTheta = sin(theta / 2); // map |1> to cosTheta*|1> and |0> to cosTheta*|0> #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { - parity ? qOdd_[i] += cosTheta * qEven_[i] : qEven_[i] += cosTheta * qOdd_[i]; + _parity ? _qOdd[i] += cosTheta * _qEven[i] : _qEven[i] += cosTheta * _qOdd[i]; if (!isParallel) { std::bitset state = i; state.flip(target); // action if bit is 1 (i.e. set) if (state.test(target)) - parity ? qOdd_[state.to_ulong()] += sinTheta * qEven_[i] : qEven_[state.to_ulong()] += sinTheta * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] += sinTheta * _qEven[i] : _qEven[state.to_ulong()] += sinTheta * _qOdd[i]; // action if bit is 0 (i.e. not set) else - parity ? qOdd_[state.to_ulong()] -= sinTheta * qEven_[i] : qEven_[state.to_ulong()] -= sinTheta * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] -= sinTheta * _qEven[i] : _qEven[state.to_ulong()] -= sinTheta * _qOdd[i]; } } if (isParallel) @@ -182,17 +183,17 @@ void QubitLayer::applyRy(int target, precision theta) #pragma omp barrier // map |0> to sinTheta*|1> and |1> to -sinTheta*|0> #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; state.flip(target); // action if bit is 1 (i.e. set) if (state.test(target)) - parity ? qOdd_[state.to_ulong()] += sinTheta * qEven_[i] : qEven_[state.to_ulong()] += sinTheta * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] += sinTheta * _qEven[i] : _qEven[state.to_ulong()] += sinTheta * _qOdd[i]; // action if bit is 0 (i.e. not set) else - parity ? qOdd_[state.to_ulong()] -= sinTheta * qEven_[i] : qEven_[state.to_ulong()] -= sinTheta * qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] -= sinTheta * _qEven[i] : _qEven[state.to_ulong()] -= sinTheta * _qOdd[i]; } } updateLayer(); @@ -204,16 +205,16 @@ void QubitLayer::applyRz(int target, precision theta) precision cosTheta = cos(theta / 2); precision sinTheta = sin(theta / 2); #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; // action if bit is 1 (i.e. set) if (state.test(target)) - parity ? qOdd_[i] += cosTheta * qEven_[i] + complexImg * sinTheta * qEven_[i] : qEven_[i] += cosTheta * qOdd_[i] + complexImg * sinTheta * qOdd_[i]; + _parity ? _qOdd[i] += cosTheta * _qEven[i] + complexImg * sinTheta * _qEven[i] : _qEven[i] += cosTheta * _qOdd[i] + complexImg * sinTheta * _qOdd[i]; // action if bit is 0 (i.e. not set) else - parity ? qOdd_[i] += cosTheta * qEven_[i] - complexImg * sinTheta * qEven_[i] : qEven_[i] += cosTheta * qOdd_[i] - complexImg * sinTheta * qOdd_[i]; + _parity ? _qOdd[i] += cosTheta * _qEven[i] - complexImg * sinTheta * _qEven[i] : _qEven[i] += cosTheta * _qOdd[i] - complexImg * sinTheta * _qOdd[i]; } updateLayer(); } @@ -229,7 +230,7 @@ bool QubitLayer::checkControls(int *controls, int numControls, std::bitset state = i; @@ -258,10 +259,10 @@ void QubitLayer::applyToffoli(int control1, int control2, int target) if (state.test(control1) && state.test(control2)) { state.flip(target); - parity ? qOdd_[state.to_ulong()] = qEven_[i] : qEven_[state.to_ulong()] = qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] = _qEven[i] : _qEven[state.to_ulong()] = _qOdd[i]; } else - parity ? qOdd_[i] = qEven_[i] : qEven_[i] = qOdd_[i]; + _parity ? _qOdd[i] = _qEven[i] : _qEven[i] = _qOdd[i]; } updateLayer(); } @@ -269,7 +270,7 @@ void QubitLayer::applyToffoli(int control1, int control2, int target) void QubitLayer::applyMcnot(int *controls, int numControls, int target) { #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; @@ -277,10 +278,10 @@ void QubitLayer::applyMcnot(int *controls, int numControls, int target) if (checkControls(controls, numControls, state)) { state.flip(target); - parity ? qOdd_[state.to_ulong()] = qEven_[i] : qEven_[state.to_ulong()] = qOdd_[i]; + _parity ? _qOdd[state.to_ulong()] = _qEven[i] : _qEven[state.to_ulong()] = _qOdd[i]; } else - parity ? qOdd_[i] = qEven_[i] : qEven_[i] = qOdd_[i]; + _parity ? _qOdd[i] = _qEven[i] : _qEven[i] = _qOdd[i]; } updateLayer(); } @@ -288,15 +289,15 @@ void QubitLayer::applyMcnot(int *controls, int numControls, int target) void QubitLayer::applyCz(int control, int target) { #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; // add phase to target qubit if control bit and target bits are 1 (i.e. set) if (state.test(control) && state.test(target)) - parity ? qOdd_[i] = -qEven_[i] : qEven_[i] = -qOdd_[i]; + _parity ? _qOdd[i] = -_qEven[i] : _qEven[i] = -_qOdd[i]; else - parity ? qOdd_[i] = qEven_[i] : qEven_[i] = qOdd_[i]; + _parity ? _qOdd[i] = _qEven[i] : _qEven[i] = _qOdd[i]; } updateLayer(); } @@ -304,15 +305,15 @@ void QubitLayer::applyCz(int control, int target) void QubitLayer::applyMcphase(int *controls, int numControls, int target) { #pragma omp parallel for shared(qOdd_, qEven_) - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) if (checkZeroState(i)) { std::bitset state = i; // add phase to target qubit if control bit(s) and target bit is 1 (i.e. set) if (checkControls(controls, numControls, state) && state.test(target)) - parity ? qOdd_[i] = -qEven_[i] : qEven_[i] = -qOdd_[i]; + _parity ? _qOdd[i] = -_qEven[i] : _qEven[i] = -_qOdd[i]; else - parity ? qOdd_[i] = qEven_[i] : qEven_[i] = qOdd_[i]; + _parity ? _qOdd[i] = _qEven[i] : _qEven[i] = _qOdd[i]; } updateLayer(); } @@ -323,10 +324,10 @@ qProb QubitLayer::getMaxAmplitude() qProb result; precision currentProb{0}; precision previousProb{0}; - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) { state = i; - currentProb = parity ? abs(qEven_[i]) * abs(qEven_[i]) : abs(qOdd_[i]) * abs(qOdd_[i]); + currentProb = _parity ? abs(_qEven[i]) * abs(_qEven[i]) : abs(_qOdd[i]) * abs(_qOdd[i]); if (currentProb > previousProb) { result.state = state; @@ -339,7 +340,7 @@ qProb QubitLayer::getMaxAmplitude() void QubitLayer::toggleParity() { - parity = !parity; + _parity = !_parity; } void QubitLayer::printMeasurement() @@ -353,25 +354,25 @@ void QubitLayer::printQubits() { std::cout << "Amplitude, " << "State \n"; - for (unsigned long long int i = 0; i < numStates; i++) + for (unsigned long long int i = 0; i < _numStates; i++) { std::bitset binaryRep = i; std::string state = binaryRep.to_string(); - std::cout << qEven_[i] << " " << qOdd_[i] << " "; + std::cout << _qEven[i] << " " << _qOdd[i] << " "; std::cout << "|" << state << ">\n"; } } qubitLayer *QubitLayer::getQubitLayerEven() { - return qEven_; + return _qEven; } qubitLayer *QubitLayer::getQubitLayerOdd() { - return qOdd_; + return _qOdd; } -unsigned long long int QubitLayer::getNumStates() { return numStates; } +unsigned long long int QubitLayer::getNumStates() { return _numStates; } -unsigned int QubitLayer::getNumQubits() { return numQubits; } \ No newline at end of file +unsigned int QubitLayer::getNumQubits() { return _numQubits; } \ No newline at end of file diff --git a/src/QubitLayer.hpp b/src/QubitLayer.hpp index 9caa3f1..79ffb2c 100644 --- a/src/QubitLayer.hpp +++ b/src/QubitLayer.hpp @@ -9,23 +9,112 @@ struct qProb precision prob; }; +/** + * @class QubitLayer + * @brief Represents a QubitLayer, which is a pair of qubit states, the input and the ouptut. + */ class QubitLayer { public: + /** + * @brief Default constructor. + * Initialises the qOdd layer to the state |0...0> and the qEven layer to the state |0...01> unless an input is provided. + * + * @param[in] numQubits number of qubits. + * @param[in] qL optional pointer to the array to initialise qEven to. + */ QubitLayer(unsigned int numQubits, qubitLayer *qL = nullptr); + /** + * @brief Default destructor. + * Deletes the memory allocated for qEven and qOdd. + */ ~QubitLayer(); + /** + * @brief Applies Pauli X gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + */ void applyPauliX(int target); + /** + * @brief Applies Pauli Y gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + */ void applyPauliY(int target); + /** + * @brief Applies Pauli Z gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + */ void applyPauliZ(int target); + /** + * @brief Applies Hadamard gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + */ void applyHadamard(int target); + /** + * @brief Applies Rx gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + * @param[in] theta angle to rotate qubit along X axis. + */ void applyRx(int target, precision theta); + /** + * @brief Applies Ry gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + * @param[in] theta angle to rotate qubit along Y axis. + */ void applyRy(int target, precision theta); + /** + * @brief Applies Rz gate on a qubit. + * + * @param[in] target qubit to apply the gate to. + * @param[in] theta angle to rotate qubit along Z axis. + */ void applyRz(int target, precision theta); + /** + * @brief Applies CNOT gate on a qubit given the control qubit. + * + * @param[in] control control qubit. + * @param[in] target target qubit. + */ void applyCnot(int control, int target); + /** + * @brief Applies Toffoli gate on a qubit given the control qubits. + * + * @param[in] control1 control qubit 1. + * @param[in] control1 control qubit 2. + * @param[in] target target qubit. + */ void applyToffoli(int control1, int control2, int target); + /** + * @brief Applies MCNOT gate on a qubit given the control qubits. + * + * @param[in] controls pointer to array of control qubits. + * @param[in] numControls number of control qubits. + * @param[in] target target qubit. + */ void applyMcnot(int *controls, int numControls, int target); + /** + * @brief Applies CZ gate on a qubit given the control qubit. + * + * @param[in] control control qubit. + * @param[in] target target qubit. + */ void applyCz(int control, int target); + /** + * @brief Applies MCPHASE gate on a qubit given the control qubit. + * + * @param[in] controls pointer to array of control qubits. + * @param[in] numControls number of control qubits. + * @param[in] target target qubit. + */ void applyMcphase(int *controls, int numControls, int target); + /** + * @brief Gets the maximum amplitude of the QubitLayer. + */ qProb getMaxAmplitude(); void printMeasurement(); void printQubits(); @@ -39,11 +128,11 @@ class QubitLayer bool checkZeroState(int qubit); void updateLayer(); void toggleParity(); - unsigned int numQubits; - unsigned long long int numStates; - qubitLayer *qEven_; - qubitLayer *qOdd_; - bool parity = true; + unsigned int _numQubits; + unsigned long long int _numStates; + bool _parity = true; + qubitLayer *_qEven; + qubitLayer *_qOdd; }; #endif \ No newline at end of file