intel-qs/include/NoisyQureg.hpp

506 lines
19 KiB
C++

#ifndef NOISY_QUREG_HPP
#define NOISY_QUREG_HPP
#include <random>
#include <vector>
/// \addtogroup NoisyQureg
/// @{
/// @file NoisyQureg.h
/// @brief Define the class @c NoisyQureg that automatically includes noise gates.
using namespace std;
/// @brief Class that expand @c QubitRegister states by adding noise between "logical" gates.
///
/// @param num_qubit is the number of qubits
/// When we refer to ``experimental'' gates, it means that noise gates are excluded.
/// For the simulation to be faithful (i.e. with one-to-one correspondence with the experimental gates),
/// we include among the experimental gates both the gates for the algorithm and those to schedule it
/// according to the connectivity of the specific hardware.
/////////////////////////////////////////////////////////////////////////////////////////
template <class Type = ComplexDP>
class NoisyQureg : public QubitRegister<Type>
{
private :
typedef typename QubitRegister<Type>::BaseType BaseType;
std::vector<BaseType> time_from_last_gate; // given in terms of the chosen time unit
BaseType time_one_qubit_experimental_gate = 1.;
BaseType time_two_qubit_experimental_gate = 1.;
// matrix with number of gates between qubits (diagonal is one-qubit gates)
std::vector<unsigned> experimental_gate_count_matrix;
unsigned one_qubit_experimental_gate_count=0;
unsigned two_qubit_experimental_gate_count=0;
// ~~~~~~~~~~ parameters characterizing the noise model ~~~~~~~~~
BaseType T_1 = 1000; // T_1 given in terms of the chosen time unit
BaseType T_2 = 100; // T_2 given in terms of the chosen time unit
BaseType T_phi = 1./( 1./T_2 - 1./(2.*T_1) ); // T_phi given in terms of the chosen time unit
// Pseudo random-number-generator to sample from normal distribution
// (for the rotation angles of the noise gates)
std::default_random_engine generator;
std::normal_distribution<BaseType> gaussian_RNG{0.,1.} ;
public :
/// Constructor
NoisyQureg<Type>(unsigned num_qubits , unsigned RNG_seed=12345 ,
BaseType T1=2000 , BaseType T2=1000 ) :
QubitRegister<Type>(num_qubits)
{
T_1 = T1;
T_2 = T2;
time_from_last_gate.assign(num_qubits,0.);
experimental_gate_count_matrix.assign(num_qubits*num_qubits,0);
// Initialization of the seed for the generation of the noise gate parameters
generator.seed( RNG_seed );
// srand48( RNG_seed );
}
/// Default destructor
~NoisyQureg<Type>() {};
// Initialization of the state (without noise)
void Initialize(std::string style, std::size_t base_index);
// Utilities
void ResetTimeForAllQubits();
void ApplyNoiseGatesOnAllQubits();
// Noise model
void SetDecoherenceTime(BaseType, BaseType);
void SetGateDurations(BaseType, BaseType);
// Get stats
unsigned GetTotalExperimentalGateCount();
unsigned GetOneQubitExperimentalGateCount();
unsigned GetTwoQubitExperimentalGateCount();
std::vector<unsigned> GetExperimentalGateCount(unsigned q1);
unsigned GetExperimentalGateCount(unsigned q1, unsigned q2);
// Reset decoherence time and implement noise gate
void AddNoiseOneQubitGate(unsigned const);
void AddNoiseTwoQubitGate(unsigned const, unsigned const);
void NoiseGate(unsigned const);
// Old funcation that can be deleted and should NOT be used:
void NoiseGate_OLD(unsigned const);
// Perform gates
void Apply1QubitGate(unsigned const, qhipster::TinyMatrix<Type, 2, 2, 32>);
void ApplyHadamard(unsigned const);
void ApplyRotationX(unsigned const, BaseType);
void ApplyRotationY(unsigned const, BaseType);
void ApplyRotationZ(unsigned const, BaseType);
void ApplyCPauliX(unsigned const, unsigned const);
void ApplyControlled1QubitGate(unsigned const, unsigned const, qhipster::TinyMatrix<Type, 2, 2, 32>);
};
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////
// ------------ initialize state ---------------------------------------------------------
template <class Type>
void NoisyQureg<Type>::Initialize(std::string style, std::size_t base_index)
{
one_qubit_experimental_gate_count = 0;
two_qubit_experimental_gate_count = 0;
ResetTimeForAllQubits();
QubitRegister<Type>::Initialize(style,base_index);
}
// ------------ utils --------------------------------------------------------------------
/// Reset to zero the time elapsed for each and every qubit in the register.
template <class Type>
void NoisyQureg<Type>::ResetTimeForAllQubits()
{
unsigned num_qubits = this->num_qubits;
// increase the idle time for all the qubits (TODO no gate parallelism is assumed)
for (unsigned q = 0; q<num_qubits; q++)
time_from_last_gate[q] = 0. ;
}
/// @brief Apply the noise gates on each and every qubit. Then reset to time counter.
///
/// This is useful, for example, at the end of a circuit before measuring the
/// quantities of interest: One has to apply the noise corresponding to the idle
/// evolution between the last logical gate and the final time.
/// The time from last logical gate is then resetted to zero for every qubit.
template <class Type>
void NoisyQureg<Type>::ApplyNoiseGatesOnAllQubits()
{
unsigned num_qubits = this->num_qubits;
// increase the idle time for all the qubits (TODO no gate parallelism is assumed)
for (unsigned q = 0; q<num_qubits; q++)
NoiseGate(q);
ResetTimeForAllQubits();
}
/// Set the decoherence time in terms of T_1 and T_2 values (in accordance to the new noise model).
template <class Type>
void NoisyQureg<Type>::SetDecoherenceTime(BaseType T1, BaseType T2)
{
T_1 = T1;
T_2 = T2;
}
/// Update the duration of single- and two- qubit gates.
template <class Type>
void NoisyQureg<Type>::SetGateDurations(BaseType Ts, BaseType Td)
{
time_one_qubit_experimental_gate = Ts;
time_two_qubit_experimental_gate = Td;
}
// ------------ count the experimental gates ---------------------------------------------
/// Return the current number of (experimental) single-qubit gates.
template <class Type>
unsigned NoisyQureg<Type>::GetOneQubitExperimentalGateCount()
{ return one_qubit_experimental_gate_count; }
/// Return the current number of (experimental) two-qubit gates.
template <class Type>
unsigned NoisyQureg<Type>::GetTwoQubitExperimentalGateCount()
{ return two_qubit_experimental_gate_count; }
/// Return the current number of (experimental) 1- and 2-qubit gates.
template <class Type>
unsigned NoisyQureg<Type>::GetTotalExperimentalGateCount()
{ return one_qubit_experimental_gate_count + two_qubit_experimental_gate_count; }
/// Return the number of (experimental) gates involving qubit q.
template <class Type>
std::vector<unsigned> NoisyQureg<Type>::GetExperimentalGateCount(unsigned q)
{
std::vector<unsigned> SingleRowOfMatrix (experimental_gate_count_matrix.begin()+ q * this->num_qubits,
experimental_gate_count_matrix.begin()+ (q+1) * this->num_qubits);
return SingleRowOfMatrix;
}
/// Return the number of (experimental) gates involving qubits q1,q2.
template <class Type>
unsigned NoisyQureg<Type>::GetExperimentalGateCount(unsigned q1, unsigned q2)
{
return experimental_gate_count_matrix[ q1 * this->num_qubits + q2 ];
}
// ------------ execute the noise gates --------------------------------------------------
/// Include and execute the noise gate corresponding to the idle time of a single qubit.
template <class Type>
void NoisyQureg<Type>::AddNoiseOneQubitGate(unsigned const qubit)
{
unsigned num_qubits = this->num_qubits;
// Implement the noise gate
NoiseGate(qubit);
// Increase the idle time for all the qubits (TODO no gate parallelism is assumed)
for (unsigned q = 0; q<num_qubits; q++)
time_from_last_gate[q] += time_one_qubit_experimental_gate ;
// Reset the time elapsed from last logical gate on the specific qubit
time_from_last_gate[qubit]=0.;
// Update counter for (logical) one-qubit gates
one_qubit_experimental_gate_count++;
experimental_gate_count_matrix[qubit*num_qubits+qubit]++;
}
/// Include and execute the noise gate corresponding to the idle time of two qubits.
template <class Type>
void NoisyQureg<Type>::AddNoiseTwoQubitGate(unsigned const q1, unsigned const q2)
{
unsigned num_qubits = this->num_qubits;
// Implement the noise gate
NoiseGate(q1);
NoiseGate(q2);
// Increase the idle time for all the qubits (TODO no gate parallelism is assumed)
for (unsigned q = 0; q<num_qubits; q++)
time_from_last_gate[q] += time_two_qubit_experimental_gate ;
// Reset the time elapsed from last logical gate on the specific qubits
time_from_last_gate[q1]=0.;
time_from_last_gate[q2]=0.;
// Update counter for (logical) two-qubit gates
two_qubit_experimental_gate_count++;
experimental_gate_count_matrix[q1*num_qubits+q2]++;
experimental_gate_count_matrix[q2*num_qubits+q1]++;
}
/// @brief Noise gate corresponding to single-qubit rotation with appropriate (stochastic) angle.
///
/// Each noise gate is the product of three rotations around X,Y,Z axis (by a small angle each).
/// We compute their product before applying it to the quantum register.
template <class Type>
void NoisyQureg<Type>::NoiseGate(unsigned const qubit )
{
BaseType t = time_from_last_gate[qubit] ;
if (t==0) return;
BaseType p_X , p_Y , p_Z ;
p_X = (1. - std::exp(-t/T_1) )/4.;
p_Y = (1. - std::exp(-t/T_1) )/4.;
p_Z = (1. - std::exp(-t/T_2) )/2. + (1. - std::exp(-t/T_1) )/4.;
assert( p_X>0 && p_Y>0 && p_Z>0 );
// Computation of the standard deviations for the noise gate parameters
BaseType s_X , s_Y , s_Z ;
s_X = std::sqrt( -std::log(1.-p_X) );
s_Y = std::sqrt( -std::log(1.-p_Y) );
s_Z = std::sqrt( -std::log(1.-p_Z) );
// Generate angle and direction of Pauli rotation for Pauli-twirld noise channel
BaseType v_X , v_Y , v_Z;
v_X = gaussian_RNG(generator) * s_X /2.;
v_Y = gaussian_RNG(generator) * s_Y /2.;
v_Z = gaussian_RNG(generator) * s_Z /2.;
// Direct construction of the 2x2 matrix corresponding to the noise gate
// U_noise = exp(-i v_X X) * exp(-i v_Y Y) * exp(-i v_Z Z)
// Helpful quantities:
// (A) = cos v_z -i sin v_z
// (B) = cos v_x * cos v_Y -i sin v_X * sin v_Y
// (C) = cos v_x * sin v_Y -i sin v_X * cos v_Y
// Then :
// | A*B -A'*C' |
// U_noise = | |
// | A*C A'*B' |
Type A , B , C ;
A = { std::cos(v_Z) , -std::sin(v_Z) };
B = { std::cos(v_X)*std::cos(v_Y) , -std::sin(v_X)*std::sin(v_Y) };
C = { std::cos(v_X)*std::sin(v_Y) , -std::sin(v_X)*std::cos(v_Y) };
qhipster::TinyMatrix<Type, 2, 2, 32> U_noise;
U_noise(0, 0) = A*B;
U_noise(0, 1) = -std::conj(A)*std::conj(C);
U_noise(1, 0) = A*C;
U_noise(1, 1) = std::conj(A)*std::conj(B);
// Apply the noise gate
QubitRegister<Type>::Apply1QubitGate(qubit,U_noise);
}
/// @brief Noise gate corresponding to single-qubit rotation with appropriate (stochastic) angle.
///
/// ** OLD OLD OLD OLD OLD OLD **
///
/// Kept for historical reasons, it shouldy be deletead.
///
/// To obtain a single rotation around an arbitrary axis we use the relations:
/// | a b c | | h-f |
/// R = | d e f | --> u = | c-g | ---> abs(u) = 2 sin( 'angle' )
/// | g h i | | d-b | ---> u/abs(u) = rotation axis
template <class Type>
void NoisyQureg<Type>::NoiseGate_OLD(unsigned const qubit )
{
BaseType t = time_from_last_gate[qubit] ;
if (t==0) return;
BaseType p_X , p_Y , p_Z ;
p_X = (1. - std::exp(-t/T_1) )/4.;
p_Y = (1. - std::exp(-t/T_1) )/4.;
p_Z = (1. - std::exp(-t/T_2) )/2. + (1. - std::exp(-t/T_1) )/4.;
assert( p_X>0 && p_Y>0 && p_Z>0 );
// Computation of the standard deviations for the noise gate parameters
BaseType s_X , s_Y , s_Z ;
s_X = std::sqrt( -std::log(1.-p_X) );
s_Y = std::sqrt( -std::log(1.-p_Y) );
s_Z = std::sqrt( -std::log(1.-p_Z) );
// Generate angle and direction of Pauli rotation for Pauli-twirld noise channel
BaseType v_X , v_Y , v_Z;
v_X = gaussian_RNG(generator) * s_X /2.;
v_Y = gaussian_RNG(generator) * s_Y /2.;
v_Z = gaussian_RNG(generator) * s_Z /2.;
// Compose the 3-dimensional rotation: R_X(v_X).R_Y(v_Y).R_Z(v_Z)
// | cos Y cos Z -cos Y sin Z sin Y |
// R = | sin X sin Y cos Z + cos X sin Z -sin X sin Y sin Z + cos X cos Z -sin X cos Y |
// | -cos X sin Y cos Z + sin X sin Z cos X sin Y sin Z + sin X cos Z cos X cos Y |
//
// | cos X sin Y sin Z + sin X cos Z + sin X cos Y |
// u = | sin Y + cos X sin Y cos Z - sin X sin Z |
// | sin X sin Y cos Z + cos X sin Z + cos Y sin Z |
std::vector<BaseType> R = { std::cos(v_Y)*std::cos(v_Z) ,
-std::cos(v_Y)*std::sin(v_Z) ,
std::sin(v_Y) ,
std::sin(v_X)*std::sin(v_Y)*std::cos(v_Z) + std::cos(v_X)*std::sin(v_Z) ,
-std::sin(v_X)*std::sin(v_Y)*std::sin(v_Z) + std::cos(v_X)*std::cos(v_Z) ,
-std::sin(v_X) * std::cos(v_Y),
-std::cos(v_X)*std::sin(v_Y)*std::cos(v_Z) + std::sin(v_X)*std::sin(v_Z),
std::cos(v_X)*std::sin(v_Y)*std::sin(v_Z) + std::sin(v_X)*std::cos(v_Z),
std::cos(v_X)*std::cos(v_Y) };
std::vector<BaseType> u = { R[3*2+1] - R[3*1+2] ,
R[3*0+2] - R[3*2+0] ,
R[3*1+0] - R[3*0+1] } ;
BaseType norm_u = std::sqrt( std::norm(u[0]) + std::norm(u[1]) + std::norm(u[2]) );
std::vector<BaseType> axis = { u[0]/norm_u , u[1]/norm_u , u[2]/norm_u };
// Compute the angle of rotation
BaseType trace_R = R[0] + R[4] + R[8] ;
BaseType angle = std::acos( (trace_R-1.)/2. );
// Alternative expression for the angle, from Wikipedia:
BaseType angle_alternative = std::asin( norm_u/2. ) ;
// if 0<angle<Pi/2 then angle = angle_alternative
// if Pi/2<angle<Pi then angle = Pi - angle_alternative
// It may happen that "axis" still requires a "minus sign"
if (false)
{
std::vector<double> R_u , R_minus_u ;
BaseType si(std::sin(angle)) , co(std::cos(angle)) ;
R_u = { co + axis[0]*axis[0]*(1.-co) ,
axis[0]*axis[1]*(1.-co) - axis[2]*si ,
axis[0]*axis[2]*(1.-co) + axis[1]*si ,
axis[0]*axis[1]*(1.-co) + axis[2]*si ,
co + axis[1]*axis[1]*(1.-co) ,
axis[1]*axis[2]*(1.-co) - axis[0]*si ,
axis[0]*axis[2]*(1.-co) - axis[1]*si ,
axis[1]*axis[2]*(1.-co) + axis[0]*si ,
co + axis[2]*axis[2]*(1.-co) };
R_minus_u = { co + axis[0]*axis[0]*(1.-co) ,
axis[0]*axis[1]*(1.-co) + axis[2]*si ,
axis[0]*axis[2]*(1.-co) - axis[1]*si ,
axis[0]*axis[1]*(1.-co) - axis[2]*si ,
co + axis[1]*axis[1]*(1.-co) ,
axis[1]*axis[2]*(1.-co) + axis[0]*si ,
axis[0]*axis[2]*(1.-co) + axis[1]*si ,
axis[1]*axis[2]*(1.-co) - axis[0]*si ,
co + axis[2]*axis[2]*(1.-co) };
// print_vector(R,"rotation matrix:\n");
// print_vector(R_u,"reconstructed with u:\n");
// print_vector(R_minus_u,"reconstructed with -u:\n");
// u was defined up to a sign. To resolve the ambiguity:
BaseType R_element , Ru_element;
if (axis[0] != 0.)
{
R_element = std::cos(v_X) * std::sin(v_Y) * std::sin(v_Z) + std::sin(v_X) * std::cos(v_Z) ;
Ru_element = axis[2]*axis[1]*(1-std::cos(angle)) + axis[0]*std::sin(angle) ;
if ( std::abs(R_element - Ru_element)> 1e-7 ) std::cout << " wrong sign from axis[0]\n";
// else std::cout << " correct sign from axis[0]\n";
}
if (axis[1] != 0.)
{
R_element = std::sin(v_Y) ;
Ru_element = axis[2]*axis[0]*(1-std::cos(angle)) + axis[1]*std::sin(angle) ;
if ( std::abs(R_element - Ru_element)> 1e-7 ) std::cout << " wrong sign from axis[1]\n";
// else std::cout << " correct sign from axis[1]\n";
}
if (axis[2] != 0.)
{
R_element = std::sin(v_X) * std::sin(v_Y) * std::cos(v_Z) + std::cos(v_X) * std::sin(v_Z) ;
Ru_element = axis[0]*axis[1]*(1-std::cos(angle)) + axis[2]*std::sin(angle) ;
if ( std::abs(R_element - Ru_element)> 1e-7 ) std::cout << " wrong sign from axis[2]\n";
// else std::cout << " correct sign from axis[2]\n";
}
}
// Compute the angle of rotation
trace_R = std::cos(v_Y) * std::cos(v_Z)
- std::sin(v_X) * std::sin(v_Y) * std::sin(v_Z)
+ std::cos(v_X) * std::cos(v_Z)
+ std::cos(v_X) * std::cos(v_Y) ;
angle = std::acos( (trace_R-1.)/2. );
if (false) std::cout << " angle of rotation = " << angle << "\n";
// Alternative expression for the angle, from Wikipedia.
if (false) std::cout << " angle of rotation (alternative formula) = " << std::asin( norm_u/2. ) << "\n";
// Costruct the 1/2-spin matrix corresponding to the axis above
// sigma_axis
// and use it to implement the single-qubit rotation :
// rot = exp( i * angle * sigma_axis )
qhipster::TinyMatrix<Type, 2, 2, 32> rot;
BaseType s(std::sin(angle/2.)) , c(std::cos(angle/2.)) ;
rot(0, 0) = Type( c , s*axis[2] );
rot(0, 1) = Type( s*axis[1] , s*axis[0] );
rot(1, 0) = Type( -s*axis[1] , s*axis[0] );
rot(1, 1) = Type( c , -s*axis[2] );
// Apply the noise gate
QubitRegister<Type>::Apply1QubitGate(qubit,rot);
}
// ----------------------------------------------------------
// -------- list of modified gates --------------------------
// ----------------------------------------------------------
template <class Type>
void NoisyQureg<Type>::Apply1QubitGate(unsigned const q,
qhipster::TinyMatrix<Type, 2, 2, 32> V)
{
AddNoiseOneQubitGate(q);
QubitRegister<Type>::Apply1QubitGate(q,V);
}
template <class Type>
void NoisyQureg<Type>::ApplyHadamard(unsigned const q)
{
AddNoiseOneQubitGate(q);
QubitRegister<Type>::ApplyHadamard(q);
}
template <class Type>
void NoisyQureg<Type>::ApplyRotationX(unsigned const q, BaseType theta)
{
AddNoiseOneQubitGate(q);
QubitRegister<Type>::ApplyRotationX(q,theta);
}
template <class Type>
void NoisyQureg<Type>::ApplyRotationY(unsigned const q, BaseType theta)
{
AddNoiseOneQubitGate(q);
QubitRegister<Type>::ApplyRotationY(q,theta);
}
template <class Type>
void NoisyQureg<Type>::ApplyRotationZ(unsigned const q, BaseType theta)
{
AddNoiseOneQubitGate(q);
QubitRegister<Type>::ApplyRotationZ(q,theta);
}
template <class Type>
void NoisyQureg<Type>::ApplyCPauliX(unsigned const q1, unsigned const q2)
{
AddNoiseTwoQubitGate(q1,q2);
QubitRegister<Type>::ApplyCPauliX(q1,q2);
}
template <class Type>
void NoisyQureg<Type>::ApplyControlled1QubitGate(unsigned const q1, unsigned const q2,
qhipster::TinyMatrix<Type, 2, 2, 32> V)
{
AddNoiseTwoQubitGate(q1,q2);
QubitRegister<Type>::ApplyControlled1QubitGate(q1,q2,V);
}
/// @}
#endif // header guard NOISY_QUREG_HPP