# Importing standard Qiskit libraries
from qiskit import QuantumCircuit, transpile
from qiskit.visualization import *
from ibm_quantum_widgets import *

# qiskit-ibmq-provider has been deprecated.
# Please see the Migration Guides in https://ibm.biz/provider_migration_guide for more detail.
from qiskit_ibm_runtime import QiskitRuntimeService, Sampler, Estimator, Session, Options

# Loading your IBM Quantum account(s)
service = QiskitRuntimeService(channel="ibm_quantum")

# Invoke a primitive. For more details see https://docs.quantum.ibm.com/run/primitives
# result = Sampler().run(circuits).result()

from qiskit import quantum_info as qi
import numpy as np

# Define the probability for the bit-flip error
p = 0.5

# Define the Kraus operators for the bit-flip channel
I = np.eye(2)  # Identity matrix
X = np.array([[0, 1], [1, 0]])  # Pauli-X matrix
E0 = np.sqrt(1-p) * I
E1 = np.sqrt(p) * X

# Create the bit-flip channel using Kraus operators
bit_flip_channel = qi.Kraus([E0, E1])

# Get the Choi matrix of the channel
choi_matrix = qi.Choi(bit_flip_channel)

# Alternatively, create the channel as a SuperOp and get the process matrix
process_matrix = qi.SuperOp(bit_flip_channel)

# Alternatively, create the channel as a SuperOp and get the process matrix
chi_matrix = qi.Chi(bit_flip_channel)

print("Choi matrix:")
print(choi_matrix.data)

print("\n(SuperOp representation):")
print(process_matrix.data)

print("\nProcess matrix ")
print(chi_matrix.data)

from qiskit import QuantumCircuit
from qiskit.quantum_info import Choi, Operator

# Create the entanglement circuit
qc = QuantumCircuit(2)
qc.h(0)        # Apply Hadamard gate to the first qubit
qc.cx(0, 1)    # Apply CNOT gate controlled by the first qubit targeting the second qubit

# Convert the circuit into an operator
operator = Operator(qc)

# Get the Choi matrix of the operator
choi_matrix = Choi(operator)

# Print the Choi matrix
print(choi_matrix)


from qiskit import QuantumCircuit
from qiskit.quantum_info import Operator

# Create the entanglement circuit
qc = QuantumCircuit(2)
qc.h(0)        # Apply Hadamard gate to the first qubit
qc.cx(0, 1)    # Apply CNOT gate controlled by the first qubit targeting the second qubit

# Convert the circuit to a unitary operator
unitary = Operator(qc)

# The Kraus operator for this unitary process is the unitary itself
kraus_op = [unitary.data]

# Print out the Kraus operator
for i, k in enumerate(kraus_op):
    print(f"Kraus operator K{i+1}:\n{k}\n")


