def test_equivalence_with_manual_dm_then_trace(self): with tf.device('cpu'): states = random_pure_states((1000, 2**8, 1)) t_start1 = time.time() dms1 = trace(density_matrix(states), [4, 5, 6, 7]) # Trace 2 and 3 t_start2 = time.time() dms2 = density_matrix(states, [0, 1, 2, 3]) # Get the dm of 0 and 1 t_end = time.time() self.assertTrue(almost_equal(dms1, dms2)) print('Method 1:', t_end - t_start1) print('Method 2:', t_end - t_start2) print('Abount 10x faster')
def test_non_last_qubits_trace(self): # Test trace for two conseg. non-last qubits state1 = tensor([s0, s1]) state2 = tensor([(s0 + s1)/np.sqrt(2), (s0 - s1)/np.sqrt(2)]) state = tensor([state1, state2]) dm1 = trace(density_matrix(state), [0, 1]) self.assertTrue(almost_equal(dm1, density_matrix(state2))) # Test trace for two separated non-last qubits state_test = tensor([s0, (s0 + s1)/np.sqrt(2)]) dm2 = trace(density_matrix(state), [1, 3]) self.assertTrue(almost_equal(dm2, density_matrix(state_test)))
def apply_u(theta): u_layer.thetas.assign([theta]) new_states = u_layer.matrix() @ input_states new_states_dm = density_matrix(new_states, [0]) out_shape = ((lin_size, ) * 8) P0 = new_states_dm[:, 0, 0] return tf.reshape(P0, out_shape)
def test_append_zeros(self): def naive_impl(states, n): tensor([states] + [s0] * n) states = random_pure_states((10, 2 ** 12, 1)) states = append_zeros(states, 2) print(density_matrix(states))
def layer2(state_w, dm_in, t4): dm_w = density_matrix(state_w) dm_in = tensor([dm_w, dm_in]) # ndtotext_print(dm_in) # Remember this is a density matrix, so we must apply U on both sides U_layer2.thetas.assign([t4]) U_matrix = U_layer2.matrix() dm_out = U_matrix @ dm_in @ tf.math.conj(U_matrix) return trace(dm_out, [1, 2, 3])
def layer1(w1, w2, w3, x1, x2, x3, t1, t2, t3): state_1_in = tensor([w1, x1]) state_2_in = tensor([w2, x2]) state_3_in = tensor([w3, x3]) state_in = tensor([state_1_in, state_2_in, state_3_in]) U_layer1.thetas.assign([t1, t2, t3]) state_out = U_layer1.matrix() @ state_in out_layer1 = density_matrix(state_out, [0, 4, 8]) return out_layer1
def call(self, inputs, training=None, mask=None): x1, x2, x3 = inputs[..., 0, :, :], inputs[..., 1, :, :], inputs[..., 2, :, :] state_1_in = tensor([self.w1, x1]) state_2_in = tensor([self.w2, x2]) state_3_in = tensor([self.w3, x3]) state_in = tensor([state_1_in, state_2_in, state_3_in]) state_out = self.U_layer1.matrix() @ state_in dm_out_layer1 = density_matrix(state_out, [0, 4, 8]) dm_w = density_matrix(self.w4) dm_in = tensor([dm_w, dm_out_layer1]) U_matrix = self.U_layer2.matrix() dm_out = U_matrix @ dm_in @ tf.math.conj(U_matrix) dm_output = trace(dm_out, [1, 2, 3]) P0 = dm_output[..., 0, 0] return P0
def test_density_matrix_trace_from_state(self): with tf.device('cpu'): # This method can take 500 states of 2**12 at once! dm1 = density_matrix(self.states1, [0, 1]) # Density matrix of forst 10 qubits dm1_0 = density_matrix(self.states1[0], [0, 1]) # This con only take ~40 states of 2**12 at once... dm2 = trace(density_matrix(self.states1), [2, 3]) dm2_0 = trace(density_matrix(self.states1[0]), [2, 3]) # print('dm') # ndtotext_print(density_matrix(self.states1)) # print('dm1') # ndtotext_print(dm1) # print('dm1_0') # ndtotext_print(dm1_0) # print('dm2') # ndtotext_print(dm2) # print('dm2_0') # ndtotext_print(dm2_0) self.assertTrue(almost_equal(dm1[0], dm1_0, 1e-3), 'density_matrix mixes states!') self.assertTrue(almost_equal(dm2[0], dm2_0, 1e-3), 'trace->density_matrix mixes states!') self.assertTrue(almost_equal(dm1, dm2, 1e-3))
def naive_alg(a, b): sqrtm = tf.linalg.sqrtm dm_a = density_matrix(a) dm_b = density_matrix(b) result = tf.linalg.trace(sqrtm(sqrtm(dm_a) @ dm_b @ sqrtm(dm_a))) ** 2 return tf.cast(result, float_type)
def call(self, y_true, y_pred): delta = density_matrix(y_true, self.subsystem) - density_matrix( y_pred, self.subsystem) result = tf.linalg.trace( tf.linalg.sqrtm(tf.linalg.adjoint(delta) @ delta)) / 2 return tf.cast(tf.reduce_mean(result), float_type)