From ca20143f8bcd4b8e7f8ff916222e555756858143 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 11:59:51 +0200 Subject: [PATCH 01/31] Add AG state for Clifford+phase simulators --- .../clifford_plus_phase/ag_state.hpp | 881 ++++++++++++++++++ 1 file changed, 881 insertions(+) create mode 100644 src/simulators/clifford_plus_phase/ag_state.hpp diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp new file mode 100644 index 0000000000..dee392e993 --- /dev/null +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -0,0 +1,881 @@ +#ifndef _aer_extended_stabilizer_estimator_ag_hpp +#define _aer_extended_stabilizer_estimator_ag_hpp + +#include +#include +#include + +#include "framework/types.hpp" +#include "framework/operations.hpp" +#include "simulators/stabilizer/pauli.hpp" + +namespace AER{ +namespace CliffPhaseCompute{ + +const double T_ANGLE = M_PI/4.; +const double AG_CHOP_THRESHOLD = 1e-10; //if phases are closer to Clifford phases than this we say they are Clifford +const uint_t ONE = 1u; +/* + * We need a variant of the stabilizer tableau described in + * Improved Simulation of Stabilizer Circuits + * Scott Aaronson, Daniel Gottesman (2004) + * + * We only store the stabilizer part of the tableau because we don't need the destabilizer part + * We also add some extra subroutines they don't have which are useful for the Estimate and Compute algorithms + */ + +class AGState{ +public: + //initialize such that the jth stabilizer is the Pauli Z_j + AGState(uint_t num_qubits, uint_t num_stabilizers) : num_qubits(num_qubits), num_stabilizers(num_stabilizers) {}; + AGState() : num_qubits(0), num_stabilizers(0) {}; + AGState(uint_t num_qubits) : num_qubits(num_qubits), num_stabilizers(num_qubits) {}; + + void initialize(); + void initialize(uint_t num_qubits); + + size_t num_qubits; + size_t num_stabilizers; //we can represent mixed states so we may have fewer stabilizers than qubits + + std::vector table; + std::vector< unsigned char > phases; + + std::vector magic_phases; //each T or non-Clifford Z-rotation gate we add comes with a phase in [0, pi/4) + virtual ~AGState() = default; //TODO I'm not actually certain the default destructor is correct here + + void Print(); + + // CX controlled on a and targetted on b + void applyCX(size_t a, size_t b); + // CZ controlled on a and targetted on b + void applyCZ(size_t a, size_t b); + void applyH(size_t a); + void applyS(size_t a); + void applyX(size_t a); + void applyY(size_t a); + void applyZ(size_t a); + void applySwap(size_t a, size_t b); + //this adds a new magic qubit initialised in the |0> state and applies the CX gate required for out gadget + void gadgetized_phase_gate(size_t a, double phase); + + // "add" row i onto row h + // this group operation is equivalent to the multiplication of Pauli matrices + void rowsum(size_t h, size_t i); + + /* Does not change the table at all + * updates the "tableau row" given to be row * table_i + * returns the update to the phase of row (0 or 1) + */ + bool rowsum2(Pauli::Pauli row, bool phase, size_t i); + + //return the bool you get at index (stabilizer, column) of the matrix obtained by joining the X and Z matrix as two blocks + //i.e. if column < num_qubits return X[stabilizer][column] otherwise return Z[stabilizer][column-num_qubits] + bool tableau_element(size_t stabilizer, size_t column); + + std::pair first_non_zero_in_col(size_t col, size_t startpoint); + std::pair first_non_zero_in_row(size_t col, size_t startpoint); + + void swap_rows(size_t i, size_t j); + void delete_row(size_t i); +/* + * create a QCircuit that brings the state represented by this table to the state |0><0|^k \otimes I^(n-k) / (2^(n-k)) + * Explicitly num_stabilizers stabilisers on num_qubits qubits with the jth stabilzier being a +z on the jth qubit + * Note that in addition to returning the circuit that does the simplification this method also brings the state into the simplified form + * if you need the state after calling this method then either use the circuit to rebuild it, or make a copy + */ + std::vector simplifying_unitary(); + + /* + * attempt to find a Pauli X operator acting on qubit q in a stabilizer with an index at least a and at most this->num_stabilizer-2 (we ignore the last stabilizer) + */ + std::pair find_x_on_q(size_t q, size_t a); + /* + * attempt to find a Pauli Y operator acting on qubit q in a stabilizer with an index at least a and at most this->num_stabilizer-2 (we ignore the last stabilizer) + */ + std::pair find_y_on_q(size_t q, size_t a); + /* + * attempt to find a Pauli Z operator acting on qubit q in a stabilizer with an index at least a and at most this->num_stabilizer-2 (we ignore the last stabilizer) + */ + std::pair find_z_on_q(size_t q, size_t a); + + bool independence_test(int q); + + /* + * Apply constraints arising from the fact that we measure the first w qubits and project the last t onto T gates + * In particular we remove stabilisers if qubits [0, w) get killed by taking the expectation value <0| P |0> + * and we remove stabilisers if qubits in [w, this->num_qubits-t) aren't the identity + * we do not remove any qubits from the table + */ + std::pair apply_constraints(size_t w, size_t t); + size_t apply_T_constraints(); + /* + * Go through our stabilizer table and delete every qubit for which every stabilizer is the identity on that qubit + * In other words delete every column from the X and Z matrices if both are 0 for every element in that column + * intended only for use when we have restricted our table to only have magic qubits + * also deletes magic_phases elements to reflect deletion of the magic qubits + */ + void delete_identity_magic_qubits(); + +}; + +// Implementation +void AGState::initialize() +{ + this->table = std::vector(); + + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->table.push_back(Pauli::Pauli(num_qubits)); + this->table[i].Z.set1(i); + this->phases.push_back(0); + } +} + +// Implementation +void AGState::initialize(uint_t num_qubits) +{ + this->num_qubits = num_qubits; + this->num_stabilizers = num_qubits; + this->initialize(); +} + + +void AGState::Print(){ + for(size_t i = 0; i < this->num_stabilizers; i++){ + for(size_t j = 0; j < this->num_qubits; j++){ + std::cout << this->table[i].X[j]; + } + std::cout << "|"; + for(size_t j = 0; j < this->num_qubits; j++){ + std::cout << this->table[i].Z[j]; + } + std::cout << " " << this->phases[i] << std::endl; + } +} + +void AGState::applyCX(size_t a, size_t b){ + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->phases[i] ^= this->table[i].X[a] & this->table[i].Z[a] & (this->table[i].X[b] ^ this->table[i].Z[a] ^ true); + this->table[i].X.xorAt(this->table[i].X[a], b); + this->table[i].Z.xorAt(this->table[i].Z[b], a); + } +} + +void AGState::applyCZ(size_t a, size_t b){ + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->phases[i] ^= this->table[i].X[a] & this->table[i].X[b] & (this->table[i].Z[a] ^ this->table[i].Z[b]); + this->table[i].Z.xorAt(this->table[i].X[b], a); + this->table[i].Z.xorAt(this->table[i].X[a], b); + } +} + +void AGState::applySwap(size_t a, size_t b){ + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->table[i].X.xorAt(this->table[i].X[a], b); + this->table[i].X.xorAt(this->table[i].X[b], a); + this->table[i].X.xorAt(this->table[i].X[a], b); + + this->table[i].Z.xorAt(this->table[i].Z[b], a); + this->table[i].Z.xorAt(this->table[i].Z[a], b); + this->table[i].Z.xorAt(this->table[i].Z[b], a); + } +} + +void AGState::applyH(size_t a){ + bool scratch; + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->phases[i] ^= this->table[i].X[a] & this->table[i].Z[a]; + scratch = this->table[i].X[a]; + this->table[i].X.setValue(this->table[i].Z[a], a); + this->table[i].Z.setValue(scratch, a); + } +} + +void AGState::applyS(size_t a){ + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->phases[i] ^= this->table[i].X[a] & this->table[i].Z[a]; + this->table[i].Z.xorAt(this->table[i].X[a], a); + } +} + +void AGState::applyX(size_t a){ + //TODO write a proper X implementation + this->applyH(a); + this->applyS(a); + this->applyS(a); + this->applyH(a); +} + +void AGState::applyY(size_t a){ + //TODO write a proper Y implementation + this->applyS(a); + this->applyX(a); + this->applyS(a); + this->applyS(a); + this->applyS(a); +} + +void AGState::applyZ(size_t a){ + //TODO write a proper Z implementation + this->applyS(a); + this->applyS(a); +} + +void AGState::gadgetized_phase_gate(size_t a, double phase){ + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->table[i].X.resize(this->num_qubits + 1); + this->table[i].Z.resize(this->num_qubits + 1); + } + this->table.push_back(Pauli::Pauli(this->num_qubits + 1)); + this->table[this->num_stabilizers].Z.set1(this->num_qubits); + this->num_stabilizers += 1; + this->num_qubits += 1; + + + + phase = fmod(phase , M_PI*2); + if(phase < 0){ + phase += M_PI*2; + } + + //now phase is in [0, 2*pi) + while(phase > M_PI/2){ + phase -= M_PI/2; + this->applyS(a); + } + //now phase is in [0, M_PI/2.] + + if(fabs(phase) < AG_CHOP_THRESHOLD){ + //phase on gate is zero so it is an identity + }else if(fabs(phase - M_PI/2.) < AG_CHOP_THRESHOLD){ + //phase on gate is pi/2 so it is an S + this->applyS(a); + this->applyCX(a, this->num_stabilizers-1); + }else{ + //its actually non-Clifford + //we want our phases to be in [0, pi/4] + if(phase > T_ANGLE){ + phase -= M_PI/2 - phase; + this->applyX(a); + this->applyCX(a, this->num_stabilizers-1); + this->applyX(a); + }else{ + this->applyCX(a, this->num_stabilizers-1); + } + } + this->magic_phases.push_back(phase); +} + +void AGState::rowsum(size_t h, size_t i){ + unsigned char sum = 2*(this->phases[h] + this->phases[i]) + Pauli::Pauli::phase_exponent(this->table[i], this->table[h]); + + sum %= 4u; + + if(sum == 0){ + this->phases[h] = false; + } + if(sum == 2){ + this->phases[h] = true; + } + + this->table[h].X += this->table[i].X; + this->table[h].Z += this->table[i].Z; + +} +bool AGState::rowsum2(Pauli::Pauli row, bool phase, size_t i){ + unsigned char sum = 2*(phase + this->phases[i]) + Pauli::Pauli::phase_exponent(this->table[i], row); + sum %= 4u; + row.X += this->table[i].X; + row.Z += this->table[i].Z; + + if(sum == 0){ + return false; + } + if(sum == 2){ + return true; + } + + //we should never reach here - maybe printing a warning or throwing an exception would be sensible + return false; +} + + +void AGState::delete_identity_magic_qubits(){ + //indended for use when we've already restricted our table to only contain the magic qubits + size_t qubits_deleted = 0; + for(size_t q = 0; q < this->num_qubits; q++){ + size_t non_identity_paulis = 0; + for(size_t s = 0; (s < this->num_stabilizers) && (non_identity_paulis == 0); s++){ + if(this->table[s].X[q] || this->table[s].Z[q]){ + non_identity_paulis += 1; + } + } + if(non_identity_paulis == 0){ + //every stabiliser is identity on this qubit + //so we can just delete this qubit + qubits_deleted += 1; + }else{ + if(qubits_deleted > 0){ + for(size_t s = 0; s < this->num_stabilizers; s++){ + this->table[s].X.setValue(this->table[s].X[q], q-qubits_deleted); + this->table[s].Z.setValue(this->table[s].Z[q], q-qubits_deleted); + } + magic_phases[q - qubits_deleted] = magic_phases[q]; + } + } + } + this->num_qubits = this->num_qubits - qubits_deleted; + for(size_t s = 0; s < this->num_stabilizers; s++){ + this->table[s].X.resize(this->num_qubits); + this->table[s].Z.resize(this->num_qubits); + } + magic_phases.resize(magic_phases.size() - qubits_deleted); + this->num_qubits -= qubits_deleted; +} + +bool AGState::tableau_element(size_t stabilizer, size_t column){ + if(column < this->num_qubits){ + return this->table[stabilizer].X[column]; + }else{ + return this->table[stabilizer].Z[column-num_qubits]; + } +} + +std::pair AGState::first_non_zero_in_col(size_t col, size_t startpoint){ + for(size_t i = startpoint; i < this->num_stabilizers; i++){ + if(this->tableau_element(i, col)){ + return std::pair(true, i); + } + } + return std::pair(false, 0); +} +std::pair AGState::first_non_zero_in_row(size_t row, size_t startpoint){ + for(size_t i = startpoint; i < 2*this->num_qubits; i++){ + if(this->tableau_element(row, i) != 0){ + return std::pair(true, i); + } + } + + return std::pair(false, 0); +} + +void AGState::swap_rows(size_t i, size_t j){ + + //this->table[i].X.swap(this->table[j].X); + //this->table[i].Z.swap(this->table[j].Z); + std::swap(this->table[i], this->table[j]); + std::swap(this->phases[i], this->phases[j]); +} + +void AGState::delete_row(size_t i){ + this->table.erase(this->table.begin() + i); + this->phases.erase(this->phases.begin() + i); + this->num_stabilizers -= 1; +} + +Operations::Op make_H(uint_t a){ + return {.type=Operations::OpType::gate, .name="h", .qubits={a}}; +} + +Operations::Op make_S(uint_t a){ + return {.type=Operations::OpType::gate, .name="s", .qubits={a}}; +} + +Operations::Op make_CX(uint_t a, uint_t b){ + return {.type=Operations::OpType::gate, .name="cx", .qubits={a, b}}; +} + +Operations::Op make_CZ(uint_t a, uint_t b){ + return {.type=Operations::OpType::gate, .name="cz", .qubits={a, b}}; +} + + +std::vector AGState::simplifying_unitary(){ + std::vector circuit; + //first we do "augmented gaussian elimination" on the circuit + //augmented here means that if we don't find a pivot in our column + //we will hadamard that qubit and try again + //in other words bring in a pivot from the z part if necessary + + size_t h = 0; + size_t k = 0; + while(h < this->num_stabilizers && k < this->num_qubits){ + std::pair poss_pivot = this->first_non_zero_in_col(k, h); + if(!poss_pivot.first){ + this->applyH(k); + circuit.push_back(make_H(k)); + poss_pivot = this->first_non_zero_in_col(k, h); + } + if(!poss_pivot.first){ + k += 1; + }else{ + size_t pivot = poss_pivot.second; //now known to exist + if(pivot != h){ + //swap rows h and pivot of the table + this->swap_rows(h,pivot); + } + for(size_t j = 0; j < this->num_stabilizers; j++){ + if((j != h) && this->table[j].X[k]){ + this->rowsum(j,h); + } + } + h += 1; + k += 1; + } + } + + //so now we have a reduced row echelon form with the X part of the table having full rank + + //we swap columns (using CX) to make the X part into a kxk identity followed by a "junk" block + + for(int r = 0; r < this->num_stabilizers; r++){ + if(!this->table[r].X[r]){ + size_t col = this->first_non_zero_in_row(r, 0).second; + + this->applyCX(r, col); + circuit.push_back(make_CX(r,col)); + this->applyCX(col, r); + circuit.push_back(make_CX(col,r)); + this->applyCX(r, col); + circuit.push_back(make_CX(r,col)); + } + } + + + //now we use CX to clear out the "junk" block at the end of the X table + for(size_t r = 0; r < this->num_stabilizers; r++){ + for(size_t col = this->num_stabilizers; col < this->num_qubits; col++){ + if(this->table[r].X[col]){ + this->applyCX(r, col); + circuit.push_back(make_CX(r,col)); + } + } + } + + //now we clear the leading diagonal of the z block + for(size_t r = 0; r < this->num_stabilizers; r++){ + if(this->table[r].Z[r]){ + this->applyS(r); + circuit.push_back(make_S(r)); + } + } + + //clear out the last k x (n-k) block of the z matrix + for(size_t col = this->num_stabilizers; col < this->num_qubits; col++){ + for(size_t r = 0; r < this->num_stabilizers; r++){ + if(this->table[r].Z[col]){ + this->applyCZ(r, col); + circuit.push_back(make_CZ(r, col)); + } + } + } + + //clear out the first k x k block of the Z matrix, using that it is symmetric + for(size_t col = 0; col < this->num_stabilizers; col++){ + for(size_t r = 0; r < col; r++){ + if(this->table[r].Z[col]){ + this->applyCZ(r, col); + circuit.push_back(make_CZ(r, col)); + } + } + } + + //fix the phases + for(size_t r = 0; r < this->num_stabilizers; r++){ + if(this->phases[r]){ + this->applyS(r); + circuit.push_back(make_S(r)); + this->applyS(r); + circuit.push_back(make_S(r)); + } + } + + //swap the identity matrix to the z part + for(size_t r = 0; r < this->num_stabilizers; r++){ + this->applyH(r); + circuit.push_back(make_H(r)); + } + + return circuit; +} + +std::pair AGState::find_x_on_q(size_t q, size_t a){ + for(size_t row = a; row < this->num_stabilizers-1; row++){ + if(this->table[row].X[q] && !this->table[row].Z[q]){ + return std::pair(true, row); + } + } + return std::pair(false, 0); +} + +std::pair AGState::find_y_on_q(size_t q, size_t a){ + for(size_t row = a; row < this->num_stabilizers-1; row++){ + if(this->table[row].X[q] && this->table[row].Z[q]){ + return std::pair(true, row); + } + } + return std::pair(false, 0); +} + +std::pair AGState::find_z_on_q(size_t q, size_t a){ + for(size_t row = a; row < this->num_stabilizers-1; row++){ + if(!this->table[row].X[q] && this->table[row].Z[q]){ + return std::pair(true, row); + } + } + return std::pair(false, 0); +} + +/* + * we test that if you ignore the first q+1 qubits whether that last (n-q-1) part of the last stabiliser in the table can be generated (up to phase) + * by the other stabilisers + * for use in the apply_constraints code + */ +bool AGState::independence_test(int q){ + //we basically do gaussian elimination + + size_t a = 0; + size_t b = q+1; + while(a < this->num_stabilizers-1 && b < this->num_qubits){ + std::pair x = this->find_x_on_q(b, a); + std::pair y = this->find_y_on_q(b, a); + std::pair z = this->find_z_on_q(b, a); + + if(y.first && x.first){ + this->rowsum(y.second, x.second); + z = y; + y = std::pair(false,0); + } + if(y.first && z.first){ + this->rowsum(y.second, z.second); + x = y; + y = std::pair(false,0); + } + + if(x.first){ + if(x.second != a){ + this->swap_rows(a, x.second); + } + if(z.first && (z.second == a)){ + z = x; + } + for(size_t j = 0; j < this->num_stabilizers; j++){ + if((j != a) && this->table[j].X[b]){ + this->rowsum(j,a); + } + } + a += 1; + } + if(y.first){ + if(y.second != a){ + this->swap_rows(a,y.second); + } + for(size_t j = 0; j < this->num_stabilizers; j++){ + if((j != a) && this->table[j].X[b] && this->table[j].Z[b]){ + this->rowsum(j,a); + } + } + a += 1; + } + if(z.first){ + if(z.second != a){ + this->swap_rows(a,z.second); + } + for(size_t j = 0; j < this->num_stabilizers; j++){ + if((j != a) && this->table[j].Z[b]){ + this->rowsum(j,a); + } + } + a += 1; + } + b += 1; + } + + for(size_t p = q+1; p < this->num_qubits; p++){ + if((this->table[this->num_stabilizers-1].X[p] == 1) || (this->table[this->num_stabilizers-1].Z[p] == 1)){ + return true; + } + } + return false; +} + + +/* + * Apply constraints arising from the fact that we measure the first w qubits and project the last t onto T gates + * In particular we kill stabilisers if qubits [0, w) get killed by taking the expectation value <0| P |0> + * and we kill stabilisers if qubits in [w, table->n-t) aren't the identity + * we do not remove any qubits from the table + * note that it is possible for the region a constraints to be "inconsistent" - this means that the probability we were calculating is zero + * in that case we return pair(false, 0) + * in other cases we return pair(true, v) where v is as defined in the paper + */ +std::pair AGState::apply_constraints(size_t w, size_t t){ + size_t v = 0; + + //first apply region a constraints (measurement) + for(size_t q=0; q < w; q++){ //iterate over all the measured qubits + std::pair y_stab = std::pair(false, 0); //store the index of the first stab we come to with both x and z = 1 on this qubit + std::pair x_stab = std::pair(false, 0); //store the index of the first stab we come to with x=1, z=0 + std::pair z_stab = std::pair(false, 0); //store the index of the first stab we come to with z=1, x=0 + + for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabilisers + if(this->table[s].X[q] && this->table[s].Z[q]){ + y_stab.first = true; + y_stab.second = s; + } + if(this->table[s].X[q] && !this->table[s].Z[q]){ + x_stab.first = true; + x_stab.second = s; + } + if(!this->table[s].X[q] && this->table[s].Z[q]){ + z_stab.first = true; + z_stab.second = s; + } + } + + //there are several cases here + //either a single z, a single x, a single y or we can generate the whole Pauli group on this qubit + + //case 1) we generate the whole group + //put things in standard form (first stab is x then z) + if((y_stab.first + x_stab.first + z_stab.first) >= 2){ //we have at least two of the set + if(!x_stab.first){//we don't have a generator for x alone, but we can make one + this->rowsum(y_stab.second, z_stab.second); + //now we have a z and an x but not a y + x_stab = y_stab; + y_stab = std::pair(false,0); + }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one + this->rowsum(y_stab.second, x_stab.second); + //now we have a z and an x but not a y + z_stab = y_stab; + y_stab = std::pair(false,0); + } + } + + if(y_stab.first && x_stab.first && z_stab.first){ //we have all 3 + //ignore the y one if we have all 3 + y_stab = std::pair(false,0); + } + + //now the only possibilities are that we have an an x, y or z or both an x and a z + //zero everything else on this qubit + for(size_t s = 0; s < this->num_stabilizers; s++){ + if((!y_stab.first || s != y_stab.second) && (!x_stab.first || s != x_stab.second) && (!z_stab.first || s != z_stab.second)){ + if(this->table[s].X[q] && this->table[s].Z[q] && y_stab.first){ + this->rowsum(s, y_stab.second); + } + + if(this->table[s].X[q]){ + this->rowsum(s, x_stab.first); + } + + if(this->table[s].Z[q]){ + this->rowsum(s, z_stab.first); + } + } + } + + //case 1 - there is a generator which does not commute with Z_q + if(y_stab.first || x_stab.first){ + //we can't have both >= 0 + size_t non_commuting_generator = y_stab.first ? y_stab.second : x_stab.second; + + //we delete the non-commuting guy + this->swap_rows(non_commuting_generator, this->num_stabilizers-1); + this->delete_row(this->num_stabilizers-1); + + }else{ + //case 2 - all generators commute with Z_q + //our generating set contains either Z_q or -Z_q + //we need to work out which one it is + + //swap our Z_q guy to the end + this->swap_rows(z_stab.second, this->num_stabilizers-1); + + bool independent = this->independence_test(q); + + if(!independent){ + if(this->phases[this->num_stabilizers-1] == 0){ + // +Z_q + v += 1; + this->delete_row(this->num_stabilizers-1); + }else{ + //our chosen measurement outcome is impossible + return std::pair(false,0); + } + }else{ + //if we get here there has been an error + //TODO decide if we're going to throw an exception or print an error message here + } + } + } + + //time to impose region b constraints + + for(size_t q=w; q < this->num_qubits - t ; q++){ //iterate over all the non-measured non-magic qubits + std::pair y_stab = std::pair(false,0); //store the index of the first stab we come to with both x and z = 1 on this qubit + std::pair x_stab = std::pair(false,0); //store the index of the first stab we come to with x=1, z=0 + std::pair z_stab = std::pair(false,0); //store the index of the first stab we come to with z=1, x=0 + + for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabilisers + if(this->table[s].X[q] && this->table[s].Z[q]){ + y_stab.first = true; + y_stab.second = s; + } + if(this->table[s].X[q] && !this->table[s].Z[q]){ + x_stab.first = true; + x_stab.second = s; + } + if(!this->table[s].X[q] && this->table[s].Z[q]){ + z_stab.first = true; + z_stab.second = s; + } + } + + //there are several cases here + //either a single z, a single x, a single y or we can generate the whole Pauli group on this qubit + + //case 1) we generate the whole group + //put things in standard form (first stab is x then z) + if((y_stab.first + x_stab.first + z_stab.first) >= 2){ //we have at least two of the set + if(!x_stab.first){//we don't have a generator for x alone, but we can make one + this->rowsum(y_stab.second, z_stab.second); + //now we have a z and an x but not a y + x_stab = y_stab; + y_stab = std::pair(false,0); + }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one + this->rowsum(y_stab.second, x_stab.second); + //now we have a z and an x but not a y + z_stab = y_stab; + y_stab = std::pair(false,0); + } + } + + if(y_stab.first && x_stab.first && z_stab.first){ //we have all 3 + //ignore the y one if we have all 3 + y_stab = std::pair(false,0); + } + + //now the only possibilities are that we have an x_and_z, an x a z or an x and a z + //zero everything else on this qubit + for(size_t s = 0; s < this->num_stabilizers; s++){ + if((!y_stab.first || s != y_stab.second) && (!x_stab.first || s != x_stab.second) && (!z_stab.first || s != z_stab.second)){ + if(this->table[s].X[q] && this->table[s].Z[q] && y_stab.first){ + this->rowsum(s, y_stab.second); + } + + if(this->table[s].X[q]){ + this->rowsum(s, x_stab.second); + } + + if(this->table[s].Z[q]){ + this->rowsum(s, z_stab.second); + } + } + } + + //now we just delete the non-identity guys on this qubit + + int num_to_delete = 0; + if(y_stab.first){ + //if we have a Y stab we don't have either of the others + this->swap_rows(y_stab.second, this->num_stabilizers-1); + num_to_delete += 1; + }else{ + if(x_stab.first){ + this->swap_rows(x_stab.second, this->num_stabilizers-1); + if(z_stab.first && this->num_stabilizers - 1 == z_stab.second){ + z_stab = x_stab; + } + num_to_delete += 1; + } + if(z_stab.first){ + this->swap_rows(z_stab.second, this->num_stabilizers-1-num_to_delete); + num_to_delete += 1; + } + } + //delete the last num_to_delete rows + //TODO should we implement an erase method that works like the std::vector one so you can pass a range? + for(size_t deletes = 0; deletes < num_to_delete; deletes++){ + this->delete_row(this->num_stabilizers-1); + } + } + + return std::pair(true, v); +} + + +/* + * Our magic states are equatorial + * so = 0 + * here we delete any stabilisers with a Z in the magic region + * which we assume now all the qubits + * we return the number of qubits which have identities on them in every generator after this deletion + */ +size_t AGState::apply_T_constraints(){ + size_t starting_rows = this->num_stabilizers; + size_t deleted_rows = 0; + for(size_t reps = 0; reps < starting_rows; reps++){ + for(size_t q=0; q < this->num_qubits; q++){ //iterate over all the magic qubits + std::pair y_stab = std::pair(false,0); //store the index of the first stab we come to with both x and z = 1 on this qubit + std::pair x_stab = std::pair(false,0); //store the index of the first stab we come to with x=1, z=0 + std::pair z_stab = std::pair(false,0); //store the index of the first stab we come to with z=1, x=0 + + for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabi lisers + if(this->table[s].X[q] && this->table[s].Z[q]){ + y_stab.first = true; + y_stab.second = s; + } + if(this->table[s].X[q] && !this->table[s].Z[q]){ + x_stab.first = true; + x_stab.second = s; + } + if(!this->table[s].X[q] && this->table[s].Z[q]){ + z_stab.first = true; + z_stab.second = s; + } + } + + //there are several cases here + //either a single z, a single x, a single y or we can generate the whole Pauli group on this qubit + + //case 1) we generate the whole group + //put things in standard form (first stab is x then z) + if((y_stab.first + x_stab.first + z_stab.first) >= 2){ //we have at least two of the set + if(!x_stab.first){//we don't have a generator for x alone, but we can make one + this->rowsum(y_stab.second, z_stab.second); + //now we have a z and an x but not a y + x_stab = y_stab; + y_stab = std::pair(false,0); + }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one + this->rowsum(y_stab.second, x_stab.second); + //now we have a z and an x but not a y + z_stab = y_stab; + y_stab = std::pair(false,0); + } + } + + if(y_stab.first && x_stab.first && z_stab.first){ //we have all 3 + //ignore the y one if we have all 3 + y_stab = std::pair(false,0); + } + + if(z_stab.first && !x_stab.first){ + //kill all other z stuff on this qubit + for(size_t s = 0; s < this->num_qubits; s++){ + if((s != z_stab.second) && this->table[s].Z[q]){ + this->rowsum(s, z_stab.second); + } + } + //now delete the z guy + this->swap_rows(z_stab.second, this->num_stabilizers-1); + this->delete_row(this->num_stabilizers-1); + deleted_rows += 1; + } + } + } + return deleted_rows; +} + +} +} +#endif From da04866e60a0f3f3a9ba8df36b1c0d1c7e520f04 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 12:05:02 +0200 Subject: [PATCH 02/31] Add resize method to binary_vector class --- src/simulators/stabilizer/binary_vector.hpp | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/simulators/stabilizer/binary_vector.hpp b/src/simulators/stabilizer/binary_vector.hpp index a982097c17..94a7998b1e 100644 --- a/src/simulators/stabilizer/binary_vector.hpp +++ b/src/simulators/stabilizer/binary_vector.hpp @@ -46,6 +46,10 @@ class BinaryVector { void setLength(uint64_t length); + //preserves the vector up to the new length + //and pads with zeros if necessary + void resize(uint64_t length); + void setVector(std::string); void setValue(bool value, uint64_t pos); @@ -180,6 +184,16 @@ void BinaryVector::setLength(uint64_t length) { m_data.assign((length - 1) / BLOCK_SIZE + 1, ZERO_); } +void BinaryVector::resize(uint64_t new_length) { + m_data.resize((new_length - 1) / BLOCK_SIZE + 1, ZERO_); + //zero the rest of the last block if necessary + if((new_length < m_length) && (new_length % BLOCK_SIZE) > 0){ + for(size_t i = (new_length % BLOCK_SIZE) ; i < BLOCK_SIZE; i++){ + m_data[m_data.size() - 1] &= ~(ONE_ << i); + } + } + m_length = new_length; +} void BinaryVector::setValue(bool value, uint64_t pos) { auto q = pos / BLOCK_SIZE; @@ -297,4 +311,4 @@ std::vector BinaryVector::nonzeroIndices() const { //------------------------------------------------------------------------------ } // end namespace BV //------------------------------------------------------------------------------ -#endif \ No newline at end of file +#endif From 73f94b5e4ef3ac3ba6694b025bca5449c8144a61 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 12:06:31 +0200 Subject: [PATCH 03/31] add += method to Pauli::Pauli class --- src/simulators/stabilizer/pauli.hpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/simulators/stabilizer/pauli.hpp b/src/simulators/stabilizer/pauli.hpp index ecf0ec5673..0bb10c34f1 100644 --- a/src/simulators/stabilizer/pauli.hpp +++ b/src/simulators/stabilizer/pauli.hpp @@ -46,6 +46,8 @@ class Pauli { // exponent g of i such that P(x1,z1) P(x2,z2) = i^g P(x1+x2,z1+z2) static int8_t phase_exponent(const Pauli& pauli1, const Pauli& pauli2); + + Pauli &operator+=(const Pauli &rhs); }; @@ -114,6 +116,12 @@ int8_t Pauli::phase_exponent(const Pauli& pauli1, const Pauli& pauli2) { return exponent; } +Pauli &Pauli::operator+=(const Pauli &rhs) { + this->X += rhs.X; + this->Z += rhs.Z; + return (*this); +} + //------------------------------------------------------------------------------ } // end namespace Pauli //------------------------------------------------------------------------------ From dc6963cd8e37d2781d94df00b5743b83f0f9e6fc Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 12:29:04 +0200 Subject: [PATCH 04/31] Add save_specific_probability operation/instruction --- .../save_instructions/save_probabilities.py | 60 +++++++++++++++++++ src/framework/operations.hpp | 7 ++- 2 files changed, 65 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index caabbe53c6..675059e8ab 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -73,6 +73,33 @@ def __init__(self, pershot=pershot, conditional=conditional) +class SaveSpecificProbability(SaveAverageData): + """Save measurement outcome probabilities vector.""" + def __init__(self, num_qubits, + states, qubits, + label="specific-probabilities", + unnormalized=False, + pershot=False, + conditional=False): + """Instruction to save specific probabilities. + + Args: + num_qubits (int): the number of qubits for the snapshot type. + label (str): the key for retrieving saved data from results. + unnormalized (bool): If True return save the unnormalized accumulated + probabilities over all shots [Default: False]. + pershot (bool): if True save a list of probabilities for each shot + of the simulation rather than the average over + all shots [Default: False]. + conditional (bool): if True save the probabilities data conditional + on the current classical register values + [Default: False]. + """ + + super().__init__("save_specific_prob", num_qubits, label, + pershot=pershot, + conditional=conditional, + params=[qubits, states]) def save_probabilities(self, qubits=None, @@ -139,6 +166,39 @@ def save_probabilities_dict(self, conditional=conditional) return self.append(instr, qubits) +def save_specific_probability(self, states, qubits, label="specific_probability", + unnormalized=False, + pershot=False, + conditional=False): + """Save squared statevector amplitudes (probabilities). + + Args: + states (List[int] or List[str]): the basis states to return amplitudes for. + qubits (List[int] or List[str]): the qubits to return amplitudes for. + label (str): the key for retrieving saved data from results. + unnormalized (bool): If True return save the unnormalized accumulated + probabilities over all shots [Default: False]. + pershot (bool): if True save a list of probability vectors for each + shot of the simulation rather than the a single + amplitude vector [Default: False]. + conditional (bool): if True save the probability vector conditional + on the current classical register values + [Default: False]. + + Returns: + QuantumCircuit: with attached instruction. + + Raises: + ExtensionError: if params is invalid for the specified number of qubits. + """ + if qubits == None: + qubits = default_qubits(self) + instr = SaveSpecificProbability(len(qubits), states, qubits, label=label, + unnormalized=unnormalized, + pershot=pershot, + conditional=conditional) + return self.append(instr, qubits) QuantumCircuit.save_probabilities = save_probabilities QuantumCircuit.save_probabilities_dict = save_probabilities_dict +QuantumCircuit.save_specific_probability = save_specific_probability diff --git a/src/framework/operations.hpp b/src/framework/operations.hpp index d84547c7fc..c8c2bd57c2 100755 --- a/src/framework/operations.hpp +++ b/src/framework/operations.hpp @@ -44,7 +44,7 @@ enum class OpType { // Save instructions save_state, save_expval, save_expval_var, save_statevec, save_statevec_dict, save_densmat, save_probs, save_probs_ket, save_amps, save_amps_sq, - save_stabilizer, save_unitary, save_mps, save_superop, + save_stabilizer, save_unitary, save_mps, save_superop, save_specific_prob, // Set instructions set_statevec, set_densmat, set_unitary, set_superop, set_stabilizer, set_mps @@ -59,7 +59,7 @@ static const std::unordered_set SAVE_TYPES = { OpType::save_statevec, OpType::save_statevec_dict, OpType::save_densmat, OpType::save_probs, OpType::save_probs_ket, OpType::save_amps, OpType::save_amps_sq, OpType::save_stabilizer, - OpType::save_unitary, OpType::save_mps, OpType::save_superop + OpType::save_unitary, OpType::save_mps, OpType::save_superop, OpType::save_specific_prob }; inline std::ostream& operator<<(std::ostream& stream, const OpType& type) { @@ -120,6 +120,9 @@ inline std::ostream& operator<<(std::ostream& stream, const OpType& type) { case OpType::save_superop: stream << "save_superop"; break; + case OpType::save_specific_prob: + stream << "save_specific_prob"; + break; case OpType::set_statevec: stream << "set_statevector"; break; From 38141f8df55f8b423e2871e06c235acb89bae773 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 13:34:42 +0200 Subject: [PATCH 05/31] Add compute state class --- .../clifford_plus_phase/compute.hpp | 352 ++++++++++++++++++ 1 file changed, 352 insertions(+) create mode 100644 src/simulators/clifford_plus_phase/compute.hpp diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp new file mode 100644 index 0000000000..e00cbf852d --- /dev/null +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -0,0 +1,352 @@ +#ifndef _aer_extended_stabilizer_compute_hpp +#define _aer_extended_stabilizer_compute_hpp + +#define _USE_MATH_DEFINES +#include + +#include +#include +#include + +#include "simulators/state.hpp" +#include "framework/json.hpp" +#include "framework/types.hpp" + +#include "ag_state.hpp" +#include "simulators/stabilizer/pauli.hpp" + +namespace AER{ +namespace CliffPhaseCompute{ + +// OpSet of supported instructions +const Operations::OpSet StateOpSet( + // Op types + {Operations::OpType::gate, Operations::OpType::save_specific_prob}, + // Gates + {"CX", "cx", "cz", "swap", "id", "x", "y", "z", "h", + "s", "sdg", "t"}, + // Snapshots + {} +); + + + +enum class Gates { + id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, +}; + + +using agstate_t = CliffPhaseCompute::AGState; + + +class State: public Base::State{ +public: + using BaseState = Base::State; + + State() : BaseState(StateOpSet) {}; + virtual ~State() = default; + + std::string name() const override {return "clifford_phase_compute";} + + //Apply a sequence of operations to the cicuit. + //We just store these operations in _circuit because we can't implement them + //until we know how many non-Clifford gates and what the measurements are + virtual void apply_ops(const std::vector &ops, + ExperimentResult &result, + RngEngine &rng, + bool final_ops = false) override; + + void apply_gate(const Operations::Op &op); + + void initialize_qreg(uint_t num_qubits) override; + void initialize_qreg(uint_t num_qubits, const agstate_t &state) override; + size_t required_memory_mb(uint_t num_qubits, const std::vector &ops) const override; + + void apply_save_specific_probs(const Operations::Op &op, ExperimentResult &result); + double expval_pauli(const reg_t &qubits, const std::string& pauli); +private: + const static stringmap_t gateset_; + const static stringmap_t snapshotset_; + size_t num_code_qubits; //out AG state has code+magic qubits + double compute_probability(std::vector measured_qubits, std::vector outcomes); + //void apply_save_specific_probs(const Operations::Op &op, ExperimentResult &result); + //void apply_snapshot(const Operations::Op &ops, ExperimentResult &result); + //void probabilities_snapshot(const Operations::Op &op, ExperimentResult &result); +}; + +const stringmap_t State::snapshotset_({ + {"probabilities", Snapshots::probs}, +}); + + +const stringmap_t State::gateset_({ + // Single qubit gates + {"delay", Gates::id}, // Delay gate + {"id", Gates::id}, // Pauli-Identity gate + {"x", Gates::x}, // Pauli-X gate + {"y", Gates::y}, // Pauli-Y gate + {"z", Gates::z}, // Pauli-Z gate + {"s", Gates::s}, // Phase gate (aka sqrt(Z) gate) + {"sdg", Gates::sdg}, // Conjugate-transpose of Phase gate + {"h", Gates::h}, // Hadamard gate (X + Z / sqrt(2)) + {"t", Gates::t}, // T-gate (sqrt(S)) + {"tdg", Gates::tdg}, // Conjguate-transpose of T gate + // Two-qubit gates + {"CX", Gates::cx}, // Controlled-X gate (CNOT) + {"cx", Gates::cx}, // Controlled-X gate (CNOT) + {"CZ", Gates::cz}, // Controlled-Z gate + {"cz", Gates::cz}, // Controlled-Z gate + {"swap", Gates::swap}, // SWAP gate + // Three-qubit gates +}); + + +void State::apply_ops(const std::vector &ops, ExperimentResult &result, RngEngine &rng, bool final_ops){ + std::cout << "2" << std::endl; + for(const auto &op: ops){ + std::cout << op.type << std::endl; + switch(op.type){ + case Operations::OpType::gate: + std::cout << "Operations::OpType::gate" << std::endl; + this->apply_gate(op); + break; + case Operations::OpType::save_specific_probs: + std::cout << "Operations::OpType::save_specific_probs" << std::endl; + this->apply_save_specific_probs(op, result); + break; + default: + throw std::invalid_argument("Compute::State::invalid instruction \'" + op.name + "\'."); + } + } +} + +void State::apply_gate(const Operations::Op &op){ + std::cout << "State::apply_gate " << op.name << ": " << op.qubits[0] << std::endl; + auto it = gateset_.find(op.name); + if (it == gateset_.end()) + { + throw std::invalid_argument("Compute::State: Invalid gate operation \'" + +op.name + "\'."); + } + switch(it->second){ + case Gates::id: + break; + case Gates::x: + this->qreg_.applyX(op.qubits[0]); + break; + case Gates::y: + this->qreg_.applyY(op.qubits[0]); + break; + case Gates::z: + this->qreg_.applyZ(op.qubits[0]); + break; + case Gates::s: + this->qreg_.applyS(op.qubits[0]); + break; + case Gates::sdg: + this->qreg_.applyZ(op.qubits[0]); + this->qreg_.applyS(op.qubits[0]); + break; + case Gates::h: + this->qreg_.applyH(op.qubits[0]); + break; + case Gates::t: + this->qreg_.gadgetized_phase_gate(op.qubits[0], T_ANGLE); + break; + case Gates::tdg: + this->qreg_.gadgetized_phase_gate(op.qubits[0], -T_ANGLE); + break; + case Gates::cx: + this->qreg_.applyCX(op.qubits[0],op.qubits[1]); + break; + case Gates::cz: + this->qreg_.applyCZ(op.qubits[0],op.qubits[1]); + break; + case Gates::swap: + this->qreg_.applySwap(op.qubits[0],op.qubits[1]); + break; + default: //u0 or Identity + break; + } +} + +void State::apply_save_specific_probs(const Operations::Op &op, ExperimentResult &result){ + std::vector v; + std::cout << "qubits = "; + for(size_t i = 0; i < op.qubits.size(); i++){ + std::cout << op.qubits[i] << " "; + } + std::cout << std::endl; + + std::cout << "states = "; + for(size_t i = 0; i < op.int_params.size(); i++){ + std::cout << op.int_params[i] << " "; + } + std::cout << std::endl; + + double p = this->compute_probability(op.qubits, op.int_params); + std::cout << p << std::endl; + v.push_back(p); + std::cout << v[0] << std::endl; + + BaseState::save_data_average(result, op.string_params[0], std::move(v), Operations::DataSubType::list); +} + +// This function converts an unsigned binary number to reflected binary Gray code. +uint_t BinaryToGray(uint_t num) +{ + return num ^ (num >> 1); // The operator >> is shift right. The operator ^ is exclusive or. +} + + +double compute_algorithm_all_phases_T(AGState &state){ + uint_t full_mask = 0u; + for(size_t i = 0; i < state.num_stabilizers; i++){ + full_mask |= (ONE << i); + } + double acc = 1.; + Pauli::Pauli row(state.num_qubits); + unsigned char phase = 0; + + for(uint_t mask = 1u; mask <= full_mask; mask++){ + uint_t mask_with_bit_to_flip = BinaryToGray(mask) ^ BinaryToGray(mask - 1); + size_t bit_to_flip = 0; + for(size_t j = 0; j < state.num_stabilizers; j++){ + if((mask_with_bit_to_flip >> j) & ONE){ + bit_to_flip = j; + break; + } + } + phase += Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]); + row += state.table[bit_to_flip]; + + size_t XCount = 0; + size_t YCount = 0; + size_t ZCount = 0; + + for(size_t j = 0; j < state.num_qubits; j++){ + //if((row[j] == 0) && (row[j+state.n] == 0)){ + // ICount += 1; + //} + if(row.X[j] && !row.Z[j]){ + XCount += 1; + } + if(!row.X[j] && row.Z[j]){ + ZCount += 1; + break; + } + if(row.X[j] && row.Z[j]){ + YCount += 1; + } + } + + if(ZCount == 0){ + if(((phase + YCount) % 2) == 0){ + acc += powl(1./2., (XCount + YCount)/2.);; + }else{ + acc -= powl(1./2., (XCount + YCount)/2.);; + } + } + } + + if(full_mask == 0u){ + return 1.; + } + return acc; +} + +double State::compute_probability(std::vector measured_qubits, std::vector outcomes){ + AGState copied_ag = this->qreg_; //copy constructor TODO check this + //first reorder things so the first w qubits are measured + std::vector measured_qubits_sorted = measured_qubits; + std::sort(measured_qubits_sorted.begin(), measured_qubits_sorted.end()); + + //size_t qubits_swapped = 0; + for(size_t i = 0; i < measured_qubits.size(); i++){ + size_t w = measured_qubits_sorted[i]; + size_t idx = 0; + for(size_t j = 0; j < measured_qubits.size(); j++){ + if(measured_qubits[j] == w){ + idx = j; + break; + } + } + //now swap element w with element idx + if(measured_qubits[i] != i){ + copied_ag.applySwap(w, idx); + } + } + + //from this point on we will assume we're looking for the measurement outcome 0 on all measured qubits + //so apply X gates to measured qubits where we're looking for outcome 1 to correct this + for(size_t i = 0; i < outcomes.size(); i++){ + if(outcomes[i] == 1){ + copied_ag.applyX(i); + } + } + + size_t w = measured_qubits.size(); + size_t t = copied_ag.magic_phases.size(); + + //now all the measured qubits are at the start and the magic qubits are at the end + std::pair v_pair = copied_ag.apply_constraints(w, t); + if(!v_pair.first){ + return 0.; + } + + size_t v = v_pair.second; + //at this point we can delete all the non-magic qubits + for(size_t q = 0; q < t; q++){ + copied_ag.applySwap(q, q+t); + } + for(size_t s = 0; s < copied_ag.num_stabilizers; s++){ + copied_ag.table[s].X.resize(t); + copied_ag.table[s].Z.resize(t); + } + + copied_ag.num_qubits = t; + + copied_ag.apply_T_constraints(); + copied_ag.delete_identity_magic_qubits(); + + //we can make the compute algorithm much faster if all of our non-Clifford gates are in fact T gates (pi/4 rotations) + + bool all_phases_are_T = true; + for(size_t i = 0; i < copied_ag.num_qubits; i++){ + if(fabs(copied_ag.magic_phases[i] - T_ANGLE) > AG_CHOP_THRESHOLD){ + all_phases_are_T = false; + break; + } + } + if(copied_ag.num_qubits == 0){ + return powl(2., (double)v - (double)w); + } + + return compute_algorithm_all_phases_T(copied_ag) * powl(2., v - w); + +} + +void State::initialize_qreg(uint_t num_qubits){ + this->qreg_.initialize(num_qubits); + this->num_code_qubits = num_qubits; +} +void State::initialize_qreg(uint_t num_qubits, const agstate_t &state){ + if(BaseState::qreg_.num_qubits != num_qubits){ + throw std::invalid_argument("CH::State::initialize: initial state does not match qubit number."); + } + BaseState::qreg_ = state; +} + +size_t State::required_memory_mb(uint_t num_qubits, const std::vector &ops) const { + return 0; //TODO update this! +} + + +double State::expval_pauli(const reg_t &qubits, const std::string& pauli){ + return 0; //TODO fix this +} + +} //close namespace CliffPhaseCompute +} //close namespace AER + +#endif From 1d318707135de9e6601200b88b5389559bb7c66f Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 14:09:34 +0200 Subject: [PATCH 06/31] add clifford+phase compute algorithm to aer_controller --- src/controllers/aer_controller.hpp | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 9b34f6d6ce..2f0338bcc3 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -65,6 +65,7 @@ #include "simulators/superoperator/superoperator_state.hpp" #include "simulators/unitary/unitary_state.hpp" #include "simulators/unitary/unitary_state_chunk.hpp" +#include "simulators/clifford_plus_phase/compute.hpp" namespace AER { @@ -116,6 +117,7 @@ class Controller { matrix_product_state, stabilizer, extended_stabilizer, + clifford_phase_compute, unitary, superop }; @@ -132,6 +134,7 @@ class Controller { {Method::matrix_product_state, "matrix_product_state"}, {Method::stabilizer, "stabilizer"}, {Method::extended_stabilizer, "extended_stabilizer"}, + {Method::clifford_phase_compute, "clifford_phase_compute"}, {Method::unitary, "unitary"}, {Method::superop, "superop"} }; @@ -475,6 +478,8 @@ void Controller::set_config(const json_t &config) { sim_method_ = Method::stabilizer; } else if (method == "extended_stabilizer") { sim_method_ = Method::extended_stabilizer; + } else if (method == "clifford_phase_compute") { + sim_method_ = Method::clifford_phase_compute; } else if (method == "matrix_product_state") { sim_method_ = Method::matrix_product_state; } else if (method == "unitary") { @@ -1492,6 +1497,11 @@ void Controller::run_circuit(const Circuit &circ, return run_circuit_helper( circ, noise, config, shots, rng_seed, Method::matrix_product_state, false, result); + case Method::clifford_phase_compute: + return run_circuit_helper( + circ, noise, config, shots, rng_seed, Method::extended_stabilizer_compute, + false, result + ) default: throw std::runtime_error("Controller:Invalid simulation method"); } @@ -1628,6 +1638,14 @@ Controller::simulation_method(const Circuit &circ, } return Method::matrix_product_state; } + case Method::clifford_phase_compute: { + if (validate) { + CliffPhaseCompute::State state; + validate_state(state, circ, noise_model, true); + validate_memory_requirements(CliffPhaseCompute::State(), circ, true); + } + return Method::clifford_phase_compute; + } case Method::automatic: { // If circuit and noise model are Clifford run on Stabilizer simulator if (validate_state(Stabilizer::State(), circ, noise_model, false)) { @@ -1733,6 +1751,10 @@ size_t Controller::required_memory_mb(const Circuit &circ, MatrixProductState::State state; return state.required_memory_mb(circ.num_qubits, circ.ops); } + case Method::clifford_phase_compute: { + CliffPhaseCompute::State state; + return state.required_memory_mb(circ.num_qubits, circ.ops); + } default: // We shouldn't get here, so throw an exception if we do throw std::runtime_error("Controller: Invalid simulation method"); @@ -2076,6 +2098,10 @@ bool Controller::check_measure_sampling_opt(const Circuit &circ, method == Method::unitary) { return true; } + + if(method == Method::clifford_phase_compute){ + return false; + } // If circuit contains a non-initial initialize that is not a full width // instruction we can't sample From a4cea853a3b810b37f49866dbd4441e40da6d251 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 14:28:33 +0200 Subject: [PATCH 07/31] add xorAt method to BinaryVector class --- src/simulators/stabilizer/binary_vector.hpp | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/simulators/stabilizer/binary_vector.hpp b/src/simulators/stabilizer/binary_vector.hpp index 94a7998b1e..ba4b7cdd98 100644 --- a/src/simulators/stabilizer/binary_vector.hpp +++ b/src/simulators/stabilizer/binary_vector.hpp @@ -57,7 +57,8 @@ class BinaryVector { void set1(uint64_t pos) { setValue(ONE_, pos); }; void flipAt(uint64_t pos); - + void xorAt(bool value, uint64_t pos); + BinaryVector &operator+=(const BinaryVector &rhs); bool operator[](const uint64_t pos) const; @@ -211,6 +212,11 @@ void BinaryVector::flipAt(const uint64_t pos) { m_data[q] ^= (ONE_ << r); } +void BinaryVector::xorAt(bool value, uint64_t pos) { + auto q = pos / BLOCK_SIZE; + auto r = pos % BLOCK_SIZE; + m_data[q] ^= ((value & ONE_) << r); +} BinaryVector &BinaryVector::operator+=(const BinaryVector &rhs) { const auto size = m_data.size(); From 54bfd53e552fcde55da8d774db4070a501fd418b Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 14:39:23 +0200 Subject: [PATCH 08/31] remove snapshot code --- .../clifford_plus_phase/compute.hpp | 37 +++---------------- 1 file changed, 5 insertions(+), 32 deletions(-) diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index e00cbf852d..8994f676f2 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -62,22 +62,14 @@ class State: public Base::State{ void initialize_qreg(uint_t num_qubits, const agstate_t &state) override; size_t required_memory_mb(uint_t num_qubits, const std::vector &ops) const override; - void apply_save_specific_probs(const Operations::Op &op, ExperimentResult &result); + void apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result); double expval_pauli(const reg_t &qubits, const std::string& pauli); private: const static stringmap_t gateset_; - const static stringmap_t snapshotset_; size_t num_code_qubits; //out AG state has code+magic qubits double compute_probability(std::vector measured_qubits, std::vector outcomes); - //void apply_save_specific_probs(const Operations::Op &op, ExperimentResult &result); - //void apply_snapshot(const Operations::Op &ops, ExperimentResult &result); - //void probabilities_snapshot(const Operations::Op &op, ExperimentResult &result); }; -const stringmap_t State::snapshotset_({ - {"probabilities", Snapshots::probs}, -}); - const stringmap_t State::gateset_({ // Single qubit gates @@ -102,17 +94,13 @@ const stringmap_t State::gateset_({ void State::apply_ops(const std::vector &ops, ExperimentResult &result, RngEngine &rng, bool final_ops){ - std::cout << "2" << std::endl; for(const auto &op: ops){ - std::cout << op.type << std::endl; switch(op.type){ case Operations::OpType::gate: - std::cout << "Operations::OpType::gate" << std::endl; this->apply_gate(op); break; - case Operations::OpType::save_specific_probs: - std::cout << "Operations::OpType::save_specific_probs" << std::endl; - this->apply_save_specific_probs(op, result); + case Operations::OpType::save_specific_prob: + this->apply_save_specific_prob(op, result); break; default: throw std::invalid_argument("Compute::State::invalid instruction \'" + op.name + "\'."); @@ -121,7 +109,6 @@ void State::apply_ops(const std::vector &ops, ExperimentResult & } void State::apply_gate(const Operations::Op &op){ - std::cout << "State::apply_gate " << op.name << ": " << op.qubits[0] << std::endl; auto it = gateset_.find(op.name); if (it == gateset_.end()) { @@ -170,24 +157,10 @@ void State::apply_gate(const Operations::Op &op){ } } -void State::apply_save_specific_probs(const Operations::Op &op, ExperimentResult &result){ - std::vector v; - std::cout << "qubits = "; - for(size_t i = 0; i < op.qubits.size(); i++){ - std::cout << op.qubits[i] << " "; - } - std::cout << std::endl; - - std::cout << "states = "; - for(size_t i = 0; i < op.int_params.size(); i++){ - std::cout << op.int_params[i] << " "; - } - std::cout << std::endl; - +void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result){ + std::vector v; double p = this->compute_probability(op.qubits, op.int_params); - std::cout << p << std::endl; v.push_back(p); - std::cout << v[0] << std::endl; BaseState::save_data_average(result, op.string_params[0], std::move(v), Operations::DataSubType::list); } From 8364caa1d1a34917b5011e940f51594d1eb263c3 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 14:41:07 +0200 Subject: [PATCH 09/31] fix error in aer_controller --- src/controllers/aer_controller.hpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 2f0338bcc3..69d016236e 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -1499,9 +1499,9 @@ void Controller::run_circuit(const Circuit &circ, false, result); case Method::clifford_phase_compute: return run_circuit_helper( - circ, noise, config, shots, rng_seed, Method::extended_stabilizer_compute, + circ, noise, config, shots, rng_seed, Method::clifford_phase_compute, false, result - ) + ); default: throw std::runtime_error("Controller:Invalid simulation method"); } From 9d9a05e1fe7526c983c876e138c1784853685cbb Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 15:01:41 +0200 Subject: [PATCH 10/31] add compute algorithm to aer_simulator.py --- qiskit/providers/aer/backends/aer_simulator.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index 6ca8b3168e..408ed59195 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -346,6 +346,10 @@ class AerSimulator(AerBackend): 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 'sx', 'swap', 'u0', 't', 'tdg', 'u1', 'p', 'ccx', 'ccz', 'delay' ]), + 'clifford_phase_compute': sorted([ + 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', + 'swap', 't', 'tdg', 'delay' + ]), 'unitary': sorted([ 'u1', 'u2', 'u3', 'u', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', 'sx', 't', 'tdg', 'swap', 'cx', @@ -402,6 +406,9 @@ class AerSimulator(AerBackend): 'extended_stabilizer': sorted([ 'roerror', 'snapshot', 'save_statevector' ]), + 'clifford_phase_compute':sorted([ + 'save_specific_probability' + ]), 'unitary': sorted([ 'snapshot', 'save_state', 'save_unitary', 'set_unitary' ]), @@ -440,7 +447,7 @@ class AerSimulator(AerBackend): _SIMULATION_METHODS = [ 'automatic', 'statevector', 'density_matrix', 'stabilizer', 'matrix_product_state', 'extended_stabilizer', - 'unitary', 'superop' + 'unitary', 'superop', 'clifford_phase_compute' ] _AVAILABLE_METHODS = None From e77cdccea93c49bedd90730a682243bba522197c Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 15:32:54 +0200 Subject: [PATCH 11/31] fix errors --- src/framework/operations.hpp | 16 ++++++++++++++++ src/simulators/clifford_plus_phase/compute.hpp | 1 + 2 files changed, 17 insertions(+) diff --git a/src/framework/operations.hpp b/src/framework/operations.hpp index c8c2bd57c2..f63fc479fd 100755 --- a/src/framework/operations.hpp +++ b/src/framework/operations.hpp @@ -529,6 +529,8 @@ template Op input_to_op_save_expval(const inputdata_t& input, bool variance); template Op input_to_op_save_amps(const inputdata_t& input, bool squared); +template +Op input_to_op_save_specific_prob(const inputdata_t& input); // Snapshots template @@ -621,6 +623,9 @@ Op input_to_op(const inputdata_t& input) { return input_to_op_save_amps(input, false); if (name == "save_amplitudes_sq") return input_to_op_save_amps(input, true); + if (name == "save_specific_prob"){ + return input_to_op_save_specific_prob(input); + } // Set if (name == "set_statevector") return input_to_op_set_vector(input, OpType::set_statevec); @@ -1148,6 +1153,17 @@ Op input_to_op_save_amps(const inputdata_t& input, bool squared) { return op; } +template +Op input_to_op_save_specific_prob(const inputdata_t& input) { + Op op = input_to_op_save_default(input, OpType::save_specific_prob); + const inputdata_t& params = Parser::get_value("params", input); + op.qubits = Parser::template get_list_elem>(params, 0); + op.int_params = Parser::template get_list_elem>(params, 1); + + return op; +} + + //------------------------------------------------------------------------------ // Implementation: Snapshot deserialization //------------------------------------------------------------------------------ diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index 8994f676f2..8be18e1f4d 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -94,6 +94,7 @@ const stringmap_t State::gateset_({ void State::apply_ops(const std::vector &ops, ExperimentResult &result, RngEngine &rng, bool final_ops){ + for(const auto &op: ops){ switch(op.type){ case Operations::OpType::gate: From a5f17fec14b73e50529c77a556c8ebe1f573f477 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 14 Jun 2021 19:05:54 +0200 Subject: [PATCH 12/31] fix errors --- .../clifford_plus_phase/ag_state.hpp | 24 +++++++++---------- .../clifford_plus_phase/compute.hpp | 13 +++++----- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index dee392e993..f2c1a41282 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -227,6 +227,7 @@ void AGState::gadgetized_phase_gate(size_t a, double phase){ } this->table.push_back(Pauli::Pauli(this->num_qubits + 1)); this->table[this->num_stabilizers].Z.set1(this->num_qubits); + this->phases.push_back(0); this->num_stabilizers += 1; this->num_qubits += 1; @@ -302,6 +303,7 @@ bool AGState::rowsum2(Pauli::Pauli row, bool phase, size_t i){ void AGState::delete_identity_magic_qubits(){ //indended for use when we've already restricted our table to only contain the magic qubits size_t qubits_deleted = 0; + for(size_t q = 0; q < this->num_qubits; q++){ size_t non_identity_paulis = 0; for(size_t s = 0; (s < this->num_stabilizers) && (non_identity_paulis == 0); s++){ @@ -324,12 +326,12 @@ void AGState::delete_identity_magic_qubits(){ } } this->num_qubits = this->num_qubits - qubits_deleted; + for(size_t s = 0; s < this->num_stabilizers; s++){ this->table[s].X.resize(this->num_qubits); this->table[s].Z.resize(this->num_qubits); } - magic_phases.resize(magic_phases.size() - qubits_deleted); - this->num_qubits -= qubits_deleted; + magic_phases.resize(this->num_qubits); } bool AGState::tableau_element(size_t stabilizer, size_t column){ @@ -366,7 +368,7 @@ void AGState::swap_rows(size_t i, size_t j){ std::swap(this->phases[i], this->phases[j]); } -void AGState::delete_row(size_t i){ +void AGState::delete_row(size_t i){ this->table.erase(this->table.begin() + i); this->phases.erase(this->phases.begin() + i); this->num_stabilizers -= 1; @@ -612,12 +614,13 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ size_t v = 0; //first apply region a constraints (measurement) - for(size_t q=0; q < w; q++){ //iterate over all the measured qubits + for(size_t q=0; q < w; q++){ //iterate over all the measured qubits std::pair y_stab = std::pair(false, 0); //store the index of the first stab we come to with both x and z = 1 on this qubit std::pair x_stab = std::pair(false, 0); //store the index of the first stab we come to with x=1, z=0 std::pair z_stab = std::pair(false, 0); //store the index of the first stab we come to with z=1, x=0 for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabilisers + if(this->table[s].X[q] && this->table[s].Z[q]){ y_stab.first = true; y_stab.second = s; @@ -631,7 +634,6 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ z_stab.second = s; } } - //there are several cases here //either a single z, a single x, a single y or we can generate the whole Pauli group on this qubit @@ -655,7 +657,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ //ignore the y one if we have all 3 y_stab = std::pair(false,0); } - + //now the only possibilities are that we have an an x, y or z or both an x and a z //zero everything else on this qubit for(size_t s = 0; s < this->num_stabilizers; s++){ @@ -673,26 +675,22 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ } } } - + //case 1 - there is a generator which does not commute with Z_q if(y_stab.first || x_stab.first){ //we can't have both >= 0 size_t non_commuting_generator = y_stab.first ? y_stab.second : x_stab.second; - //we delete the non-commuting guy this->swap_rows(non_commuting_generator, this->num_stabilizers-1); - this->delete_row(this->num_stabilizers-1); - + this->delete_row(this->num_stabilizers-1); }else{ //case 2 - all generators commute with Z_q //our generating set contains either Z_q or -Z_q //we need to work out which one it is - //swap our Z_q guy to the end + //swap our Z_q guy to the end this->swap_rows(z_stab.second, this->num_stabilizers-1); - bool independent = this->independence_test(q); - if(!independent){ if(this->phases[this->num_stabilizers-1] == 0){ // +Z_q diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index 8be18e1f4d..576a27044c 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -109,7 +109,7 @@ void State::apply_ops(const std::vector &ops, ExperimentResult & } } -void State::apply_gate(const Operations::Op &op){ +void State::apply_gate(const Operations::Op &op){ auto it = gateset_.find(op.name); if (it == gateset_.end()) { @@ -159,7 +159,7 @@ void State::apply_gate(const Operations::Op &op){ } void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result){ - std::vector v; + std::vector v; double p = this->compute_probability(op.qubits, op.int_params); v.push_back(p); @@ -261,25 +261,26 @@ double State::compute_probability(std::vector measured_qubits, std::vect size_t w = measured_qubits.size(); size_t t = copied_ag.magic_phases.size(); - - //now all the measured qubits are at the start and the magic qubits are at the end + + //now all the measured qubits are at the start and the magic qubits are at the end std::pair v_pair = copied_ag.apply_constraints(w, t); if(!v_pair.first){ return 0.; } size_t v = v_pair.second; + //at this point we can delete all the non-magic qubits for(size_t q = 0; q < t; q++){ copied_ag.applySwap(q, q+t); } + for(size_t s = 0; s < copied_ag.num_stabilizers; s++){ copied_ag.table[s].X.resize(t); copied_ag.table[s].Z.resize(t); } copied_ag.num_qubits = t; - copied_ag.apply_T_constraints(); copied_ag.delete_identity_magic_qubits(); @@ -296,7 +297,7 @@ double State::compute_probability(std::vector measured_qubits, std::vect return powl(2., (double)v - (double)w); } - return compute_algorithm_all_phases_T(copied_ag) * powl(2., v - w); + return compute_algorithm_all_phases_T(copied_ag) * powl(2., (double)v - w); } From 04772a959de167580417544ceb7b888f4d6fa93f Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Wed, 16 Jun 2021 17:13:30 +0200 Subject: [PATCH 13/31] fix errors --- .../clifford_plus_phase/ag_state.hpp | 74 ++++++----- .../clifford_plus_phase/compute.hpp | 116 +++++++++++++++--- src/simulators/stabilizer/binary_vector.hpp | 7 +- 3 files changed, 143 insertions(+), 54 deletions(-) diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index f2c1a41282..77fed2b457 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -76,7 +76,7 @@ class AGState{ std::pair first_non_zero_in_row(size_t col, size_t startpoint); void swap_rows(size_t i, size_t j); - void delete_row(size_t i); + void delete_last_row(); /* * create a QCircuit that brings the state represented by this table to the state |0><0|^k \otimes I^(n-k) / (2^(n-k)) * Explicitly num_stabilizers stabilisers on num_qubits qubits with the jth stabilzier being a +z on the jth qubit @@ -154,7 +154,7 @@ void AGState::Print(){ void AGState::applyCX(size_t a, size_t b){ for(size_t i = 0; i < this->num_stabilizers; i++){ - this->phases[i] ^= this->table[i].X[a] & this->table[i].Z[a] & (this->table[i].X[b] ^ this->table[i].Z[a] ^ true); + this->phases[i] ^= this->table[i].X[a] & this->table[i].Z[b] & (this->table[i].X[b] ^ this->table[i].Z[a] ^ true); this->table[i].X.xorAt(this->table[i].X[a], b); this->table[i].Z.xorAt(this->table[i].Z[b], a); } @@ -169,15 +169,18 @@ void AGState::applyCZ(size_t a, size_t b){ } void AGState::applySwap(size_t a, size_t b){ - for(size_t i = 0; i < this->num_stabilizers; i++){ - this->table[i].X.xorAt(this->table[i].X[a], b); - this->table[i].X.xorAt(this->table[i].X[b], a); - this->table[i].X.xorAt(this->table[i].X[a], b); + this->applyCX(a,b); + this->applyCX(b,a); + this->applyCX(a,b); + // for(size_t i = 0; i < this->num_stabilizers; i++){ + // this->table[i].X.xorAt(this->table[i].X[a], b); + // this->table[i].X.xorAt(this->table[i].X[b], a); + // this->table[i].X.xorAt(this->table[i].X[a], b); - this->table[i].Z.xorAt(this->table[i].Z[b], a); - this->table[i].Z.xorAt(this->table[i].Z[a], b); - this->table[i].Z.xorAt(this->table[i].Z[b], a); - } + // this->table[i].Z.xorAt(this->table[i].Z[b], a); + // this->table[i].Z.xorAt(this->table[i].Z[a], b); + // this->table[i].Z.xorAt(this->table[i].Z[b], a); + // } } void AGState::applyH(size_t a){ @@ -364,14 +367,16 @@ void AGState::swap_rows(size_t i, size_t j){ //this->table[i].X.swap(this->table[j].X); //this->table[i].Z.swap(this->table[j].Z); - std::swap(this->table[i], this->table[j]); - std::swap(this->phases[i], this->phases[j]); + if(i != j){ + std::swap(this->table[i], this->table[j]); + std::swap(this->phases[i], this->phases[j]); + } } -void AGState::delete_row(size_t i){ - this->table.erase(this->table.begin() + i); - this->phases.erase(this->phases.begin() + i); +void AGState::delete_last_row(){ this->num_stabilizers -= 1; + this->table.resize(this->num_stabilizers); + this->phases.resize(this->num_stabilizers); } Operations::Op make_H(uint_t a){ @@ -620,7 +625,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ std::pair z_stab = std::pair(false, 0); //store the index of the first stab we come to with z=1, x=0 for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabilisers - + if(this->table[s].X[q] && this->table[s].Z[q]){ y_stab.first = true; y_stab.second = s; @@ -667,14 +672,14 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ } if(this->table[s].X[q]){ - this->rowsum(s, x_stab.first); + this->rowsum(s, x_stab.second); } if(this->table[s].Z[q]){ - this->rowsum(s, z_stab.first); + this->rowsum(s, z_stab.second); } } - } + } //case 1 - there is a generator which does not commute with Z_q if(y_stab.first || x_stab.first){ @@ -682,7 +687,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ size_t non_commuting_generator = y_stab.first ? y_stab.second : x_stab.second; //we delete the non-commuting guy this->swap_rows(non_commuting_generator, this->num_stabilizers-1); - this->delete_row(this->num_stabilizers-1); + this->delete_last_row(); }else{ //case 2 - all generators commute with Z_q //our generating set contains either Z_q or -Z_q @@ -691,13 +696,14 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ //swap our Z_q guy to the end this->swap_rows(z_stab.second, this->num_stabilizers-1); bool independent = this->independence_test(q); + if(!independent){ if(this->phases[this->num_stabilizers-1] == 0){ // +Z_q v += 1; - this->delete_row(this->num_stabilizers-1); + this->delete_last_row(); }else{ - //our chosen measurement outcome is impossible + //our chosen measurement outcome is impossible return std::pair(false,0); } }else{ @@ -707,8 +713,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ } } - //time to impose region b constraints - + //time to impose region b constraints for(size_t q=w; q < this->num_qubits - t ; q++){ //iterate over all the non-measured non-magic qubits std::pair y_stab = std::pair(false,0); //store the index of the first stab we come to with both x and z = 1 on this qubit std::pair x_stab = std::pair(false,0); //store the index of the first stab we come to with x=1, z=0 @@ -769,10 +774,9 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ this->rowsum(s, z_stab.second); } } - } - - //now we just delete the non-identity guys on this qubit + } + //now we just delete the non-identity guys on this qubit int num_to_delete = 0; if(y_stab.first){ //if we have a Y stab we don't have either of the others @@ -781,7 +785,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ }else{ if(x_stab.first){ this->swap_rows(x_stab.second, this->num_stabilizers-1); - if(z_stab.first && this->num_stabilizers - 1 == z_stab.second){ + if(z_stab.first && (this->num_stabilizers - 1 == z_stab.second)){ z_stab = x_stab; } num_to_delete += 1; @@ -791,10 +795,11 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ num_to_delete += 1; } } + //delete the last num_to_delete rows //TODO should we implement an erase method that works like the std::vector one so you can pass a range? for(size_t deletes = 0; deletes < num_to_delete; deletes++){ - this->delete_row(this->num_stabilizers-1); + this->delete_last_row(); } } @@ -812,7 +817,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ size_t AGState::apply_T_constraints(){ size_t starting_rows = this->num_stabilizers; size_t deleted_rows = 0; - for(size_t reps = 0; reps < starting_rows; reps++){ + for(size_t reps = 0; reps < starting_rows; reps++){ for(size_t q=0; q < this->num_qubits; q++){ //iterate over all the magic qubits std::pair y_stab = std::pair(false,0); //store the index of the first stab we come to with both x and z = 1 on this qubit std::pair x_stab = std::pair(false,0); //store the index of the first stab we come to with x=1, z=0 @@ -859,14 +864,17 @@ size_t AGState::apply_T_constraints(){ if(z_stab.first && !x_stab.first){ //kill all other z stuff on this qubit - for(size_t s = 0; s < this->num_qubits; s++){ + for(size_t s = 0; s < this->num_stabilizers; s++){ if((s != z_stab.second) && this->table[s].Z[q]){ this->rowsum(s, z_stab.second); } } //now delete the z guy - this->swap_rows(z_stab.second, this->num_stabilizers-1); - this->delete_row(this->num_stabilizers-1); + + if(z_stab.second != this->num_stabilizers-1){ + this->swap_rows(z_stab.second, this->num_stabilizers-1); + } + this->delete_last_row(); deleted_rows += 1; } } diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index 576a27044c..f989241f19 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -24,7 +24,7 @@ const Operations::OpSet StateOpSet( {Operations::OpType::gate, Operations::OpType::save_specific_prob}, // Gates {"CX", "cx", "cz", "swap", "id", "x", "y", "z", "h", - "s", "sdg", "t"}, + "s", "sdg", "t","p", "rz"}, // Snapshots {} ); @@ -32,7 +32,7 @@ const Operations::OpSet StateOpSet( enum class Gates { - id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, + id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, u1, }; @@ -82,6 +82,8 @@ const stringmap_t State::gateset_({ {"sdg", Gates::sdg}, // Conjugate-transpose of Phase gate {"h", Gates::h}, // Hadamard gate (X + Z / sqrt(2)) {"t", Gates::t}, // T-gate (sqrt(S)) + {"rz", Gates::rz}, // Pauli-Z rotation gate + {"p", Gates::p}, // Parameterized phase gate {"tdg", Gates::tdg}, // Conjguate-transpose of T gate // Two-qubit gates {"CX", Gates::cx}, // Controlled-X gate (CNOT) @@ -162,7 +164,6 @@ void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult std::vector v; double p = this->compute_probability(op.qubits, op.int_params); v.push_back(p); - BaseState::save_data_average(result, op.string_params[0], std::move(v), Operations::DataSubType::list); } @@ -174,10 +175,12 @@ uint_t BinaryToGray(uint_t num) double compute_algorithm_all_phases_T(AGState &state){ + uint_t full_mask = 0u; for(size_t i = 0; i < state.num_stabilizers; i++){ full_mask |= (ONE << i); } + double acc = 1.; Pauli::Pauli row(state.num_qubits); unsigned char phase = 0; @@ -191,9 +194,11 @@ double compute_algorithm_all_phases_T(AGState &state){ break; } } - phase += Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]); + + phase += state.phases[bit_to_flip] + (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); //phases for stabilizers are always 1 or -1 + phase %= 2; row += state.table[bit_to_flip]; - + size_t XCount = 0; size_t YCount = 0; size_t ZCount = 0; @@ -222,6 +227,57 @@ double compute_algorithm_all_phases_T(AGState &state){ } } } + + if(full_mask == 0u){ + return 1.; + } + return acc; +} + +double compute_algorithm_arbitrary_phases(AGState &state){ + uint_t full_mask = 0u; + for(size_t i = 0; i < state.num_stabilizers; i++){ + full_mask |= (ONE << i); + } + double acc = 1.; + Pauli::Pauli row(state.num_qubits); + unsigned char phase = 0; + + for(uint_t mask = 1u; mask <= full_mask; mask++){ + uint_t mask_with_bit_to_flip = BinaryToGray(mask) ^ BinaryToGray(mask - 1); + size_t bit_to_flip = 0; + for(size_t j = 0; j < state.num_stabilizers; j++){ + if((mask_with_bit_to_flip >> j) & ONE){ + bit_to_flip = j; + break; + } + } + + phase += (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); //phases for stabilizers are always 0 or 2 + row += state.table[bit_to_flip]; + + double prod = 1.; + for(size_t j = 0; j < state.num_qubits; j++){ + //if((row[j] == 0) && (row[j+state.n] == 0)){ + // ICount += 1; + //} + if(row.X[j] && !row.Z[j]){ + prod *= cos(state.magic_phases[j]); + } + if(!row.X[j] && row.Z[j]){ + prod = 0.; + break; + } + if(row.X[j] && row.Z[j]){ + prod *= -sin(state.magic_phases[j]); + } + } + if(phase){ + acc -= prod; + }else{ + acc += prod; + } + } if(full_mask == 0u){ return 1.; @@ -229,30 +285,46 @@ double compute_algorithm_all_phases_T(AGState &state){ return acc; } + double State::compute_probability(std::vector measured_qubits, std::vector outcomes){ - AGState copied_ag = this->qreg_; //copy constructor TODO check this + + AGState copied_ag(this->qreg_); //copy constructor TODO check this + //first reorder things so the first w qubits are measured - std::vector measured_qubits_sorted = measured_qubits; + std::vector measured_qubits_sorted(measured_qubits); + + std::vector qubit_indexes; + for(size_t i = 0; i < this->qreg_.num_qubits; i++){ + qubit_indexes.push_back(i); + } std::sort(measured_qubits_sorted.begin(), measured_qubits_sorted.end()); - //size_t qubits_swapped = 0; for(size_t i = 0; i < measured_qubits.size(); i++){ - size_t w = measured_qubits_sorted[i]; - size_t idx = 0; + size_t w = measured_qubits_sorted[i]; + size_t idx1 = 0; + size_t idx2 = 0; + for(size_t j = 0; j < qubit_indexes.size(); j++){ + if(qubit_indexes[j] == w){ + idx1 = j; + break; + } + } for(size_t j = 0; j < measured_qubits.size(); j++){ if(measured_qubits[j] == w){ - idx = j; + idx2 = j; break; } } - //now swap element w with element idx - if(measured_qubits[i] != i){ - copied_ag.applySwap(w, idx); + + if(idx1 != idx2){ + std::swap(qubit_indexes[idx1], qubit_indexes[idx2]); + copied_ag.applySwap(idx1, idx2); } } //from this point on we will assume we're looking for the measurement outcome 0 on all measured qubits //so apply X gates to measured qubits where we're looking for outcome 1 to correct this + //now all the measured qubits are at the start and the magic qubits are at the end for(size_t i = 0; i < outcomes.size(); i++){ if(outcomes[i] == 1){ copied_ag.applyX(i); @@ -263,18 +335,18 @@ double State::compute_probability(std::vector measured_qubits, std::vect size_t t = copied_ag.magic_phases.size(); //now all the measured qubits are at the start and the magic qubits are at the end + std::pair v_pair = copied_ag.apply_constraints(w, t); if(!v_pair.first){ return 0.; } - size_t v = v_pair.second; //at this point we can delete all the non-magic qubits for(size_t q = 0; q < t; q++){ - copied_ag.applySwap(q, q+t); + copied_ag.applySwap(q, q+(copied_ag.num_qubits - t)); } - + for(size_t s = 0; s < copied_ag.num_stabilizers; s++){ copied_ag.table[s].X.resize(t); copied_ag.table[s].Z.resize(t); @@ -293,12 +365,16 @@ double State::compute_probability(std::vector measured_qubits, std::vect break; } } + + if(copied_ag.num_qubits == 0){ return powl(2., (double)v - (double)w); } - - return compute_algorithm_all_phases_T(copied_ag) * powl(2., (double)v - w); - + if(all_phases_are_T){ + return compute_algorithm_all_phases_T(copied_ag) * powl(2., (double)v - w); + } else { + return compute_algorithm_arbitrary_phases(copied_ag) * powl(2., (double)v - w); + } } void State::initialize_qreg(uint_t num_qubits){ diff --git a/src/simulators/stabilizer/binary_vector.hpp b/src/simulators/stabilizer/binary_vector.hpp index ba4b7cdd98..d8b2b7775d 100644 --- a/src/simulators/stabilizer/binary_vector.hpp +++ b/src/simulators/stabilizer/binary_vector.hpp @@ -186,7 +186,12 @@ void BinaryVector::setLength(uint64_t length) { } void BinaryVector::resize(uint64_t new_length) { - m_data.resize((new_length - 1) / BLOCK_SIZE + 1, ZERO_); + if(new_length == 0){ + m_data.resize(0); + } else { + m_data.resize((new_length - 1) / BLOCK_SIZE + 1, ZERO_); + } + //zero the rest of the last block if necessary if((new_length < m_length) && (new_length % BLOCK_SIZE) > 0){ for(size_t i = (new_length % BLOCK_SIZE) ; i < BLOCK_SIZE; i++){ From 51ba8f35590f6b71185dbd7abf9b6eb4fc433970 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Thu, 17 Jun 2021 13:03:40 +0200 Subject: [PATCH 14/31] add arbitrary phase gates --- src/simulators/clifford_plus_phase/compute.hpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index f989241f19..a7f853ec43 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -24,7 +24,7 @@ const Operations::OpSet StateOpSet( {Operations::OpType::gate, Operations::OpType::save_specific_prob}, // Gates {"CX", "cx", "cz", "swap", "id", "x", "y", "z", "h", - "s", "sdg", "t","p", "rz"}, + "s", "sdg", "t","p", "rz", "u1"}, // Snapshots {} ); @@ -32,7 +32,7 @@ const Operations::OpSet StateOpSet( enum class Gates { - id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, u1, +id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, p, rz, u1, }; @@ -83,7 +83,8 @@ const stringmap_t State::gateset_({ {"h", Gates::h}, // Hadamard gate (X + Z / sqrt(2)) {"t", Gates::t}, // T-gate (sqrt(S)) {"rz", Gates::rz}, // Pauli-Z rotation gate - {"p", Gates::p}, // Parameterized phase gate + {"p", Gates::rz}, // Parameterized phase gate + {"u1", Gates::rz}, {"tdg", Gates::tdg}, // Conjguate-transpose of T gate // Two-qubit gates {"CX", Gates::cx}, // Controlled-X gate (CNOT) @@ -146,6 +147,8 @@ void State::apply_gate(const Operations::Op &op){ case Gates::tdg: this->qreg_.gadgetized_phase_gate(op.qubits[0], -T_ANGLE); break; + case Gates::rz: + this->qreg_.gadgetized_phase_gate(op.qubits[0], op.params[0].real()); case Gates::cx: this->qreg_.applyCX(op.qubits[0],op.qubits[1]); break; From 62ff98a29c1d89267f6a6f6598325d56dc1d5230 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Thu, 17 Jun 2021 16:21:22 +0200 Subject: [PATCH 15/31] fix linter issues --- qiskit/providers/aer/backends/aer_simulator.py | 6 +++--- .../save_instructions/save_probabilities.py | 17 ++++++++++------- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index 408ed59195..4a4cd56c2d 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -347,8 +347,8 @@ class AerSimulator(AerBackend): 'swap', 'u0', 't', 'tdg', 'u1', 'p', 'ccx', 'ccz', 'delay' ]), 'clifford_phase_compute': sorted([ - 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', - 'swap', 't', 'tdg', 'delay' + 'cx', 'cz', 'id', 'x', 'y', 'z', 'h', 's', 'sdg', + 'swap', 't', 'tdg', 'delay', 'p', 'u1', 'rz' ]), 'unitary': sorted([ 'u1', 'u2', 'u3', 'u', 'p', 'r', 'rx', 'ry', 'rz', 'id', 'x', @@ -406,7 +406,7 @@ class AerSimulator(AerBackend): 'extended_stabilizer': sorted([ 'roerror', 'snapshot', 'save_statevector' ]), - 'clifford_phase_compute':sorted([ + 'clifford_phase_compute': sorted([ 'save_specific_probability' ]), 'unitary': sorted([ diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index 675059e8ab..ae7ad322ff 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -73,10 +73,11 @@ def __init__(self, pershot=pershot, conditional=conditional) + class SaveSpecificProbability(SaveAverageData): """Save measurement outcome probabilities vector.""" def __init__(self, num_qubits, - states, qubits, + states, qubits, label="specific-probabilities", unnormalized=False, pershot=False, @@ -94,13 +95,13 @@ def __init__(self, num_qubits, conditional (bool): if True save the probabilities data conditional on the current classical register values [Default: False]. - """ - + """ super().__init__("save_specific_prob", num_qubits, label, pershot=pershot, conditional=conditional, params=[qubits, states]) + def save_probabilities(self, qubits=None, label="probabilities", @@ -166,10 +167,11 @@ def save_probabilities_dict(self, conditional=conditional) return self.append(instr, qubits) + def save_specific_probability(self, states, qubits, label="specific_probability", - unnormalized=False, - pershot=False, - conditional=False): + unnormalized=False, + pershot=False, + conditional=False): """Save squared statevector amplitudes (probabilities). Args: @@ -191,7 +193,7 @@ def save_specific_probability(self, states, qubits, label="specific_probability" Raises: ExtensionError: if params is invalid for the specified number of qubits. """ - if qubits == None: + if qubits is None: qubits = default_qubits(self) instr = SaveSpecificProbability(len(qubits), states, qubits, label=label, unnormalized=unnormalized, @@ -199,6 +201,7 @@ def save_specific_probability(self, states, qubits, label="specific_probability" conditional=conditional) return self.append(instr, qubits) + QuantumCircuit.save_probabilities = save_probabilities QuantumCircuit.save_probabilities_dict = save_probabilities_dict QuantumCircuit.save_specific_probability = save_specific_probability From 6f70128cef930c0279fb74c0fed8418a055ca72c Mon Sep 17 00:00:00 2001 From: or1426 Date: Thu, 17 Jun 2021 20:27:48 +0200 Subject: [PATCH 16/31] remove whitespace for linter --- .../aer/library/save_instructions/save_probabilities.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index ae7ad322ff..e2c8eb74a0 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -71,9 +71,9 @@ def __init__(self, super().__init__("save_probabilities_dict", num_qubits, label, unnormalized=unnormalized, pershot=pershot, - conditional=conditional) + conditional=conditional + - class SaveSpecificProbability(SaveAverageData): """Save measurement outcome probabilities vector.""" def __init__(self, num_qubits, @@ -95,7 +95,7 @@ def __init__(self, num_qubits, conditional (bool): if True save the probabilities data conditional on the current classical register values [Default: False]. - """ + """ super().__init__("save_specific_prob", num_qubits, label, pershot=pershot, conditional=conditional, From 17c88d501c5da7b2bdad8df45739245e785bbd10 Mon Sep 17 00:00:00 2001 From: or1426 Date: Thu, 17 Jun 2021 20:39:21 +0200 Subject: [PATCH 17/31] Update save_probabilities.py --- .../aer/library/save_instructions/save_probabilities.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index e2c8eb74a0..7035e28311 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -71,7 +71,7 @@ def __init__(self, super().__init__("save_probabilities_dict", num_qubits, label, unnormalized=unnormalized, pershot=pershot, - conditional=conditional + conditional=conditional) class SaveSpecificProbability(SaveAverageData): From 65db315bf1b7014dc3745aadb680db9ecd9684d0 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Fri, 18 Jun 2021 16:49:10 +0200 Subject: [PATCH 18/31] add docstrings --- .../save_instructions/save_probabilities.py | 40 +++++++++---------- 1 file changed, 18 insertions(+), 22 deletions(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index ae7ad322ff..d413c7630c 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -75,27 +75,29 @@ def __init__(self, class SaveSpecificProbability(SaveAverageData): - """Save measurement outcome probabilities vector.""" + """Save a probability for a specific measurement probability.""" def __init__(self, num_qubits, states, qubits, label="specific-probabilities", - unnormalized=False, pershot=False, conditional=False): """Instruction to save specific probabilities. Args: + states (list): list of ints indicating which measurement outcomes to compute the outcome for + qubits (list): list of ints indicating which qubits the measurement is on num_qubits (int): the number of qubits for the snapshot type. label (str): the key for retrieving saved data from results. - unnormalized (bool): If True return save the unnormalized accumulated - probabilities over all shots [Default: False]. pershot (bool): if True save a list of probabilities for each shot of the simulation rather than the average over all shots [Default: False]. conditional (bool): if True save the probabilities data conditional on the current classical register values [Default: False]. - """ + e.g: + if states = [0,1,0], qubits = [0,1,2] we compute the probability of observing the outcome 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 + if states = [0,1], qubits = [5,1] we compute the probability of observing the outcome 0 on qubit 5 and 1 on qubit 0 + """ super().__init__("save_specific_prob", num_qubits, label, pershot=pershot, conditional=conditional, @@ -169,29 +171,23 @@ def save_probabilities_dict(self, def save_specific_probability(self, states, qubits, label="specific_probability", - unnormalized=False, pershot=False, conditional=False): - """Save squared statevector amplitudes (probabilities). - + """Instruction to save specific probabilities. Args: - states (List[int] or List[str]): the basis states to return amplitudes for. - qubits (List[int] or List[str]): the qubits to return amplitudes for. + states (list): list of ints indicating which measurement outcomes to compute the outcome for + qubits (list): list of ints indicating which qubits the measurement is on + num_qubits (int): the number of qubits for the snapshot type. label (str): the key for retrieving saved data from results. - unnormalized (bool): If True return save the unnormalized accumulated - probabilities over all shots [Default: False]. - pershot (bool): if True save a list of probability vectors for each - shot of the simulation rather than the a single - amplitude vector [Default: False]. - conditional (bool): if True save the probability vector conditional + pershot (bool): if True save a list of probabilities for each shot + of the simulation rather than the average over + all shots [Default: False]. + conditional (bool): if True save the probabilities data conditional on the current classical register values [Default: False]. - - Returns: - QuantumCircuit: with attached instruction. - - Raises: - ExtensionError: if params is invalid for the specified number of qubits. + e.g: + if states = [0,1,0], qubits = [0,1,2] we compute the probability of observing the outcome 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 + if states = [0,1], qubits = [5,1] we compute the probability of observing the outcome 0 on qubit 5 and 1 on qubit 0 """ if qubits is None: qubits = default_qubits(self) From 51347d6dba383b49e3b38c7a369004dee7dba537 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Fri, 25 Jun 2021 18:08:29 +0200 Subject: [PATCH 19/31] reduce length of lines in doc strings --- .../save_instructions/save_probabilities.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index 7af78369c6..97d877ef7e 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -84,7 +84,7 @@ def __init__(self, num_qubits, """Instruction to save specific probabilities. Args: - states (list): list of ints indicating which measurement outcome to compute the probability for. + states (list): list of ints indicating the outcome to compute the probability for. qubits (list): list of ints indicating which qubits the measurement is on. num_qubits (int): the number of qubits for the snapshot type. label (str): the key for retrieving saved data from results. @@ -95,8 +95,10 @@ def __init__(self, num_qubits, on the current classical register values [Default: False]. e.g: - if states = [0,1,0], qubits = [0,1,2] we compute the probability of observing the outcome 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 - if states = [0,1], qubits = [5,1] we compute the probability of observing the outcome 0 on qubit 5 and 1 on qubit 0 + if states = [0,1,0], qubits = [0,1,2] + we compute the probability of the outcome 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 + if states = [0,1], qubits = [5,1] + we compute the probability of the outcome 0 on qubit 5 and 1 on qubit 0 """ super().__init__("save_specific_prob", num_qubits, label, pershot=pershot, @@ -176,7 +178,7 @@ def save_specific_probability(self, states, qubits, label="specific_probability" """Instruction to save specific probabilities. Args: - states (list): list of ints indicating which measurement outcome to compute the probability for + states (list): list of ints indicating the outcome to compute the probability for qubits (list): list of ints indicating which qubits the measurement is on num_qubits (int): the number of qubits for the snapshot type. label (str): the key for retrieving saved data from results. @@ -187,8 +189,10 @@ def save_specific_probability(self, states, qubits, label="specific_probability" on the current classical register values [Default: False]. e.g: - if states = [0,1,0], qubits = [0,1,2] we compute the probability of observing the outcome 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 - if states = [0,1], qubits = [5,1] we compute the probability of observing the outcome 0 on qubit 5 and 1 on qubit 0 + if states = [0,1,0], qubits = [0,1,2] + we compute the probability of 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 + if states = [0,1], qubits = [5,1] + we compute the probability of 0 on qubit 5 and 1 on qubit 0 """ if qubits is None: qubits = default_qubits(self) From 76895d4c1445897c26bc6c1030f28469ef1beda0 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Fri, 25 Jun 2021 18:14:50 +0200 Subject: [PATCH 20/31] trailing whitespace --- .../aer/library/save_instructions/save_probabilities.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index 97d877ef7e..a5d0a3d1fa 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -95,9 +95,9 @@ def __init__(self, num_qubits, on the current classical register values [Default: False]. e.g: - if states = [0,1,0], qubits = [0,1,2] + if states = [0,1,0], qubits = [0,1,2] we compute the probability of the outcome 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 - if states = [0,1], qubits = [5,1] + if states = [0,1], qubits = [5,1] we compute the probability of the outcome 0 on qubit 5 and 1 on qubit 0 """ super().__init__("save_specific_prob", num_qubits, label, @@ -189,9 +189,9 @@ def save_specific_probability(self, states, qubits, label="specific_probability" on the current classical register values [Default: False]. e.g: - if states = [0,1,0], qubits = [0,1,2] + if states = [0,1,0], qubits = [0,1,2] we compute the probability of 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 - if states = [0,1], qubits = [5,1] + if states = [0,1], qubits = [5,1] we compute the probability of 0 on qubit 5 and 1 on qubit 0 """ if qubits is None: From 8436eec8e8e53111759f9a2d7a4dc46570c6b8b2 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Fri, 25 Jun 2021 18:21:00 +0200 Subject: [PATCH 21/31] fix linter issues --- .../aer/library/save_instructions/save_probabilities.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/qiskit/providers/aer/library/save_instructions/save_probabilities.py b/qiskit/providers/aer/library/save_instructions/save_probabilities.py index a5d0a3d1fa..34701d10dc 100644 --- a/qiskit/providers/aer/library/save_instructions/save_probabilities.py +++ b/qiskit/providers/aer/library/save_instructions/save_probabilities.py @@ -180,7 +180,6 @@ def save_specific_probability(self, states, qubits, label="specific_probability" Args: states (list): list of ints indicating the outcome to compute the probability for qubits (list): list of ints indicating which qubits the measurement is on - num_qubits (int): the number of qubits for the snapshot type. label (str): the key for retrieving saved data from results. pershot (bool): if True save a list of probabilities for each shot of the simulation rather than the average over @@ -193,11 +192,13 @@ def save_specific_probability(self, states, qubits, label="specific_probability" we compute the probability of 0 on qubit 0, 1 on qubit 1 and 0 on qubit 2 if states = [0,1], qubits = [5,1] we compute the probability of 0 on qubit 5 and 1 on qubit 0 + + Returns: + QuantumCircuit: with attached instruction. """ if qubits is None: qubits = default_qubits(self) instr = SaveSpecificProbability(len(qubits), states, qubits, label=label, - unnormalized=unnormalized, pershot=pershot, conditional=conditional) return self.append(instr, qubits) From 7796a82e151d5d50f0007433a8ef74698965f8a5 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Mon, 19 Jul 2021 15:29:05 +0200 Subject: [PATCH 22/31] remove use of designated initializers --- .../clifford_plus_phase/ag_state.hpp | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index 77fed2b457..c6bd243ec1 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -380,19 +380,35 @@ void AGState::delete_last_row(){ } Operations::Op make_H(uint_t a){ - return {.type=Operations::OpType::gate, .name="h", .qubits={a}}; + Operations::Op op; + op.type = Operations::OpType::gate; + op.name = "h"; + op.qubits = {a}; + return op; } Operations::Op make_S(uint_t a){ - return {.type=Operations::OpType::gate, .name="s", .qubits={a}}; + Operations::Op op; + op.type = Operations::OpType::gate; + op.name = "s"; + op.qubits = {a}; + return op; } Operations::Op make_CX(uint_t a, uint_t b){ - return {.type=Operations::OpType::gate, .name="cx", .qubits={a, b}}; + Operations::Op op; + op.type = Operations::OpType::gate; + op.name = "cx"; + op.qubits = {a, b}; + return op; } Operations::Op make_CZ(uint_t a, uint_t b){ - return {.type=Operations::OpType::gate, .name="cz", .qubits={a, b}}; + Operations::Op op; + op.type = Operations::OpType::gate; + op.name = "cz"; + op.qubits = {a, b}; + return op; } From f7c44a2db4465b3df9adbda0ec1b51dc1e3f0fdb Mon Sep 17 00:00:00 2001 From: Hiroshi Date: Mon, 16 Aug 2021 18:15:38 +0900 Subject: [PATCH 23/31] replace tab to spaces and fix implicit cast that is not allowed in Windows --- .../clifford_plus_phase/ag_state.hpp | 274 +++++++++--------- .../clifford_plus_phase/compute.hpp | 74 ++--- 2 files changed, 174 insertions(+), 174 deletions(-) diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index c6bd243ec1..1135b5934e 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -108,7 +108,7 @@ class AGState{ */ std::pair apply_constraints(size_t w, size_t t); size_t apply_T_constraints(); - /* + /* * Go through our stabilizer table and delete every qubit for which every stabilizer is the identity on that qubit * In other words delete every column from the X and Z matrices if both are 0 for every element in that column * intended only for use when we have restricted our table to only have magic qubits @@ -311,7 +311,7 @@ void AGState::delete_identity_magic_qubits(){ size_t non_identity_paulis = 0; for(size_t s = 0; (s < this->num_stabilizers) && (non_identity_paulis == 0); s++){ if(this->table[s].X[q] || this->table[s].Z[q]){ - non_identity_paulis += 1; + non_identity_paulis += 1; } } if(non_identity_paulis == 0){ @@ -320,11 +320,11 @@ void AGState::delete_identity_magic_qubits(){ qubits_deleted += 1; }else{ if(qubits_deleted > 0){ - for(size_t s = 0; s < this->num_stabilizers; s++){ - this->table[s].X.setValue(this->table[s].X[q], q-qubits_deleted); - this->table[s].Z.setValue(this->table[s].Z[q], q-qubits_deleted); - } - magic_phases[q - qubits_deleted] = magic_phases[q]; + for(size_t s = 0; s < this->num_stabilizers; s++){ + this->table[s].X.setValue(this->table[s].X[q], q-qubits_deleted); + this->table[s].Z.setValue(this->table[s].Z[q], q-qubits_deleted); + } + magic_phases[q - qubits_deleted] = magic_phases[q]; } } } @@ -433,13 +433,13 @@ std::vector AGState::simplifying_unitary(){ }else{ size_t pivot = poss_pivot.second; //now known to exist if(pivot != h){ - //swap rows h and pivot of the table - this->swap_rows(h,pivot); + //swap rows h and pivot of the table + this->swap_rows(h,pivot); } for(size_t j = 0; j < this->num_stabilizers; j++){ - if((j != h) && this->table[j].X[k]){ - this->rowsum(j,h); - } + if((j != h) && this->table[j].X[k]){ + this->rowsum(j,h); + } } h += 1; k += 1; @@ -468,8 +468,8 @@ std::vector AGState::simplifying_unitary(){ for(size_t r = 0; r < this->num_stabilizers; r++){ for(size_t col = this->num_stabilizers; col < this->num_qubits; col++){ if(this->table[r].X[col]){ - this->applyCX(r, col); - circuit.push_back(make_CX(r,col)); + this->applyCX(r, col); + circuit.push_back(make_CX(r,col)); } } } @@ -486,8 +486,8 @@ std::vector AGState::simplifying_unitary(){ for(size_t col = this->num_stabilizers; col < this->num_qubits; col++){ for(size_t r = 0; r < this->num_stabilizers; r++){ if(this->table[r].Z[col]){ - this->applyCZ(r, col); - circuit.push_back(make_CZ(r, col)); + this->applyCZ(r, col); + circuit.push_back(make_CZ(r, col)); } } } @@ -496,8 +496,8 @@ std::vector AGState::simplifying_unitary(){ for(size_t col = 0; col < this->num_stabilizers; col++){ for(size_t r = 0; r < col; r++){ if(this->table[r].Z[col]){ - this->applyCZ(r, col); - circuit.push_back(make_CZ(r, col)); + this->applyCZ(r, col); + circuit.push_back(make_CZ(r, col)); } } } @@ -576,37 +576,37 @@ bool AGState::independence_test(int q){ if(x.first){ if(x.second != a){ - this->swap_rows(a, x.second); + this->swap_rows(a, x.second); } if(z.first && (z.second == a)){ - z = x; + z = x; } for(size_t j = 0; j < this->num_stabilizers; j++){ - if((j != a) && this->table[j].X[b]){ - this->rowsum(j,a); - } + if((j != a) && this->table[j].X[b]){ + this->rowsum(j,a); + } } a += 1; } if(y.first){ if(y.second != a){ - this->swap_rows(a,y.second); + this->swap_rows(a,y.second); } for(size_t j = 0; j < this->num_stabilizers; j++){ - if((j != a) && this->table[j].X[b] && this->table[j].Z[b]){ - this->rowsum(j,a); - } + if((j != a) && this->table[j].X[b] && this->table[j].Z[b]){ + this->rowsum(j,a); + } } a += 1; } if(z.first){ if(z.second != a){ - this->swap_rows(a,z.second); + this->swap_rows(a,z.second); } for(size_t j = 0; j < this->num_stabilizers; j++){ - if((j != a) && this->table[j].Z[b]){ - this->rowsum(j,a); - } + if((j != a) && this->table[j].Z[b]){ + this->rowsum(j,a); + } } a += 1; } @@ -643,16 +643,16 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabilisers if(this->table[s].X[q] && this->table[s].Z[q]){ - y_stab.first = true; - y_stab.second = s; + y_stab.first = true; + y_stab.second = s; } if(this->table[s].X[q] && !this->table[s].Z[q]){ - x_stab.first = true; - x_stab.second = s; + x_stab.first = true; + x_stab.second = s; } if(!this->table[s].X[q] && this->table[s].Z[q]){ - z_stab.first = true; - z_stab.second = s; + z_stab.first = true; + z_stab.second = s; } } //there are several cases here @@ -662,15 +662,15 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ //put things in standard form (first stab is x then z) if((y_stab.first + x_stab.first + z_stab.first) >= 2){ //we have at least two of the set if(!x_stab.first){//we don't have a generator for x alone, but we can make one - this->rowsum(y_stab.second, z_stab.second); - //now we have a z and an x but not a y - x_stab = y_stab; - y_stab = std::pair(false,0); + this->rowsum(y_stab.second, z_stab.second); + //now we have a z and an x but not a y + x_stab = y_stab; + y_stab = std::pair(false,0); }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one - this->rowsum(y_stab.second, x_stab.second); - //now we have a z and an x but not a y - z_stab = y_stab; - y_stab = std::pair(false,0); + this->rowsum(y_stab.second, x_stab.second); + //now we have a z and an x but not a y + z_stab = y_stab; + y_stab = std::pair(false,0); } } @@ -683,17 +683,17 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ //zero everything else on this qubit for(size_t s = 0; s < this->num_stabilizers; s++){ if((!y_stab.first || s != y_stab.second) && (!x_stab.first || s != x_stab.second) && (!z_stab.first || s != z_stab.second)){ - if(this->table[s].X[q] && this->table[s].Z[q] && y_stab.first){ - this->rowsum(s, y_stab.second); - } - - if(this->table[s].X[q]){ - this->rowsum(s, x_stab.second); - } - - if(this->table[s].Z[q]){ - this->rowsum(s, z_stab.second); - } + if(this->table[s].X[q] && this->table[s].Z[q] && y_stab.first){ + this->rowsum(s, y_stab.second); + } + + if(this->table[s].X[q]){ + this->rowsum(s, x_stab.second); + } + + if(this->table[s].Z[q]){ + this->rowsum(s, z_stab.second); + } } } @@ -714,17 +714,17 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ bool independent = this->independence_test(q); if(!independent){ - if(this->phases[this->num_stabilizers-1] == 0){ - // +Z_q - v += 1; - this->delete_last_row(); - }else{ - //our chosen measurement outcome is impossible - return std::pair(false,0); - } + if(this->phases[this->num_stabilizers-1] == 0){ + // +Z_q + v += 1; + this->delete_last_row(); + }else{ + //our chosen measurement outcome is impossible + return std::pair(false,0); + } }else{ - //if we get here there has been an error - //TODO decide if we're going to throw an exception or print an error message here + //if we get here there has been an error + //TODO decide if we're going to throw an exception or print an error message here } } } @@ -737,16 +737,16 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabilisers if(this->table[s].X[q] && this->table[s].Z[q]){ - y_stab.first = true; - y_stab.second = s; + y_stab.first = true; + y_stab.second = s; } if(this->table[s].X[q] && !this->table[s].Z[q]){ - x_stab.first = true; - x_stab.second = s; + x_stab.first = true; + x_stab.second = s; } if(!this->table[s].X[q] && this->table[s].Z[q]){ - z_stab.first = true; - z_stab.second = s; + z_stab.first = true; + z_stab.second = s; } } @@ -757,15 +757,15 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ //put things in standard form (first stab is x then z) if((y_stab.first + x_stab.first + z_stab.first) >= 2){ //we have at least two of the set if(!x_stab.first){//we don't have a generator for x alone, but we can make one - this->rowsum(y_stab.second, z_stab.second); - //now we have a z and an x but not a y - x_stab = y_stab; - y_stab = std::pair(false,0); + this->rowsum(y_stab.second, z_stab.second); + //now we have a z and an x but not a y + x_stab = y_stab; + y_stab = std::pair(false,0); }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one - this->rowsum(y_stab.second, x_stab.second); - //now we have a z and an x but not a y - z_stab = y_stab; - y_stab = std::pair(false,0); + this->rowsum(y_stab.second, x_stab.second); + //now we have a z and an x but not a y + z_stab = y_stab; + y_stab = std::pair(false,0); } } @@ -778,17 +778,17 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ //zero everything else on this qubit for(size_t s = 0; s < this->num_stabilizers; s++){ if((!y_stab.first || s != y_stab.second) && (!x_stab.first || s != x_stab.second) && (!z_stab.first || s != z_stab.second)){ - if(this->table[s].X[q] && this->table[s].Z[q] && y_stab.first){ - this->rowsum(s, y_stab.second); - } - - if(this->table[s].X[q]){ - this->rowsum(s, x_stab.second); - } - - if(this->table[s].Z[q]){ - this->rowsum(s, z_stab.second); - } + if(this->table[s].X[q] && this->table[s].Z[q] && y_stab.first){ + this->rowsum(s, y_stab.second); + } + + if(this->table[s].X[q]){ + this->rowsum(s, x_stab.second); + } + + if(this->table[s].Z[q]){ + this->rowsum(s, z_stab.second); + } } } @@ -800,15 +800,15 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ num_to_delete += 1; }else{ if(x_stab.first){ - this->swap_rows(x_stab.second, this->num_stabilizers-1); - if(z_stab.first && (this->num_stabilizers - 1 == z_stab.second)){ - z_stab = x_stab; - } - num_to_delete += 1; + this->swap_rows(x_stab.second, this->num_stabilizers-1); + if(z_stab.first && (this->num_stabilizers - 1 == z_stab.second)){ + z_stab = x_stab; + } + num_to_delete += 1; } if(z_stab.first){ - this->swap_rows(z_stab.second, this->num_stabilizers-1-num_to_delete); - num_to_delete += 1; + this->swap_rows(z_stab.second, this->num_stabilizers-1-num_to_delete); + num_to_delete += 1; } } @@ -839,19 +839,19 @@ size_t AGState::apply_T_constraints(){ std::pair x_stab = std::pair(false,0); //store the index of the first stab we come to with x=1, z=0 std::pair z_stab = std::pair(false,0); //store the index of the first stab we come to with z=1, x=0 - for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabi lisers - if(this->table[s].X[q] && this->table[s].Z[q]){ - y_stab.first = true; - y_stab.second = s; - } - if(this->table[s].X[q] && !this->table[s].Z[q]){ - x_stab.first = true; - x_stab.second = s; - } - if(!this->table[s].X[q] && this->table[s].Z[q]){ - z_stab.first = true; - z_stab.second = s; - } + for(size_t s=0; s < this->num_stabilizers && (((!y_stab.first) + (!x_stab.first) + (!z_stab.first)) > 1); s++){//iterate over all stabilisers and find interesting stabi lisers + if(this->table[s].X[q] && this->table[s].Z[q]){ + y_stab.first = true; + y_stab.second = s; + } + if(this->table[s].X[q] && !this->table[s].Z[q]){ + x_stab.first = true; + x_stab.second = s; + } + if(!this->table[s].X[q] && this->table[s].Z[q]){ + z_stab.first = true; + z_stab.second = s; + } } //there are several cases here @@ -860,38 +860,38 @@ size_t AGState::apply_T_constraints(){ //case 1) we generate the whole group //put things in standard form (first stab is x then z) if((y_stab.first + x_stab.first + z_stab.first) >= 2){ //we have at least two of the set - if(!x_stab.first){//we don't have a generator for x alone, but we can make one - this->rowsum(y_stab.second, z_stab.second); - //now we have a z and an x but not a y - x_stab = y_stab; - y_stab = std::pair(false,0); - }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one - this->rowsum(y_stab.second, x_stab.second); - //now we have a z and an x but not a y - z_stab = y_stab; - y_stab = std::pair(false,0); - } + if(!x_stab.first){//we don't have a generator for x alone, but we can make one + this->rowsum(y_stab.second, z_stab.second); + //now we have a z and an x but not a y + x_stab = y_stab; + y_stab = std::pair(false,0); + }else if(!z_stab.first){//we don't have a generator for z alone, but we can make one + this->rowsum(y_stab.second, x_stab.second); + //now we have a z and an x but not a y + z_stab = y_stab; + y_stab = std::pair(false,0); + } } if(y_stab.first && x_stab.first && z_stab.first){ //we have all 3 - //ignore the y one if we have all 3 - y_stab = std::pair(false,0); + //ignore the y one if we have all 3 + y_stab = std::pair(false,0); } if(z_stab.first && !x_stab.first){ - //kill all other z stuff on this qubit - for(size_t s = 0; s < this->num_stabilizers; s++){ - if((s != z_stab.second) && this->table[s].Z[q]){ - this->rowsum(s, z_stab.second); - } - } - //now delete the z guy - - if(z_stab.second != this->num_stabilizers-1){ - this->swap_rows(z_stab.second, this->num_stabilizers-1); - } - this->delete_last_row(); - deleted_rows += 1; + //kill all other z stuff on this qubit + for(size_t s = 0; s < this->num_stabilizers; s++){ + if((s != z_stab.second) && this->table[s].Z[q]){ + this->rowsum(s, z_stab.second); + } + } + //now delete the z guy + + if(z_stab.second != this->num_stabilizers-1){ + this->swap_rows(z_stab.second, this->num_stabilizers-1); + } + this->delete_last_row(); + deleted_rows += 1; } } } diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index a7f853ec43..5fbe127231 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -67,7 +67,7 @@ class State: public Base::State{ private: const static stringmap_t gateset_; size_t num_code_qubits; //out AG state has code+magic qubits - double compute_probability(std::vector measured_qubits, std::vector outcomes); + double compute_probability(std::vector measured_qubits, std::vector outcomes); }; @@ -193,8 +193,8 @@ double compute_algorithm_all_phases_T(AGState &state){ size_t bit_to_flip = 0; for(size_t j = 0; j < state.num_stabilizers; j++){ if((mask_with_bit_to_flip >> j) & ONE){ - bit_to_flip = j; - break; + bit_to_flip = j; + break; } } @@ -211,22 +211,22 @@ double compute_algorithm_all_phases_T(AGState &state){ // ICount += 1; //} if(row.X[j] && !row.Z[j]){ - XCount += 1; + XCount += 1; } if(!row.X[j] && row.Z[j]){ - ZCount += 1; - break; + ZCount += 1; + break; } if(row.X[j] && row.Z[j]){ - YCount += 1; + YCount += 1; } } if(ZCount == 0){ if(((phase + YCount) % 2) == 0){ - acc += powl(1./2., (XCount + YCount)/2.);; + acc += powl(1./2., (XCount + YCount)/2.);; }else{ - acc -= powl(1./2., (XCount + YCount)/2.);; + acc -= powl(1./2., (XCount + YCount)/2.);; } } } @@ -251,8 +251,8 @@ double compute_algorithm_arbitrary_phases(AGState &state){ size_t bit_to_flip = 0; for(size_t j = 0; j < state.num_stabilizers; j++){ if((mask_with_bit_to_flip >> j) & ONE){ - bit_to_flip = j; - break; + bit_to_flip = j; + break; } } @@ -265,14 +265,14 @@ double compute_algorithm_arbitrary_phases(AGState &state){ // ICount += 1; //} if(row.X[j] && !row.Z[j]){ - prod *= cos(state.magic_phases[j]); + prod *= cos(state.magic_phases[j]); } if(!row.X[j] && row.Z[j]){ - prod = 0.; - break; + prod = 0.; + break; } if(row.X[j] && row.Z[j]){ - prod *= -sin(state.magic_phases[j]); + prod *= -sin(state.magic_phases[j]); } } if(phase){ @@ -289,33 +289,33 @@ double compute_algorithm_arbitrary_phases(AGState &state){ } -double State::compute_probability(std::vector measured_qubits, std::vector outcomes){ +double State::compute_probability(std::vector measured_qubits, std::vector outcomes){ AGState copied_ag(this->qreg_); //copy constructor TODO check this //first reorder things so the first w qubits are measured - std::vector measured_qubits_sorted(measured_qubits); + std::vector measured_qubits_sorted(measured_qubits); - std::vector qubit_indexes; - for(size_t i = 0; i < this->qreg_.num_qubits; i++){ + std::vector qubit_indexes; + for(uint_t i = 0; i < this->qreg_.num_qubits; i++){ qubit_indexes.push_back(i); } std::sort(measured_qubits_sorted.begin(), measured_qubits_sorted.end()); - for(size_t i = 0; i < measured_qubits.size(); i++){ - size_t w = measured_qubits_sorted[i]; - size_t idx1 = 0; - size_t idx2 = 0; - for(size_t j = 0; j < qubit_indexes.size(); j++){ + for(uint_t i = 0; i < measured_qubits.size(); i++){ + uint_t w = measured_qubits_sorted[i]; + uint_t idx1 = 0; + uint_t idx2 = 0; + for(uint_t j = 0; j < qubit_indexes.size(); j++){ if(qubit_indexes[j] == w){ - idx1 = j; - break; + idx1 = j; + break; } } - for(size_t j = 0; j < measured_qubits.size(); j++){ + for(uint_t j = 0; j < measured_qubits.size(); j++){ if(measured_qubits[j] == w){ - idx2 = j; - break; + idx2 = j; + break; } } @@ -328,29 +328,29 @@ double State::compute_probability(std::vector measured_qubits, std::vect //from this point on we will assume we're looking for the measurement outcome 0 on all measured qubits //so apply X gates to measured qubits where we're looking for outcome 1 to correct this //now all the measured qubits are at the start and the magic qubits are at the end - for(size_t i = 0; i < outcomes.size(); i++){ + for(uint_t i = 0; i < outcomes.size(); i++){ if(outcomes[i] == 1){ copied_ag.applyX(i); } } - size_t w = measured_qubits.size(); - size_t t = copied_ag.magic_phases.size(); + uint_t w = measured_qubits.size(); + uint_t t = copied_ag.magic_phases.size(); //now all the measured qubits are at the start and the magic qubits are at the end - std::pair v_pair = copied_ag.apply_constraints(w, t); + std::pair v_pair = copied_ag.apply_constraints(w, t); if(!v_pair.first){ return 0.; } - size_t v = v_pair.second; + uint_t v = v_pair.second; //at this point we can delete all the non-magic qubits - for(size_t q = 0; q < t; q++){ + for(uint_t q = 0; q < t; q++){ copied_ag.applySwap(q, q+(copied_ag.num_qubits - t)); } - for(size_t s = 0; s < copied_ag.num_stabilizers; s++){ + for(uint_t s = 0; s < copied_ag.num_stabilizers; s++){ copied_ag.table[s].X.resize(t); copied_ag.table[s].Z.resize(t); } @@ -362,7 +362,7 @@ double State::compute_probability(std::vector measured_qubits, std::vect //we can make the compute algorithm much faster if all of our non-Clifford gates are in fact T gates (pi/4 rotations) bool all_phases_are_T = true; - for(size_t i = 0; i < copied_ag.num_qubits; i++){ + for(uint_t i = 0; i < copied_ag.num_qubits; i++){ if(fabs(copied_ag.magic_phases[i] - T_ANGLE) > AG_CHOP_THRESHOLD){ all_phases_are_T = false; break; From 633245c15e124343593c1845c58f9b543278e922 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Thu, 2 Dec 2021 17:11:30 +0100 Subject: [PATCH 24/31] Fix typo in comment --- src/simulators/clifford_plus_phase/compute.hpp | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index a7f853ec43..0c1124d4bd 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -48,9 +48,7 @@ class State: public Base::State{ std::string name() const override {return "clifford_phase_compute";} - //Apply a sequence of operations to the cicuit. - //We just store these operations in _circuit because we can't implement them - //until we know how many non-Clifford gates and what the measurements are + //Apply a sequence of operations to the cicuit virtual void apply_ops(const std::vector &ops, ExperimentResult &result, RngEngine &rng, @@ -66,7 +64,7 @@ class State: public Base::State{ double expval_pauli(const reg_t &qubits, const std::string& pauli); private: const static stringmap_t gateset_; - size_t num_code_qubits; //out AG state has code+magic qubits + size_t num_code_qubits; //our AG state has code+magic qubits double compute_probability(std::vector measured_qubits, std::vector outcomes); }; From 8ee5800b2866ec980f61f6ed40ae09933762c8b3 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Tue, 14 Dec 2021 14:40:22 +0100 Subject: [PATCH 25/31] Add save_specific_prob instruction to measurement type case statements --- src/framework/circuit.hpp | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/framework/circuit.hpp b/src/framework/circuit.hpp index 41987e1e7d..2c8a6f45c7 100755 --- a/src/framework/circuit.hpp +++ b/src/framework/circuit.hpp @@ -177,12 +177,15 @@ Circuit::Circuit(const inputdata_t &circ, const json_t &qobj_config, bool trunca // conversion we could call `get_reversed_ops` on the inputdata without first // converting. std::vector converted_ops; + int i = 0; for(auto the_op: input_ops){ + i += 1; converted_ops.emplace_back(Operations::input_to_op(the_op)); } - ops = std::move(converted_ops); + + ops = std::move(converted_ops); set_params(truncation); - + // Check for specified memory slots uint_t memory_slots = 0; Parser::get_value(memory_slots, "memory_slots", config); @@ -205,7 +208,7 @@ Circuit::Circuit(const inputdata_t &circ, const json_t &qobj_config, bool trunca // is explicitly disabled. num_qubits = n_qubits; } - } + } } //------------------------------------------------------------------------- @@ -354,6 +357,7 @@ void Circuit::set_params(bool truncation) { case OpType::save_probs: case OpType::save_probs_ket: case OpType::save_amps: + case OpType::save_specific_prob: case OpType::save_amps_sq: case OpType::save_stabilizer: case OpType::save_unitary: @@ -467,6 +471,7 @@ bool Circuit::check_result_ancestor(const Op& op, std::unordered_set& an case OpType::save_densmat: case OpType::save_probs: case OpType::save_probs_ket: + case OpType::save_specific_prob: case OpType::save_amps: case OpType::save_amps_sq: case OpType::save_stabilizer: From ede2628455a75df3d24c46bc5de7c2e6622729a7 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Tue, 14 Dec 2021 14:53:30 +0100 Subject: [PATCH 26/31] changes to fix compatibility with new state class --- src/controllers/aer_controller.hpp | 197 +----------------- .../clifford_plus_phase/compute.hpp | 49 +++-- 2 files changed, 34 insertions(+), 212 deletions(-) diff --git a/src/controllers/aer_controller.hpp b/src/controllers/aer_controller.hpp index 995867b4b7..52998b1266 100755 --- a/src/controllers/aer_controller.hpp +++ b/src/controllers/aer_controller.hpp @@ -628,6 +628,8 @@ void Controller::set_parallelization_circuit(const Circuit &circ, } case Method::extended_stabilizer: break; + case Method::clifford_phase_compute: + break; default: throw std::invalid_argument("Cannot set parallelization for unresolved method."); } @@ -1306,199 +1308,6 @@ void Controller::run_circuit(const Circuit &circ, //------------------------------------------------------------------------- // Utility methods //------------------------------------------------------------------------- -<<<<<<< HEAD -Controller::Method -Controller::simulation_method(const Circuit &circ, - const Noise::NoiseModel &noise_model, - bool validate) const { - // Check simulation method and validate state - switch (sim_method_) { - case Method::statevector: { - if (validate) { - if (sim_device_ == Device::CPU) { - if (sim_precision_ == Precision::Single) { - Statevector::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - Statevector::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } - } else { -#ifdef AER_THRUST_SUPPORTED - if (sim_precision_ == Precision::Single) { - Statevector::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - Statevector::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } -#endif - } - } - return Method::statevector; - } - case Method::density_matrix: { - if (validate) { - if (sim_device_ == Device::CPU) { - if (sim_precision_ == Precision::Single) { - DensityMatrix::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - DensityMatrix::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } - } else { -#ifdef AER_THRUST_SUPPORTED - if (sim_precision_ == Precision::Single) { - DensityMatrix::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - DensityMatrix::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } -#endif - } - } - return Method::density_matrix; - } - case Method::unitary: { - if (validate) { - if (sim_device_ == Device::CPU) { - if (sim_precision_ == Precision::Single) { - QubitUnitary::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - QubitUnitary::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } - } else { -#ifdef AER_THRUST_SUPPORTED - if (sim_precision_ == Precision::Single) { - QubitUnitary::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - QubitUnitary::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } -#endif - } - } - return Method::unitary; - } - case Method::superop: { - if (validate) { - if (sim_precision_ == Precision::Single) { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } else { - QubitSuperoperator::State> state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } - } - return Method::superop; - } - case Method::stabilizer: { - if (validate) { - Stabilizer::State state; - validate_state(Stabilizer::State(), circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } - return Method::stabilizer; - } - case Method::extended_stabilizer: { - if (validate) { - ExtendedStabilizer::State state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(ExtendedStabilizer::State(), circ, true); - } - return Method::extended_stabilizer; - } - case Method::matrix_product_state: { - if (validate) { - MatrixProductState::State state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(state, circ, true); - } - return Method::matrix_product_state; - } - case Method::clifford_phase_compute: { - if (validate) { - CliffPhaseCompute::State state; - validate_state(state, circ, noise_model, true); - validate_memory_requirements(CliffPhaseCompute::State(), circ, true); - } - return Method::clifford_phase_compute; - } - case Method::automatic: { - // If circuit and noise model are Clifford run on Stabilizer simulator - if (validate_state(Stabilizer::State(), circ, noise_model, false)) { - return Method::stabilizer; - } - // For noisy simulations we enable the density matrix method if - // shots > 2 ** num_qubits. This is based on a rough estimate that - // a single shot of the density matrix simulator is approx 2 ** nq - // times slower than a single shot of statevector due the increased - // dimension - if (noise_model.has_quantum_errors() && - circ.shots > (1ULL << circ.num_qubits) && - validate_memory_requirements(DensityMatrix::State<>(), circ, false) && - validate_state(DensityMatrix::State<>(), circ, noise_model, false) && - check_measure_sampling_opt(circ, Method::density_matrix)) { - return Method::density_matrix; - } - - // If the special conditions for stabilizer or density matrix are - // not satisfied we choose simulation method based on supported - // operations only with preference given by memory requirements - // statevector > density matrix > matrix product state > unitary > superop - // typically any save state instructions will decide the method. - if (validate_state(Statevector::State<>(), circ, noise_model, false)) { - return Method::statevector; - } - if (validate_state(DensityMatrix::State<>(), circ, noise_model, false)) { - return Method::density_matrix; - } - if (validate_state(MatrixProductState::State(), circ, noise_model, false)) { - return Method::matrix_product_state; - } - if (validate_state(QubitUnitary::State<>(), circ, noise_model, false)) { - return Method::unitary; - } - if (validate_state(QubitSuperoperator::State<>(), circ, noise_model, false)) { - return Method::superop; - } - // If we got here, circuit isn't compatible with any of the simulation - // methods - std::stringstream msg; - msg << "AerSimulator: "; - if (noise_model.is_ideal()) { - msg << "circuit with instructions " << circ.opset(); - } else { - auto opset = circ.opset(); - opset.insert(noise_model.opset()); - msg << "circuit and noise model with instructions" << opset; - } - msg << " is not compatible with any of the automatic simulation methods"; - throw std::runtime_error(msg.str()); - }} -} -======= ->>>>>>> 8ac51d88def7e406bd77f5f96879ba3443cca5e2 - size_t Controller::required_memory_mb(const Circuit &circ, const Noise::NoiseModel &noise, const Method method) const { @@ -2014,6 +1823,8 @@ bool Controller::validate_method(Method method, return validate_state(Stabilizer::State(), circ, noise_model, throw_except); case Method::extended_stabilizer: return validate_state(ExtendedStabilizer::State(), circ, noise_model, throw_except); + case Method::clifford_phase_compute: + return validate_state(CliffPhaseCompute::State(), circ, noise_model, throw_except); case Method::matrix_product_state: return validate_state(MatrixProductState::State(), circ, noise_model, throw_except); case Method::statevector: diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index ff07a760a4..7f24f6a724 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -49,10 +49,16 @@ class State: public Base::State{ std::string name() const override {return "clifford_phase_compute";} //Apply a sequence of operations to the cicuit - virtual void apply_ops(const std::vector &ops, - ExperimentResult &result, - RngEngine &rng, - bool final_ops = false) override; + template + void apply_ops(InputIterator first, InputIterator last, + ExperimentResult &result, + RngEngine &rng, + bool final_ops = false); + + virtual void apply_op(const Operations::Op &op, + ExperimentResult &result, + RngEngine &rng, + bool final_op = false) override; void apply_gate(const Operations::Op &op); @@ -94,24 +100,29 @@ const stringmap_t State::gateset_({ // Three-qubit gates }); +template +void State::apply_ops(InputIterator first, InputIterator last, ExperimentResult &result, RngEngine &rng, bool final_ops){ + for(auto it = first; it != last; ++it){ + apply_op(*it, result, rng, final_ops); + } +} -void State::apply_ops(const std::vector &ops, ExperimentResult &result, RngEngine &rng, bool final_ops){ - - for(const auto &op: ops){ - switch(op.type){ - case Operations::OpType::gate: - this->apply_gate(op); - break; - case Operations::OpType::save_specific_prob: - this->apply_save_specific_prob(op, result); - break; - default: - throw std::invalid_argument("Compute::State::invalid instruction \'" + op.name + "\'."); - } +void State::apply_op(const Operations::Op &op, ExperimentResult &result, + RngEngine &rng, bool final_op) { + switch(op.type){ + case Operations::OpType::gate: + this->apply_gate(op); + break; + case Operations::OpType::save_specific_prob: + this->apply_save_specific_prob(op, result); + break; + default: + throw std::invalid_argument("Compute::State::invalid instruction \'" + op.name + "\'."); } } -void State::apply_gate(const Operations::Op &op){ + +void State::apply_gate(const Operations::Op &op){ auto it = gateset_.find(op.name); if (it == gateset_.end()) { @@ -162,7 +173,7 @@ void State::apply_gate(const Operations::Op &op){ } } -void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result){ +void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result){ std::vector v; double p = this->compute_probability(op.qubits, op.int_params); v.push_back(p); From be8c0ca5ce9b83de9041857bdbe1041a33704441 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Tue, 14 Dec 2021 18:36:01 +0100 Subject: [PATCH 27/31] fix failing build due to new state serialisation code --- src/simulators/clifford_plus_phase/ag_state.hpp | 6 ++++++ src/simulators/clifford_plus_phase/compute.hpp | 5 ++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index 1135b5934e..6a4cf4ad1f 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -898,6 +898,12 @@ size_t AGState::apply_T_constraints(){ return deleted_rows; } +inline void to_json(json_t &js, const AGState &state) +{ + js["num_qubits"] = state.num_qubits; + js["num_stabilizers"] = state.num_stabilizers; +} + } } #endif diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index 7f24f6a724..a6ea73c125 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -174,10 +174,9 @@ void State::apply_gate(const Operations::Op &op){ } void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result){ - std::vector v; + std::cout << op.type << std::endl; double p = this->compute_probability(op.qubits, op.int_params); - v.push_back(p); - BaseState::save_data_average(result, op.string_params[0], std::move(v), Operations::DataSubType::list); + save_data_average(result, op.string_params[0], p, op.type, op.save_type); } // This function converts an unsigned binary number to reflected binary Gray code. From e36d18b19b62ccad4940ac457bf48d379d5b2640 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Wed, 15 Dec 2021 12:31:51 +0100 Subject: [PATCH 28/31] fix type error causing issues on macOS --- src/simulators/clifford_plus_phase/compute.hpp | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index a6ea73c125..bd7117cbbe 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -72,7 +72,7 @@ class State: public Base::State{ const static stringmap_t gateset_; size_t num_code_qubits; //our AG state has code+magic qubits - double compute_probability(std::vector measured_qubits, std::vector outcomes); + double compute_probability(std::vector measured_qubits, std::vector outcomes); }; @@ -174,7 +174,6 @@ void State::apply_gate(const Operations::Op &op){ } void State::apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result){ - std::cout << op.type << std::endl; double p = this->compute_probability(op.qubits, op.int_params); save_data_average(result, op.string_params[0], p, op.type, op.save_type); } From 8fcaf43bb05dadace2ae42ccb596a50de10745df Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Wed, 15 Dec 2021 12:53:40 +0100 Subject: [PATCH 29/31] fix shadowed definitions and signed vs unsigned int comparison --- src/simulators/clifford_plus_phase/ag_state.hpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index 6a4cf4ad1f..bc29f26701 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -27,12 +27,12 @@ const uint_t ONE = 1u; class AGState{ public: //initialize such that the jth stabilizer is the Pauli Z_j - AGState(uint_t num_qubits, uint_t num_stabilizers) : num_qubits(num_qubits), num_stabilizers(num_stabilizers) {}; + AGState(uint_t qubits, uint_t stabilizers) : num_qubits(qubits), num_stabilizers(stabilizers) {}; AGState() : num_qubits(0), num_stabilizers(0) {}; - AGState(uint_t num_qubits) : num_qubits(num_qubits), num_stabilizers(num_qubits) {}; + AGState(uint_t qubits) : num_qubits(qubits), num_stabilizers(qubits) {}; void initialize(); - void initialize(uint_t num_qubits); + void initialize(uint_t qubits); size_t num_qubits; size_t num_stabilizers; //we can represent mixed states so we may have fewer stabilizers than qubits @@ -131,10 +131,10 @@ void AGState::initialize() } // Implementation -void AGState::initialize(uint_t num_qubits) +void AGState::initialize(uint_t qubits) { - this->num_qubits = num_qubits; - this->num_stabilizers = num_qubits; + this->num_qubits = qubits; + this->num_stabilizers = qubits; this->initialize(); } @@ -450,7 +450,7 @@ std::vector AGState::simplifying_unitary(){ //we swap columns (using CX) to make the X part into a kxk identity followed by a "junk" block - for(int r = 0; r < this->num_stabilizers; r++){ + for(size_t r = 0; r < this->num_stabilizers; r++){ if(!this->table[r].X[r]){ size_t col = this->first_non_zero_in_row(r, 0).second; @@ -793,7 +793,7 @@ std::pair AGState::apply_constraints(size_t w, size_t t){ } //now we just delete the non-identity guys on this qubit - int num_to_delete = 0; + uint_t num_to_delete = 0; if(y_stab.first){ //if we have a Y stab we don't have either of the others this->swap_rows(y_stab.second, this->num_stabilizers-1); From c05574e7529a34dcb2e8f30e124645a34740c502 Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Wed, 9 Feb 2022 12:49:18 +0100 Subject: [PATCH 30/31] correct implementation of compute algorithm for non-T gates --- .../providers/aer/backends/aer_simulator.py | 2 + .../clifford_plus_phase/ag_state.hpp | 61 +++--- .../clifford_plus_phase/compute.hpp | 207 +++++++++++++----- 3 files changed, 188 insertions(+), 82 deletions(-) diff --git a/qiskit/providers/aer/backends/aer_simulator.py b/qiskit/providers/aer/backends/aer_simulator.py index 899554dc23..8fb4ea05f3 100644 --- a/qiskit/providers/aer/backends/aer_simulator.py +++ b/qiskit/providers/aer/backends/aer_simulator.py @@ -139,6 +139,8 @@ class AerSimulator(AerBackend): +--------------------------+---------------+ | ``extended_stabilizer`` | No | +--------------------------+---------------+ + | ``compute`` | No | + +--------------------------+---------------+ | ``unitary`` | Yes | +--------------------------+---------------+ | ``superop`` | No | diff --git a/src/simulators/clifford_plus_phase/ag_state.hpp b/src/simulators/clifford_plus_phase/ag_state.hpp index bc29f26701..b610ef6a84 100644 --- a/src/simulators/clifford_plus_phase/ag_state.hpp +++ b/src/simulators/clifford_plus_phase/ag_state.hpp @@ -4,7 +4,7 @@ #include #include #include - +#include #include "framework/types.hpp" #include "framework/operations.hpp" #include "simulators/stabilizer/pauli.hpp" @@ -195,7 +195,7 @@ void AGState::applyH(size_t a){ void AGState::applyS(size_t a){ for(size_t i = 0; i < this->num_stabilizers; i++){ - this->phases[i] ^= this->table[i].X[a] & this->table[i].Z[a]; + this->phases[i] ^= (this->table[i].X[a] & this->table[i].Z[a]); this->table[i].Z.xorAt(this->table[i].X[a], a); } } @@ -224,49 +224,52 @@ void AGState::applyZ(size_t a){ } void AGState::gadgetized_phase_gate(size_t a, double phase){ - for(size_t i = 0; i < this->num_stabilizers; i++){ - this->table[i].X.resize(this->num_qubits + 1); - this->table[i].Z.resize(this->num_qubits + 1); + //phase = fmod(phase , M_PI*2); + while(phase > (M_PI*2)){ + phase -= (M_PI*2); } - this->table.push_back(Pauli::Pauli(this->num_qubits + 1)); - this->table[this->num_stabilizers].Z.set1(this->num_qubits); - this->phases.push_back(0); - this->num_stabilizers += 1; - this->num_qubits += 1; - - - - phase = fmod(phase , M_PI*2); - if(phase < 0){ - phase += M_PI*2; + while(phase < 0){ + phase += (M_PI*2); } - + //now phase is in [0, 2*pi) - while(phase > M_PI/2){ - phase -= M_PI/2; - this->applyS(a); + while(phase > M_PI/2.){ + phase -= (M_PI/2.); + this->applyS(a); } - //now phase is in [0, M_PI/2.] + //now phase is in [0, M_PI/2.] if(fabs(phase) < AG_CHOP_THRESHOLD){ //phase on gate is zero so it is an identity }else if(fabs(phase - M_PI/2.) < AG_CHOP_THRESHOLD){ //phase on gate is pi/2 so it is an S this->applyS(a); - this->applyCX(a, this->num_stabilizers-1); + }else{ //its actually non-Clifford + for(size_t i = 0; i < this->num_stabilizers; i++){ + this->table[i].X.resize(this->num_qubits + 1); + this->table[i].Z.resize(this->num_qubits + 1); + } + this->table.push_back(Pauli::Pauli(this->num_qubits + 1)); + this->table[this->num_stabilizers].Z.set0(this->num_qubits); + this->table[this->num_stabilizers].Z.set1(this->num_qubits); + this->phases.push_back(0); + this->num_stabilizers += 1; + this->num_qubits += 1; + //we want our phases to be in [0, pi/4] if(phase > T_ANGLE){ - phase -= M_PI/2 - phase; + phase = M_PI/2 - phase; + this->applyS(a); this->applyX(a); - this->applyCX(a, this->num_stabilizers-1); + this->applyCX(a, this->num_qubits-1); this->applyX(a); }else{ - this->applyCX(a, this->num_stabilizers-1); - } - } - this->magic_phases.push_back(phase); + this->applyCX(a, this->num_qubits-1); + } + this->magic_phases.push_back(phase); + } } void AGState::rowsum(size_t h, size_t i){ @@ -902,6 +905,8 @@ inline void to_json(json_t &js, const AGState &state) { js["num_qubits"] = state.num_qubits; js["num_stabilizers"] = state.num_stabilizers; + js["phases"] = state.phases; + js["magic_phases"] = state.magic_phases; } } diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index bd7117cbbe..e2d2ca0abf 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -65,14 +65,16 @@ class State: public Base::State{ void initialize_qreg(uint_t num_qubits) override; void initialize_qreg(uint_t num_qubits, const agstate_t &state) override; size_t required_memory_mb(uint_t num_qubits, const std::vector &ops) const override; - void apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result); double expval_pauli(const reg_t &qubits, const std::string& pauli); private: const static stringmap_t gateset_; - + double compute_algorithm_all_phases_T(AGState &state); + double compute_algorithm_arbitrary_phases(AGState &state); size_t num_code_qubits; //our AG state has code+magic qubits double compute_probability(std::vector measured_qubits, std::vector outcomes); + template + uint_t count_magic_gates(InputIterator first, InputIterator last) const; }; @@ -102,6 +104,7 @@ const stringmap_t State::gateset_({ template void State::apply_ops(InputIterator first, InputIterator last, ExperimentResult &result, RngEngine &rng, bool final_ops){ + //std::cout << "applying gates" << std::endl; for(auto it = first; it != last; ++it){ apply_op(*it, result, rng, final_ops); } @@ -109,11 +112,15 @@ void State::apply_ops(InputIterator first, InputIterator last, ExperimentResult void State::apply_op(const Operations::Op &op, ExperimentResult &result, RngEngine &rng, bool final_op) { + //std::cout << "applying op "; switch(op.type){ case Operations::OpType::gate: + //std::cout << "gate" << std::endl; + //std::cout << op << std::endl; this->apply_gate(op); break; case Operations::OpType::save_specific_prob: + //std::cout << "save" << std::endl; this->apply_save_specific_prob(op, result); break; default: @@ -121,8 +128,8 @@ void State::apply_op(const Operations::Op &op, ExperimentResult &result, } } - void State::apply_gate(const Operations::Op &op){ + //std::cout << "apply_gate" << std::endl; auto it = gateset_.find(op.name); if (it == gateset_.end()) { @@ -131,41 +138,55 @@ void State::apply_gate(const Operations::Op &op){ } switch(it->second){ case Gates::id: + //std::cout << "id" << std::endl; break; case Gates::x: + //std::cout << "x" << std::endl; this->qreg_.applyX(op.qubits[0]); break; case Gates::y: + //std::cout << "y" << std::endl; this->qreg_.applyY(op.qubits[0]); break; case Gates::z: + //std::cout << "z" << std::endl; this->qreg_.applyZ(op.qubits[0]); break; case Gates::s: + //std::cout << "s" << std::endl; this->qreg_.applyS(op.qubits[0]); break; case Gates::sdg: + //std::cout << "sdg" << std::endl; this->qreg_.applyZ(op.qubits[0]); this->qreg_.applyS(op.qubits[0]); break; - case Gates::h: + case Gates::h: + //std::cout << "h" << std::endl; this->qreg_.applyH(op.qubits[0]); break; case Gates::t: + //std::cout << "t" << std::endl; this->qreg_.gadgetized_phase_gate(op.qubits[0], T_ANGLE); break; case Gates::tdg: + //std::cout << "tdg" << std::endl; this->qreg_.gadgetized_phase_gate(op.qubits[0], -T_ANGLE); break; case Gates::rz: + //std::cout << "rz" << std::endl; this->qreg_.gadgetized_phase_gate(op.qubits[0], op.params[0].real()); + break; case Gates::cx: + //std::cout << "cx[" << op.qubits[0] << ", " << op.qubits[1] << "]" <qreg_.applyCX(op.qubits[0],op.qubits[1]); break; case Gates::cz: + //std::cout << "cz" << std::endl; this->qreg_.applyCZ(op.qubits[0],op.qubits[1]); break; case Gates::swap: + //std::cout << "swap" << std::endl; this->qreg_.applySwap(op.qubits[0],op.qubits[1]); break; default: //u0 or Identity @@ -185,39 +206,68 @@ uint_t BinaryToGray(uint_t num) } -double compute_algorithm_all_phases_T(AGState &state){ +double State::compute_algorithm_all_phases_T(AGState &state){ + //std::cout << "State::compute_algorithm_all_phases_T" << std::endl; + if(state.num_stabilizers > 63){ + //we use an int_t == int_fast64_t variable to store our loop counter, this means we can deal with at most 63 stabilizers + //in the case of 63 stabilizers we will iterate over all length 63 bitstrings + //this is the most we can store in a signed 64 bit integer + //the integer has to be signed due to openMP's requirements + //realistically a computation with 63 stabilizers = 2^63 - 1 iterations is not going to terminate anyway so this restriction shouldn't matter + std::stringstream msg; + msg << "CliffPhaseCompute::State::compute_algorithm_all_phases_T called with " << state.num_stabilizers << " stabilizers. Maximum possible is 63."; + throw std::runtime_error(msg.str()); + } - uint_t full_mask = 0u; + uint_t full_mask = 0; for(size_t i = 0; i < state.num_stabilizers; i++){ full_mask |= (ONE << i); } - double acc = 1.; + double acc = 0.; Pauli::Pauli row(state.num_qubits); unsigned char phase = 0; - for(uint_t mask = 1u; mask <= full_mask; mask++){ - uint_t mask_with_bit_to_flip = BinaryToGray(mask) ^ BinaryToGray(mask - 1); - size_t bit_to_flip = 0; - for(size_t j = 0; j < state.num_stabilizers; j++){ - if((mask_with_bit_to_flip >> j) & ONE){ - bit_to_flip = j; - break; + bool row_initialised = false; + uint_t num_threads_ = BaseState::threads_; + + int_t chunk_size = 0; + #pragma omp parallel for num_threads(num_threads_) + for (int_t thread = 0; thread < num_threads_; ++thread) { + // identify a chunk from i and num_threads + } + + #pragma omp parallel for if(num_threads_ > 1) \ + num_threads(num_threads_) firstprivate(row_initialised, row, phase) shared(state) reduction(+:acc) + for(uint_t mask = 0; mask <= full_mask; mask++){ + if(row_initialised){ + uint_t mask_with_bit_to_flip = BinaryToGray((uint_t)mask) ^ BinaryToGray((uint_t)(mask - 1)); + size_t bit_to_flip = 0; + for(size_t j = 0; j < state.num_stabilizers; j++){ + if((mask_with_bit_to_flip >> j) & ONE){ + bit_to_flip = j; + break; + } } + phase += state.phases[bit_to_flip] + (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); //phases for stabilizers are always 1 or -1 + phase %= 2; + row += state.table[bit_to_flip]; + }else{ + int_t mask_with_bit_to_flip = BinaryToGray(mask); // note BinaryToGray(0u) == 0u + for(size_t j = 0; j < state.num_stabilizers; j++){ + if((mask_with_bit_to_flip >> j) & ONE){ + phase += state.phases[j] + (Pauli::Pauli::phase_exponent(row, state.table[j]) / 2); //phases for stabilizers are always 1 or -1 + phase %= 2; + row += state.table[j]; + } + } + row_initialised = true; } - - phase += state.phases[bit_to_flip] + (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); //phases for stabilizers are always 1 or -1 - phase %= 2; - row += state.table[bit_to_flip]; - size_t XCount = 0; size_t YCount = 0; size_t ZCount = 0; for(size_t j = 0; j < state.num_qubits; j++){ - //if((row[j] == 0) && (row[j+state.n] == 0)){ - // ICount += 1; - //} if(row.X[j] && !row.Z[j]){ XCount += 1; } @@ -245,7 +295,21 @@ double compute_algorithm_all_phases_T(AGState &state){ return acc; } -double compute_algorithm_arbitrary_phases(AGState &state){ +double State::compute_algorithm_arbitrary_phases(AGState &state){ + + if(state.num_stabilizers > 63){ + //we use an int_t == int_fast64_t variable to store our loop counter, this means we can deal with at most 63 stabilizers + //in the case of 63 stabilizers we will iterate over all length 63 bitstrings + //this is the most we can store in a signed 64 bit integer + //the integer has to be signed due to openMP's requirements + //realistically a computation with 63 stabilizers = 2^63 - 1 iterations is not going to terminate anyway so this restriction shouldn't matter + std::stringstream msg; + msg << "CliffPhaseCompute::State::compute_algorithm_arbitrary_phases called with " << state.num_stabilizers << " stabilizers. Maximum possible is 63."; + throw std::runtime_error(msg.str()); + } + + + //std::cout << "1" << std::endl; uint_t full_mask = 0u; for(size_t i = 0; i < state.num_stabilizers; i++){ full_mask |= (ONE << i); @@ -253,9 +317,9 @@ double compute_algorithm_arbitrary_phases(AGState &state){ double acc = 1.; Pauli::Pauli row(state.num_qubits); unsigned char phase = 0; - + //std::cout << "2" << std::endl; for(uint_t mask = 1u; mask <= full_mask; mask++){ - uint_t mask_with_bit_to_flip = BinaryToGray(mask) ^ BinaryToGray(mask - 1); + uint_t mask_with_bit_to_flip = BinaryToGray((uint_t)mask) ^ BinaryToGray((uint_t)(mask - 1)); size_t bit_to_flip = 0; for(size_t j = 0; j < state.num_stabilizers; j++){ if((mask_with_bit_to_flip >> j) & ONE){ @@ -264,14 +328,13 @@ double compute_algorithm_arbitrary_phases(AGState &state){ } } - phase += (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); //phases for stabilizers are always 0 or 2 + phase += Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2; //phases for stabilizers are always 0 or 2 + phase += state.phases[bit_to_flip]; + phase %= 2; row += state.table[bit_to_flip]; - double prod = 1.; + double prod = (phase==1) ? -1. : 1.; for(size_t j = 0; j < state.num_qubits; j++){ - //if((row[j] == 0) && (row[j+state.n] == 0)){ - // ICount += 1; - //} if(row.X[j] && !row.Z[j]){ prod *= cos(state.magic_phases[j]); } @@ -280,36 +343,31 @@ double compute_algorithm_arbitrary_phases(AGState &state){ break; } if(row.X[j] && row.Z[j]){ - prod *= -sin(state.magic_phases[j]); + prod *= (-sin(state.magic_phases[j])); } } - if(phase){ - acc -= prod; - }else{ - acc += prod; - } + acc += prod; } - + //std::cout << "3" << std::endl; if(full_mask == 0u){ - return 1.; + acc=1; } return acc; } double State::compute_probability(std::vector measured_qubits, std::vector outcomes){ - + //std::cout << "State::compute_probability" << std::endl; AGState copied_ag(this->qreg_); //copy constructor TODO check this //first reorder things so the first w qubits are measured std::vector measured_qubits_sorted(measured_qubits); - + std::vector qubit_indexes; for(uint_t i = 0; i < this->qreg_.num_qubits; i++){ qubit_indexes.push_back(i); } std::sort(measured_qubits_sorted.begin(), measured_qubits_sorted.end()); - for(uint_t i = 0; i < measured_qubits.size(); i++){ uint_t w = measured_qubits_sorted[i]; uint_t idx1 = 0; @@ -328,19 +386,22 @@ double State::compute_probability(std::vector measured_qubits, std::vect } if(idx1 != idx2){ - std::swap(qubit_indexes[idx1], qubit_indexes[idx2]); + //std::cout << "swapping " << idx1 << " and " << idx2 << std::endl; + //std::swap(qubit_indexes[idx1], qubit_indexes[idx2]); copied_ag.applySwap(idx1, idx2); } } - //from this point on we will assume we're looking for the measurement outcome 0 on all measured qubits - //so apply X gates to measured qubits where we're looking for outcome 1 to correct this //now all the measured qubits are at the start and the magic qubits are at the end + //from this point on we will assume we're looking for the measurement outcome 0 on all measured qubits + //so apply X gates to measured qubits where we're looking for outcome 1 to correct this for(uint_t i = 0; i < outcomes.size(); i++){ - if(outcomes[i] == 1){ + //std::cout << "outcomes[" << i << "] = " << outcomes[i] << " "; + if(outcomes[i] == 1){ copied_ag.applyX(i); } } + //std::cout << std::endl; uint_t w = measured_qubits.size(); uint_t t = copied_ag.magic_phases.size(); @@ -348,6 +409,7 @@ double State::compute_probability(std::vector measured_qubits, std::vect //now all the measured qubits are at the start and the magic qubits are at the end std::pair v_pair = copied_ag.apply_constraints(w, t); + if(!v_pair.first){ return 0.; } @@ -376,35 +438,72 @@ double State::compute_probability(std::vector measured_qubits, std::vect break; } } - - + //std::cout << copied_ag.magic_phases.size() << std::endl; + //std::cout << copied_ag.magic_phases[0] << ", " << cos(copied_ag.magic_phases[0]) << ", " << -sin(copied_ag.magic_phases[0]) << std::endl; if(copied_ag.num_qubits == 0){ - return powl(2., (double)v - (double)w); + return pow(2., ((double)v) - ((double)w)); } + //std::cout << "a" << std::endl; if(all_phases_are_T){ - return compute_algorithm_all_phases_T(copied_ag) * powl(2., (double)v - w); + //std::cout << "a" << std::endl; + return compute_algorithm_all_phases_T(copied_ag) * pow(2., ((double)v) - ((double)w)); } else { - return compute_algorithm_arbitrary_phases(copied_ag) * powl(2., (double)v - w); + //std::cout << "b" << std::endl; + return compute_algorithm_arbitrary_phases(copied_ag) * pow(2., ((double)v) - ((double)w)); } } -void State::initialize_qreg(uint_t num_qubits){ +void State::initialize_qreg(uint_t num_qubits) { this->qreg_.initialize(num_qubits); this->num_code_qubits = num_qubits; } -void State::initialize_qreg(uint_t num_qubits, const agstate_t &state){ +void State::initialize_qreg(uint_t num_qubits, const agstate_t &state) { if(BaseState::qreg_.num_qubits != num_qubits){ throw std::invalid_argument("CH::State::initialize: initial state does not match qubit number."); } BaseState::qreg_ = state; } +template +uint_t State::count_magic_gates(InputIterator first, InputIterator last) const { + uint_t count = 0; + for (auto op = first; op != last; op++) + { + auto it = gateset_.find(op->name); + if (it != gateset_.end()) + { + if(it->second == Gates::t || it->second == Gates::rz || it->second == Gates::tdg){ + count += 1; + } + } + } + return count; +} + + size_t State::required_memory_mb(uint_t num_qubits, const std::vector &ops) const { - return 0; //TODO update this! + uint_t t = count_magic_gates(ops.cbegin(), ops.cend()); + + uint_t total_qubits = num_qubits + t; + + // we store a stabilizer tableau with n+t stabilizers on n+t qubits + // each stabilizer (each "row" in the table) is a Pauli::Pauli on n+t qubits + // each Pauli:: Pauli stores two BV::BinaryVectors of length n+t and in addition needs one byte of space we use to store the phase + // each BinaryVector stores 8 * ceil((n+t)/64) bytes + //note that this is an upper bound, in reality the compress algorithm will give us a state with fewer qubits and stabilizers + + //in addition each working thread gets a Pauli (two binary vectors and a phase) of space to do its work in + uint_t bv_size_bytes = 8*((num_qubits+t)/64 + ((((num_qubits+t) % 64)) ? 1 : 0)); //add an extra 8 byte int that we partially use if there is a remainder + uint_t pauli_size_bytes = 2*bv_size_bytes + 1; + + // State::compute_probability copies the stabilizer tableau before operating on it so double the memory required for that + size_t mb = (pauli_size_bytes * (2*(num_qubits+t)+BaseState::threads_) + t*sizeof(double))/(1<<20); + + return mb; } -double State::expval_pauli(const reg_t &qubits, const std::string& pauli){ +double State::expval_pauli(const reg_t &qubits, const std::string& pauli) { return 0; //TODO fix this } From 535e01d9a3e7370182ee8fb16d63faf7094658db Mon Sep 17 00:00:00 2001 From: Oliver Reardon-Smith Date: Wed, 9 Feb 2022 19:27:04 +0100 Subject: [PATCH 31/31] parallelisation for compute algorithm --- .../clifford_plus_phase/compute.hpp | 364 ++++++++++-------- 1 file changed, 208 insertions(+), 156 deletions(-) diff --git a/src/simulators/clifford_plus_phase/compute.hpp b/src/simulators/clifford_plus_phase/compute.hpp index e2d2ca0abf..5389a5e8e3 100644 --- a/src/simulators/clifford_plus_phase/compute.hpp +++ b/src/simulators/clifford_plus_phase/compute.hpp @@ -27,12 +27,12 @@ const Operations::OpSet StateOpSet( "s", "sdg", "t","p", "rz", "u1"}, // Snapshots {} -); + ); enum class Gates { -id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, p, rz, u1, + id, x, y, z, h, s, sdg, sx, t, tdg, cx, cz, swap, p, rz, u1, }; @@ -42,7 +42,7 @@ using agstate_t = CliffPhaseCompute::AGState; class State: public Base::State{ public: using BaseState = Base::State; - + State() : BaseState(StateOpSet) {}; virtual ~State() = default; @@ -51,15 +51,15 @@ class State: public Base::State{ //Apply a sequence of operations to the cicuit template void apply_ops(InputIterator first, InputIterator last, - ExperimentResult &result, - RngEngine &rng, - bool final_ops = false); - + ExperimentResult &result, + RngEngine &rng, + bool final_ops = false); + virtual void apply_op(const Operations::Op &op, - ExperimentResult &result, - RngEngine &rng, - bool final_op = false) override; - + ExperimentResult &result, + RngEngine &rng, + bool final_op = false) override; + void apply_gate(const Operations::Op &op); void initialize_qreg(uint_t num_qubits) override; @@ -67,7 +67,7 @@ class State: public Base::State{ size_t required_memory_mb(uint_t num_qubits, const std::vector &ops) const override; void apply_save_specific_prob(const Operations::Op &op, ExperimentResult &result); double expval_pauli(const reg_t &qubits, const std::string& pauli); -private: +private: const static stringmap_t gateset_; double compute_algorithm_all_phases_T(AGState &state); double compute_algorithm_arbitrary_phases(AGState &state); @@ -79,32 +79,31 @@ class State: public Base::State{ const stringmap_t State::gateset_({ - // Single qubit gates - {"delay", Gates::id}, // Delay gate - {"id", Gates::id}, // Pauli-Identity gate - {"x", Gates::x}, // Pauli-X gate - {"y", Gates::y}, // Pauli-Y gate - {"z", Gates::z}, // Pauli-Z gate - {"s", Gates::s}, // Phase gate (aka sqrt(Z) gate) - {"sdg", Gates::sdg}, // Conjugate-transpose of Phase gate - {"h", Gates::h}, // Hadamard gate (X + Z / sqrt(2)) - {"t", Gates::t}, // T-gate (sqrt(S)) - {"rz", Gates::rz}, // Pauli-Z rotation gate - {"p", Gates::rz}, // Parameterized phase gate - {"u1", Gates::rz}, - {"tdg", Gates::tdg}, // Conjguate-transpose of T gate - // Two-qubit gates - {"CX", Gates::cx}, // Controlled-X gate (CNOT) - {"cx", Gates::cx}, // Controlled-X gate (CNOT) - {"CZ", Gates::cz}, // Controlled-Z gate - {"cz", Gates::cz}, // Controlled-Z gate - {"swap", Gates::swap}, // SWAP gate - // Three-qubit gates -}); + // Single qubit gates + {"delay", Gates::id}, // Delay gate + {"id", Gates::id}, // Pauli-Identity gate + {"x", Gates::x}, // Pauli-X gate + {"y", Gates::y}, // Pauli-Y gate + {"z", Gates::z}, // Pauli-Z gate + {"s", Gates::s}, // Phase gate (aka sqrt(Z) gate) + {"sdg", Gates::sdg}, // Conjugate-transpose of Phase gate + {"h", Gates::h}, // Hadamard gate (X + Z / sqrt(2)) + {"t", Gates::t}, // T-gate (sqrt(S)) + {"rz", Gates::rz}, // Pauli-Z rotation gate + {"p", Gates::rz}, // Parameterized phase gate + {"u1", Gates::rz}, + {"tdg", Gates::tdg}, // Conjguate-transpose of T gate + // Two-qubit gates + {"CX", Gates::cx}, // Controlled-X gate (CNOT) + {"cx", Gates::cx}, // Controlled-X gate (CNOT) + {"CZ", Gates::cz}, // Controlled-Z gate + {"cz", Gates::cz}, // Controlled-Z gate + {"swap", Gates::swap}, // SWAP gate + // Three-qubit gates + }); template void State::apply_ops(InputIterator first, InputIterator last, ExperimentResult &result, RngEngine &rng, bool final_ops){ - //std::cout << "applying gates" << std::endl; for(auto it = first; it != last; ++it){ apply_op(*it, result, rng, final_ops); } @@ -112,24 +111,19 @@ void State::apply_ops(InputIterator first, InputIterator last, ExperimentResult void State::apply_op(const Operations::Op &op, ExperimentResult &result, RngEngine &rng, bool final_op) { - //std::cout << "applying op "; switch(op.type){ case Operations::OpType::gate: - //std::cout << "gate" << std::endl; - //std::cout << op << std::endl; this->apply_gate(op); break; case Operations::OpType::save_specific_prob: - //std::cout << "save" << std::endl; this->apply_save_specific_prob(op, result); break; default: - throw std::invalid_argument("Compute::State::invalid instruction \'" + op.name + "\'."); + throw std::invalid_argument("Compute::State::invalid instruction \'" + op.name + "\'."); } } void State::apply_gate(const Operations::Op &op){ - //std::cout << "apply_gate" << std::endl; auto it = gateset_.find(op.name); if (it == gateset_.end()) { @@ -138,55 +132,42 @@ void State::apply_gate(const Operations::Op &op){ } switch(it->second){ case Gates::id: - //std::cout << "id" << std::endl; break; case Gates::x: - //std::cout << "x" << std::endl; this->qreg_.applyX(op.qubits[0]); break; case Gates::y: - //std::cout << "y" << std::endl; this->qreg_.applyY(op.qubits[0]); break; case Gates::z: - //std::cout << "z" << std::endl; this->qreg_.applyZ(op.qubits[0]); break; case Gates::s: - //std::cout << "s" << std::endl; this->qreg_.applyS(op.qubits[0]); break; case Gates::sdg: - //std::cout << "sdg" << std::endl; this->qreg_.applyZ(op.qubits[0]); this->qreg_.applyS(op.qubits[0]); break; case Gates::h: - //std::cout << "h" << std::endl; this->qreg_.applyH(op.qubits[0]); break; case Gates::t: - //std::cout << "t" << std::endl; this->qreg_.gadgetized_phase_gate(op.qubits[0], T_ANGLE); break; case Gates::tdg: - //std::cout << "tdg" << std::endl; this->qreg_.gadgetized_phase_gate(op.qubits[0], -T_ANGLE); break; case Gates::rz: - //std::cout << "rz" << std::endl; this->qreg_.gadgetized_phase_gate(op.qubits[0], op.params[0].real()); break; case Gates::cx: - //std::cout << "cx[" << op.qubits[0] << ", " << op.qubits[1] << "]" <qreg_.applyCX(op.qubits[0],op.qubits[1]); break; case Gates::cz: - //std::cout << "cz" << std::endl; this->qreg_.applyCZ(op.qubits[0],op.qubits[1]); break; case Gates::swap: - //std::cout << "swap" << std::endl; this->qreg_.applySwap(op.qubits[0],op.qubits[1]); break; default: //u0 or Identity @@ -207,7 +188,6 @@ uint_t BinaryToGray(uint_t num) double State::compute_algorithm_all_phases_T(AGState &state){ - //std::cout << "State::compute_algorithm_all_phases_T" << std::endl; if(state.num_stabilizers > 63){ //we use an int_t == int_fast64_t variable to store our loop counter, this means we can deal with at most 63 stabilizers //in the case of 63 stabilizers we will iterate over all length 63 bitstrings @@ -215,58 +195,48 @@ double State::compute_algorithm_all_phases_T(AGState &state){ //the integer has to be signed due to openMP's requirements //realistically a computation with 63 stabilizers = 2^63 - 1 iterations is not going to terminate anyway so this restriction shouldn't matter std::stringstream msg; - msg << "CliffPhaseCompute::State::compute_algorithm_all_phases_T called with " << state.num_stabilizers << " stabilizers. Maximum possible is 63."; + msg << "CliffPhaseCompute::State::compute_algorithm_all_phases_T called with " << state.num_stabilizers << " stabilizers. Maximum possible is 63."; throw std::runtime_error(msg.str()); } - - uint_t full_mask = 0; - for(size_t i = 0; i < state.num_stabilizers; i++){ - full_mask |= (ONE << i); + + if(state.num_stabilizers == 0){ + return 1; } - double acc = 0.; + uint_t num_iterations = (ONE << state.num_stabilizers); + + double acc = 0.; Pauli::Pauli row(state.num_qubits); unsigned char phase = 0; - - bool row_initialised = false; + uint_t num_threads_ = BaseState::threads_; + if(num_threads_ > num_iterations){ + num_threads_ = num_iterations; + } - int_t chunk_size = 0; - #pragma omp parallel for num_threads(num_threads_) - for (int_t thread = 0; thread < num_threads_; ++thread) { - // identify a chunk from i and num_threads + uint_t chunk_size = num_iterations / num_threads_; + if(num_iterations % num_threads_ > 0){ + chunk_size += 1; } - - #pragma omp parallel for if(num_threads_ > 1) \ - num_threads(num_threads_) firstprivate(row_initialised, row, phase) shared(state) reduction(+:acc) - for(uint_t mask = 0; mask <= full_mask; mask++){ - if(row_initialised){ - uint_t mask_with_bit_to_flip = BinaryToGray((uint_t)mask) ^ BinaryToGray((uint_t)(mask - 1)); - size_t bit_to_flip = 0; - for(size_t j = 0; j < state.num_stabilizers; j++){ - if((mask_with_bit_to_flip >> j) & ONE){ - bit_to_flip = j; - break; - } +#pragma omp parallel for if(num_threads_ > 1) num_threads(num_threads_) \ + firstprivate(row, phase) shared(state) reduction(+:acc) + for(int_t thread = 0; thread < num_threads_; ++thread) { + uint_t start_value = thread * chunk_size; + uint_t end_value = (thread + 1) * chunk_size; + int_t mask_with_bits_to_flip = BinaryToGray(start_value); // note BinaryToGray(0u) == 0u + //set up the row and phase variables + for(int_t bit = 0; bit < state.num_stabilizers; bit++){ + if((mask_with_bits_to_flip >> bit) & ONE){ + phase += state.phases[bit] + (Pauli::Pauli::phase_exponent(row, state.table[bit]) / 2); + phase %= 2; + row += state.table[bit]; } - phase += state.phases[bit_to_flip] + (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); //phases for stabilizers are always 1 or -1 - phase %= 2; - row += state.table[bit_to_flip]; - }else{ - int_t mask_with_bit_to_flip = BinaryToGray(mask); // note BinaryToGray(0u) == 0u - for(size_t j = 0; j < state.num_stabilizers; j++){ - if((mask_with_bit_to_flip >> j) & ONE){ - phase += state.phases[j] + (Pauli::Pauli::phase_exponent(row, state.table[j]) / 2); //phases for stabilizers are always 1 or -1 - phase %= 2; - row += state.table[j]; - } - } - row_initialised = true; } + size_t XCount = 0; size_t YCount = 0; size_t ZCount = 0; - + for(size_t j = 0; j < state.num_qubits; j++){ if(row.X[j] && !row.Z[j]){ XCount += 1; @@ -279,24 +249,60 @@ double State::compute_algorithm_all_phases_T(AGState &state){ YCount += 1; } } - + if(ZCount == 0){ if(((phase + YCount) % 2) == 0){ acc += powl(1./2., (XCount + YCount)/2.);; }else{ acc -= powl(1./2., (XCount + YCount)/2.);; } - } - } + } + - if(full_mask == 0u){ - return 1.; + for(uint_t mask = start_value+1; mask < end_value && mask < num_iterations; ++mask){ + uint_t mask_with_bit_to_flip = BinaryToGray((uint_t)mask) ^ BinaryToGray((uint_t)(mask - 1)); + uint_t bit_to_flip = 0; + for(size_t bit = 0; bit < state.num_stabilizers; bit++){ + if((mask_with_bit_to_flip >> bit) & ONE){ + bit_to_flip = bit; + break; + } + } + phase += state.phases[bit_to_flip] + (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); + phase %= 2; + row += state.table[bit_to_flip]; + + size_t XCount = 0; + size_t YCount = 0; + size_t ZCount = 0; + + for(size_t j = 0; j < state.num_qubits; j++){ + if(row.X[j] && !row.Z[j]){ + XCount += 1; + } + if(!row.X[j] && row.Z[j]){ + ZCount += 1; + break; + } + if(row.X[j] && row.Z[j]){ + YCount += 1; + } + } + + if(ZCount == 0){ + if(((phase + YCount) % 2) == 0){ + acc += powl(1./2., (XCount + YCount)/2.);; + }else{ + acc -= powl(1./2., (XCount + YCount)/2.);; + } + } + } } + return acc; } double State::compute_algorithm_arbitrary_phases(AGState &state){ - if(state.num_stabilizers > 63){ //we use an int_t == int_fast64_t variable to store our loop counter, this means we can deal with at most 63 stabilizers //in the case of 63 stabilizers we will iterate over all length 63 bitstrings @@ -304,69 +310,123 @@ double State::compute_algorithm_arbitrary_phases(AGState &state){ //the integer has to be signed due to openMP's requirements //realistically a computation with 63 stabilizers = 2^63 - 1 iterations is not going to terminate anyway so this restriction shouldn't matter std::stringstream msg; - msg << "CliffPhaseCompute::State::compute_algorithm_arbitrary_phases called with " << state.num_stabilizers << " stabilizers. Maximum possible is 63."; + msg << "CliffPhaseCompute::State::compute_algorithm_all_phases_T called with " << state.num_stabilizers << " stabilizers. Maximum possible is 63."; throw std::runtime_error(msg.str()); } - - //std::cout << "1" << std::endl; - uint_t full_mask = 0u; - for(size_t i = 0; i < state.num_stabilizers; i++){ - full_mask |= (ONE << i); + if(state.num_stabilizers == 0){ + return 1; } - double acc = 1.; - Pauli::Pauli row(state.num_qubits); - unsigned char phase = 0; - //std::cout << "2" << std::endl; - for(uint_t mask = 1u; mask <= full_mask; mask++){ - uint_t mask_with_bit_to_flip = BinaryToGray((uint_t)mask) ^ BinaryToGray((uint_t)(mask - 1)); - size_t bit_to_flip = 0; - for(size_t j = 0; j < state.num_stabilizers; j++){ - if((mask_with_bit_to_flip >> j) & ONE){ - bit_to_flip = j; - break; + + + std::vector x_values; + std::vector y_values; + + for(double phase : state.magic_phases){ + x_values.push_back(cos(phase)); + y_values.push_back(-sin(phase)); + } + + uint_t num_iterations = (ONE << state.num_stabilizers); + + double acc = 0.; + + uint_t num_threads_ = BaseState::threads_; + if(num_threads_ > num_iterations){ + num_threads_ = num_iterations; + } + uint_t chunk_size = num_iterations / num_threads_; + if((num_iterations % num_threads_) > 0){ + chunk_size += 1; + } + //std::cout << state.num_stabilizers << ", " << num_iterations << ", " << chunk_size << ", " << num_threads_ << std::endl; + +#pragma omp parallel for if(num_threads_ > 1) num_threads(num_threads_) shared(state) reduction(+:acc) + for(int_t thread = 0; thread < num_threads_; ++thread) { + uint_t start_value = thread * chunk_size; + uint_t end_value = (thread + 1) * chunk_size; + + //we do the first iteration of each thread separately because it has to initialise the row and phase variables + Pauli::Pauli row(state.num_qubits); + unsigned char phase = 0; + //if(start_value >= num_iterations){ + // std::cout << "wtf " << thread << " " << start_value << " " << chunk_size << std::endl; + // + uint_t mask_with_bits_to_flip = BinaryToGray(start_value); // note BinaryToGray(0u) == 0u + for(int_t bit = 0; bit < state.num_stabilizers; bit++){ + if((mask_with_bits_to_flip >> bit) & ONE){ + phase += state.phases[bit] + (Pauli::Pauli::phase_exponent(row, state.table[bit]) / 2); + phase %= 2; + row += state.table[bit]; } } - - phase += Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2; //phases for stabilizers are always 0 or 2 - phase += state.phases[bit_to_flip]; - phase %= 2; - row += state.table[bit_to_flip]; - - double prod = (phase==1) ? -1. : 1.; + double prod = (phase == 0) ? 1. : -1.; + int ZCount = 0; for(size_t j = 0; j < state.num_qubits; j++){ if(row.X[j] && !row.Z[j]){ - prod *= cos(state.magic_phases[j]); + prod *= x_values[j]; } if(!row.X[j] && row.Z[j]){ - prod = 0.; + ZCount += 1; break; } if(row.X[j] && row.Z[j]){ - prod *= (-sin(state.magic_phases[j])); + prod *= y_values[j]; + } + } + + if(ZCount == 0){ + acc += prod; + } + + for(uint_t mask = start_value+1; (mask < end_value) && (mask < num_iterations); ++mask){ + uint_t mask_with_bit_to_flip = BinaryToGray(mask) ^ BinaryToGray(mask - 1); + uint_t bit_to_flip = 0; + for(size_t bit = 0; bit < state.num_stabilizers; bit++){ + if((mask_with_bit_to_flip >> bit) & ONE){ + bit_to_flip = bit; + break; + } + } + phase += state.phases[bit_to_flip] + (Pauli::Pauli::phase_exponent(row, state.table[bit_to_flip]) / 2); + phase %= 2; + row += state.table[bit_to_flip]; + + double prod = (phase == 0) ? 1. : -1.; + int ZCount = 0; + for(size_t j = 0; j < state.num_qubits; j++){ + if(row.X[j] && !row.Z[j]){ + prod *= x_values[j]; + } + if(!row.X[j] && row.Z[j]){ + ZCount += 1; + break; + } + if(row.X[j] && row.Z[j]){ + prod *= y_values[j]; + } + } + + if(ZCount == 0){ + acc += prod; } } - acc += prod; - } - //std::cout << "3" << std::endl; - if(full_mask == 0u){ - acc=1; } + return acc; } double State::compute_probability(std::vector measured_qubits, std::vector outcomes){ - //std::cout << "State::compute_probability" << std::endl; AGState copied_ag(this->qreg_); //copy constructor TODO check this //first reorder things so the first w qubits are measured std::vector measured_qubits_sorted(measured_qubits); - + std::vector qubit_indexes; for(uint_t i = 0; i < this->qreg_.num_qubits; i++){ qubit_indexes.push_back(i); - } + } std::sort(measured_qubits_sorted.begin(), measured_qubits_sorted.end()); for(uint_t i = 0; i < measured_qubits.size(); i++){ uint_t w = measured_qubits_sorted[i]; @@ -384,24 +444,20 @@ double State::compute_probability(std::vector measured_qubits, std::vect break; } } - + if(idx1 != idx2){ - //std::cout << "swapping " << idx1 << " and " << idx2 << std::endl; - //std::swap(qubit_indexes[idx1], qubit_indexes[idx2]); copied_ag.applySwap(idx1, idx2); } } //now all the measured qubits are at the start and the magic qubits are at the end //from this point on we will assume we're looking for the measurement outcome 0 on all measured qubits - //so apply X gates to measured qubits where we're looking for outcome 1 to correct this + //so apply X gates to measured qubits where we're looking for outcome 1 to correct this for(uint_t i = 0; i < outcomes.size(); i++){ - //std::cout << "outcomes[" << i << "] = " << outcomes[i] << " "; - if(outcomes[i] == 1){ + if(outcomes[i] == 1){ copied_ag.applyX(i); } } - //std::cout << std::endl; uint_t w = measured_qubits.size(); uint_t t = copied_ag.magic_phases.size(); @@ -419,7 +475,7 @@ double State::compute_probability(std::vector measured_qubits, std::vect for(uint_t q = 0; q < t; q++){ copied_ag.applySwap(q, q+(copied_ag.num_qubits - t)); } - + for(uint_t s = 0; s < copied_ag.num_stabilizers; s++){ copied_ag.table[s].X.resize(t); copied_ag.table[s].Z.resize(t); @@ -430,7 +486,6 @@ double State::compute_probability(std::vector measured_qubits, std::vect copied_ag.delete_identity_magic_qubits(); //we can make the compute algorithm much faster if all of our non-Clifford gates are in fact T gates (pi/4 rotations) - bool all_phases_are_T = true; for(uint_t i = 0; i < copied_ag.num_qubits; i++){ if(fabs(copied_ag.magic_phases[i] - T_ANGLE) > AG_CHOP_THRESHOLD){ @@ -438,19 +493,16 @@ double State::compute_probability(std::vector measured_qubits, std::vect break; } } - //std::cout << copied_ag.magic_phases.size() << std::endl; - //std::cout << copied_ag.magic_phases[0] << ", " << cos(copied_ag.magic_phases[0]) << ", " << -sin(copied_ag.magic_phases[0]) << std::endl; + if(copied_ag.num_qubits == 0){ return pow(2., ((double)v) - ((double)w)); } - //std::cout << "a" << std::endl; + if(all_phases_are_T){ - //std::cout << "a" << std::endl; - return compute_algorithm_all_phases_T(copied_ag) * pow(2., ((double)v) - ((double)w)); + return compute_algorithm_all_phases_T(copied_ag) * powl(2., ((double)v) - ((double)w)); } else { - //std::cout << "b" << std::endl; - return compute_algorithm_arbitrary_phases(copied_ag) * pow(2., ((double)v) - ((double)w)); - } + return compute_algorithm_arbitrary_phases(copied_ag) * powl(2., ((double)v) - ((double)w)); + } } void State::initialize_qreg(uint_t num_qubits) { @@ -461,7 +513,7 @@ void State::initialize_qreg(uint_t num_qubits, const agstate_t &state) { if(BaseState::qreg_.num_qubits != num_qubits){ throw std::invalid_argument("CH::State::initialize: initial state does not match qubit number."); } - BaseState::qreg_ = state; + BaseState::qreg_ = state; } template @@ -473,8 +525,8 @@ uint_t State::count_magic_gates(InputIterator first, InputIterator last) const { if (it != gateset_.end()) { if(it->second == Gates::t || it->second == Gates::rz || it->second == Gates::tdg){ - count += 1; - } + count += 1; + } } } return count; @@ -491,11 +543,11 @@ size_t State::required_memory_mb(uint_t num_qubits, const std::vector