Skip to main content

A lightweight quantum computing library for foundational tools and mathematical operations.

Project description

Qlibx: A Quantum Computing Library

Qlibx is a comprehensive Python library designed to provide foundational tools for quantum computing. It offers classes and methods to represent and manipulate quantum states, operators, density matrices, and quantum channels. The library is lightweight, mathematically rigorous, and focuses on the foundations of quantum mechanics, making it an excellent choice for learning, experimentation, and research.


Features

  • Quantum States:

    • Ket and Bra classes to represent quantum states
    • Support for operations like addition, subtraction, scalar multiplication, tensor products, and inner/outer products
    • Automatic handling of complex and real coefficients
  • Quantum Operators:

    • Operator class to represent quantum operators
    • Methods for Hermitian, anti-Hermitian, unitary, and normality checks
    • Commutator and anti-commutator calculations
    • Spectral decomposition for Hermitian operators
    • Partial trace for composite systems
    • Von Neumann entropy calculation
    • Support for multi-qubit gates (CNOT, Hadamard)
  • Density Matrices:

    • DensityMatrix class for mixed quantum states
    • Automatic validation of density matrix properties
    • Fidelity calculation between states
    • Time evolution under unitary operators
    • Partial trace support
  • Quantum Channels:

    • QuantumChannel class for representing quantum noise
    • Predefined channels: amplitude damping, depolarizing, phase damping
    • Quantum teleportation protocol implementation
  • Predefined Operators:

    • Pauli matrices (pauli_x, pauli_y, pauli_z) and identity matrix

Installation

Via pip

pip install qlibx

From source

Clone the repository and include the library in your Python project:

git clone https://github.com/Suraj52721/qc_lib/tree/master
cd qlibx

Ensure the qlibx directory is in your Python path.


Usage Guide

Importing the Library

from qlibx import Ket, Bra, Operator, DensityMatrix, QuantumChannel
import numpy as np

Quantum States

Creating Ket and Bra Vectors

# Create a Ket vector |0⟩
ket0 = Ket([1, 0])
print(ket0)  # Output: Ket([1. 0.])

# Create a Ket vector |1⟩
ket1 = Ket([0, 1])
print(ket1)  # Output: Ket([0. 1.])

# Create a Bra vector ⟨0|
bra0 = Bra([1, 0])
print(bra0)  # Output: Bra([1. 0.])

# Complex coefficients
ket_complex = Ket([1, 1j])
print(ket_complex)  # Output: Ket([1.+0.j 0.+1.j])

Basic Operations

Addition and Subtraction

ket1 = Ket([1, 0])
ket2 = Ket([0, 1])

# Addition: |+⟩ = (|0⟩ + |1⟩)/√2
ket_plus = (ket1 + ket2) * (1/np.sqrt(2))
print(ket_plus)  # Output: Ket([0.70710678 0.70710678])

# Subtraction: |-⟩ = (|0⟩ - |1⟩)/√2
ket_minus = (ket1 - ket2) * (1/np.sqrt(2))
print(ket_minus)  # Output: Ket([ 0.70710678 -0.70710678])

Scalar Multiplication

ket = Ket([1, 0])

# Multiply by scalar
result = 2 * ket
print(result)  # Output: Ket([2. 0.])

# Normalization
ket_unnorm = Ket([3, 4])
norm = np.sqrt(ket_unnorm.inner_product(ket_unnorm.dagger()))
ket_norm = (1/norm) * ket_unnorm
print(ket_norm)  # Output: Ket([0.6 0.8])

Dagger (Conjugate Transpose)

# Ket to Bra
ket = Ket([1, 1j])
bra = ket.dagger()
print(bra)  # Output: Bra([1.-0.j 0.-1.j])

# Bra to Ket
bra = Bra([1, 1j])
ket = bra.dagger()
print(ket)  # Output: Ket([1.-0.j 0.-1.j])

Inner and Outer Products

# Inner product: ⟨ψ|φ⟩
ket_psi = Ket([1, 0])
bra_phi = Bra([1, 0])
inner = ket_psi.inner_product(bra_phi)
print(inner)  # Output: 1.0

# Outer product: |ψ⟩⟨φ| (creates a projection operator)
ket = Ket([1, 0])
bra = Bra([1, 0])
outer = ket.outer_product(bra)
print(outer)
# Output:
# [[1. 0.]
#  [0. 0.]]

Tensor Products

# Two-qubit state |00⟩
ket0 = Ket([1, 0])
ket00 = ket0.tensor(ket0)
print(ket00)  # Output: Ket([1. 0. 0. 0.])

# Bell state |Φ+⟩ = (|00⟩ + |11⟩)/√2
ket00 = Ket([1, 0]).tensor(Ket([1, 0]))
ket11 = Ket([0, 1]).tensor(Ket([0, 1]))
bell_state = (ket00 + ket11) * (1/np.sqrt(2))
print(bell_state)  # Output: Ket([0.70710678 0. 0. 0.70710678])

# Multiple tensor products
ket_3qubit = Ket([1, 0]).tensor(Ket([1, 0], Ket([1, 0]))
print(ket_3qubit)  # Output: Ket([1. 0. 0. 0. 0. 0. 0. 0.])

Quantum Operators

Creating and Applying Operators

# Define Pauli-X operator (NOT gate)
pauli_x_op = Operator(Operator.pauli_x)
print(pauli_x_op)
# Output: Operator([[0 1]
#                   [1 0]])

# Apply operator to a Ket
ket0 = Ket([1, 0])
ket1 = pauli_x_op.op(ket0)
print(ket1)  # Output: Ket([0.+0.j 1.+0.j])

# Pauli-Y operator
pauli_y_op = Operator(Operator.pauli_y)
print(pauli_y_op)
# Output: Operator([[ 0.+0.j -0.-1.j]
#                   [ 0.+1.j  0.+0.j]])

# Pauli-Z operator
pauli_z_op = Operator(Operator.pauli_z)
print(pauli_z_op)
# Output: Operator([[ 1  0]
#                   [ 0 -1]])

Operator Properties

# Check if operator is Hermitian
X = Operator(Operator.pauli_x)
print(X.hermitian())  # Output: True

# Check if operator is anti-Hermitian
Y = Operator(Operator.pauli_y)
print(Y.antihermitian())  # Output: True

# Check if operator is unitary
print(X.unitary())  # Output: True

# Check if operator is normal (commutes with its adjoint)
print(X.normal())  # Output: True

Operator Arithmetic

# Addition
I = Operator(Operator.identity)
X = Operator(Operator.pauli_x)
result = I + X
print(result)
# Output: Operator([[1 1]
#                   [1 1]])

# Subtraction
result = I - X
print(result)
# Output: Operator([[ 1 -1]
#                   [-1  1]])

# Scalar multiplication
result = 2 * X
print(result)
# Output: Operator([[0 2]
#                   [2 0]])

# Matrix multiplication
X = Operator(Operator.pauli_x)
Y = Operator(Operator.pauli_y)
result = X @ Y
print(result)
# Output: Operator([[0.+1.j 0.+0.j]
#                   [0.+0.j 0.-1.j]])

Commutators and Anti-Commutators

X = Operator(Operator.pauli_x)
Y = Operator(Operator.pauli_y)
Z = Operator(Operator.pauli_z)

# Commutator [X, Y] = XY - YX = 2iZ
commutator = X.commutator(Y)
print(commutator)
# Output: Operator([[0.+2.j 0.+0.j]
#                   [0.+0.j 0.-2.j]])

# Anti-commutator {X, Y} = XY + YX
anti_comm = X.anti_commutator(Y)
print(anti_comm)
# Output: Operator([[0.+0.j 0.+0.j]
#                   [0.+0.j 0.+0.j]])

# Pauli matrices anti-commute
print(X.anti_commutator(Z))
# Output: Operator([[0 0]
#                   [0 0]])

Spectral Decomposition

# Spectral decomposition of Pauli-Z
Z = Operator(Operator.pauli_z)
spectral = Z.spectral_decom()
for eigenvalue, eigenvector in spectral:
    print(f"Eigenvalue: {eigenvalue}, Eigenvector: {eigenvector}")
# Output:
# Eigenvalue: -1.0, Eigenvector: [0. 1.]
# Eigenvalue: 1.0, Eigenvector: [1. 0.]

# General Hermitian operator
H = Operator([[2, 1], [1, 2]])
spectral = H.spectral_decom()
for eigenvalue, eigenvector in spectral:
    print(f"Eigenvalue: {eigenvalue}, Eigenvector: {eigenvector}")
# Output:
# Eigenvalue: 1.0, Eigenvector: [-0.70710678  0.70710678]
# Eigenvalue: 3.0, Eigenvector: [0.70710678 0.70710678]

Tensor Products of Operators

# Two-qubit operator: X ⊗ I
X = Operator(Operator.pauli_x)
I = Operator(Operator.identity)
X_I = X.tensor(I)
print(X_I)
# Output: Operator([[0 0 1 0]
#                   [0 0 0 1]
#                   [1 0 0 0]
#                   [0 1 0 0]])

# I ⊗ X (different qubit)
I_X = I.tensor(X)
print(I_X)
# Output: Operator([[0 1 0 0]
#                   [1 0 0 0]
#                   [0 0 0 1]
#                   [0 0 1 0]])

Multi-Qubit Gates

CNOT Gate

# CNOT gate on 2 qubits (control=0, target=1)
cnot = Operator.cnot(control=0, target=1, no_of_qubits=2)
print(cnot)
# Output: Operator([[1.+0.j 0.+0.j 0.+0.j 0.+0.j]
#                   [0.+0.j 1.+0.j 0.+0.j 0.+0.j]
#                   [0.+0.j 0.+0.j 0.+0.j 1.+0.j]
#                   [0.+0.j 0.+0.j 1.+0.j 0.+0.j]])

# Apply CNOT to |10⟩ → |11⟩
ket10 = Ket([0, 1, 0, 0])
result = cnot.op(ket10)
print(result)  # Output: Ket([0.+0.j 0.+0.j 0.+0.j 1.+0.j])  # |11⟩

Hadamard Gate

# Hadamard on qubit 0 in a 2-qubit system
H0 = Operator.hadamard(qubit=0, no_of_qubits=2)
print(H0)
# Output: Operator([[ 0.70710678+0.j  0.70710678+0.j  0.        +0.j  0.        +0.j]
#                   [ 0.70710678+0.j -0.70710678+0.j  0.        +0.j  0.        +0.j]
#                   [ 0.        +0.j  0.        +0.j  0.70710678+0.j  0.70710678+0.j]
#                   [ 0.        +0.j  0.        +0.j  0.70710678+0.j -0.70710678+0.j]])

# Apply Hadamard to |0⟩ → |+⟩
ket0 = Ket([1, 0])
H = Operator.hadamard(0, 1)
ket_plus = H.op(ket0)
print(ket_plus)  # Output: Ket([0.70710678+0.j 0.70710678+0.j])

Partial Trace

# Create a Bell state density matrix
bell = (Ket([1, 0, 0, 0]) + Ket([0, 0, 0, 1])) * (1/np.sqrt(2))
rho_bell = Operator(bell.outer_product(bell.dagger()))

# Trace out second qubit (keep first)
rho_reduced = rho_bell.partial_trace(keep=[0], dims=[2, 2])
print(rho_reduced)
# Output: Operator([[0.5 0. ]
#                   [0.  0.5]])  # Maximally mixed state

# Trace out first qubit (keep second)
rho_reduced = rho_bell.partial_trace(keep=[1], dims=[2, 2])
print(rho_reduced)
# Output: Operator([[0.5 0. ]
#                   [0.  0.5]])  # Also maximally mixed

Von Neumann Entropy

# Entropy of a pure state (should be 0)
ket = Ket([1, 0])
rho_pure = Operator(ket.outer_product(ket.dagger()))
entropy = rho_pure.von_neumann_entropy()
print(f"Entropy of pure state: {entropy:.6f}")  # Output: 0.000000

# Entropy of maximally mixed state (should be ln(2))
rho_mixed = Operator([[0.5, 0], [0, 0.5]])
entropy = rho_mixed.von_neumann_entropy()
print(f"Entropy of mixed state: {entropy:.6f}")  # Output: 0.693147 (ln(2))

Density Matrices

Creating Density Matrices

# Pure state density matrix: |0⟩⟨0|
ket0 = Ket([1, 0])
rho_pure = DensityMatrix([[1, 0], [0, 0]])
print(rho_pure)

# Maximally mixed state: I/2
rho_mixed = DensityMatrix([[0.5, 0], [0, 0.5]])
print(rho_mixed)

# From a Ket
ket = Ket([1, 0])
rho = DensityMatrix(ket.outer_product(ket.dagger()))

Fidelity Calculation

# Fidelity between pure states
rho1 = DensityMatrix([[1, 0], [0, 0]])  # |0⟩
rho2 = DensityMatrix([[0, 0], [0, 1]])  # |1⟩
fidelity = rho1.fidelity(rho2)
print(f"Fidelity: {fidelity:.4f}")  # Output: 0.0000 (orthogonal states)

# Fidelity between identical states
rho1 = DensityMatrix([[1, 0], [0, 0]])
rho2 = DensityMatrix([[1, 0], [0, 0]])
fidelity = rho1.fidelity(rho2)
print(f"Fidelity: {fidelity:.4f}")  # Output: 1.0000

# Fidelity with mixed state
rho_pure = DensityMatrix([[1, 0], [0, 0]])
rho_mixed = DensityMatrix([[0.5, 0], [0, 0.5]])
fidelity = rho_pure.fidelity(rho_mixed)
print(f"Fidelity: {fidelity:.4f}")  # Output: 0.2500

Time Evolution

# Initialize state
rho = DensityMatrix([[1, 0], [0, 0]])  # |0⟩

# Apply Pauli-X (bit flip)
X = Operator(Operator.pauli_x)
rho_evolved = rho.evolve(X)
print(rho_evolved)
# Output: DensityMatrix([[0 0]
#                        [0 1]])  # |1⟩

# Apply Hadamard
H = Operator.hadamard(0, 1)
rho_evolved = rho.evolve(H)
print(rho_evolved)
# Output: DensityMatrix([[0.5 0.5]
#                        [0.5 0.5]])  # |+⟩ state

Partial Trace for Density Matrices

# Two-qubit maximally entangled state
bell = (Ket([1, 0, 0, 0]) + Ket([0, 0, 0, 1])) * (1/np.sqrt(2))
rho_bell = DensityMatrix(bell.outer_product(bell.dagger()))

# Trace out second qubit
rho_A = rho_bell.partial_trace(keep=[0], dims=[2, 2])
print(rho_A)
# Output: DensityMatrix([[0.5 0. ]
#                        [0.  0.5]])  # Maximally mixed

# Entropy of reduced state
entropy = rho_A.von_neumann_entropy()
print(f"Entanglement entropy: {entropy:.6f}")  # Output: 0.693147

Quantum Channels

Amplitude Damping Channel

# Create amplitude damping channel with γ = 0.2
gamma = 0.2
channel = QuantumChannel.amplitude_damping(gamma)

# Apply to excited state |1⟩
rho_initial = DensityMatrix([[0, 0], [0, 1]])
rho_final = channel.apply(rho_initial)
print(rho_final)
# The state decays towards |0⟩

# Fidelity with initial state
fidelity = rho_final.fidelity(rho_initial)
print(f"Fidelity after damping: {fidelity:.4f}")

Depolarizing Channel

# Create depolarizing channel with p = 0.1
p = 0.1
channel = QuantumChannel.depolarizing(p)

# Apply to pure state
rho_initial = DensityMatrix([[1, 0], [0, 0]])
rho_final = channel.apply(rho_initial)
print(rho_final)
# The state becomes more mixed

# Calculate entropy increase
entropy_initial = rho_initial.von_neumann_entropy()
entropy_final = rho_final.von_neumann_entropy()
print(f"Entropy increase: {entropy_final - entropy_initial:.4f}")

Phase Damping Channel

# Create phase damping channel with γ = 0.3
gamma = 0.3
channel = QuantumChannel.phase_damping(gamma)

# Apply to superposition state |+⟩
ket_plus = (Ket([1, 0]) + Ket([0, 1])) * (1/np.sqrt(2))
rho_initial = DensityMatrix(ket_plus.outer_product(ket_plus.dagger()))
rho_final = channel.apply(rho_initial)
print(rho_final)
# Off-diagonal elements decay

Custom Quantum Channel

# Define custom Kraus operators
K0 = Operator([[1, 0], [0, np.sqrt(0.9)]])
K1 = Operator([[0, np.sqrt(0.1)], [0, 0]])

# Create custom channel
custom_channel = QuantumChannel([K0, K1])

# Apply to state
rho = DensityMatrix([[1, 0], [0, 0]])
rho_final = custom_channel.apply(rho)
print(rho_final)

Quantum Teleportation

Basic Teleportation (No Noise)

# State to teleport: |+⟩
psi = (Ket([1, 0]) + Ket([0, 1])) * (1/np.sqrt(2))

# Run teleportation protocol
results = QuantumChannel.quantum_teleportation(psi, gamma=0.0)

# Check results
print(f"Average fidelity: {results['average_fidelity']:.6f}")
print(f"Measurement probabilities: {results['measurement_probabilities']}")

Teleportation with Noise

# State to teleport: |0⟩
psi = Ket([1, 0])

# Run with amplitude damping noise
results = QuantumChannel.quantum_teleportation(psi, gamma=0.1)

# Analyze results
print(f"Average fidelity: {results['average_fidelity']:.6f}")
for outcome, fidelity in results['fidelities'].items():
    prob = results['measurement_probabilities'][outcome]
    print(f"Outcome {outcome}: prob={prob:.4f}, fidelity={fidelity:.6f}")

Example: Bell State Creation and Analysis

from qlibx import Ket, Operator, DensityMatrix

# Create |00⟩ state
ket00 = Ket([1, 0]).tensor(Ket([1, 0]))

# Apply Hadamard to first qubit
H = Operator.hadamard(0, 2)
state_after_H = H.op(ket00)

# Apply CNOT
CNOT = Operator.cnot(0, 1, 2)
bell_state = CNOT.op(state_after_H)

print("Bell state |Φ+⟩:")
print(bell_state)

# Create density matrix
rho = DensityMatrix(bell_state.outer_product(bell_state.dagger()))

# Check entanglement via partial trace
rho_A = rho.partial_trace(keep=[0], dims=[2, 2])
entropy = rho_A.von_neumann_entropy()
print(f"\nEntanglement entropy: {entropy:.6f}")
print(f"This is {'maximal' if abs(entropy - np.log(2)) < 0.001 else 'not maximal'} entanglement")

Example: Quantum Circuit Simulation

from qlibx import Ket, Operator

# Initialize in |0⟩
psi = Ket([1, 0])
print(f"Initial state: {psi.coef}")

# Apply Hadamard gate
H = Operator.hadamard(0, 1)
psi = H.op(psi)
print(f"After Hadamard: {psi.coef}")

# Apply Pauli-Z gate
Z = Operator(Operator.pauli_z)
psi = Z.op(psi)
print(f"After Pauli-Z: {psi.coef}")

# Apply Hadamard again
psi = H.op(psi)
print(f"Final state: {psi.coef}")

Example: Decoherence Simulation

from qlibx import Ket, DensityMatrix, QuantumChannel
import matplotlib.pyplot as plt

# Initial superposition state
ket_plus = (Ket([1, 0]) + Ket([0, 1])) * (1/np.sqrt(2))
rho_initial = DensityMatrix(ket_plus.outer_product(ket_plus.dagger()))

# Simulate decoherence over time
gammas = np.linspace(0, 1, 20)
fidelities = []

for gamma in gammas:
    channel = QuantumChannel.amplitude_damping(gamma)
    rho_final = channel.apply(rho_initial)
    fidelity = rho_final.fidelity(rho_initial)
    fidelities.append(fidelity)

# Plot results
plt.plot(gammas, fidelities)
plt.xlabel('Damping parameter γ')
plt.ylabel('Fidelity')
plt.title('Decoherence of Superposition State')
plt.grid(True)
plt.show()

Advanced Usage Tips

Working with Complex States

# Create complex superposition
alpha = 1/np.sqrt(2)
beta = 1j/np.sqrt(2)
psi = alpha * Ket([1, 0]) + beta * Ket([0, 1])
print(psi)

# Check normalization
norm_squared = psi.inner_product(psi.dagger())
print(f"Norm squared: {norm_squared}")  # Should be 1

Chaining Operations

# Multiple gate applications
ket = Ket([1, 0])
X = Operator(Operator.pauli_x)
Y = Operator(Operator.pauli_y)
Z = Operator(Operator.pauli_z)

# Apply X, then Y, then Z
result = Z.op(Y.op(X.op(ket)))
print(result)

# Or using operator composition
XYZ = Z @ Y @ X
result = XYZ.op(ket)
print(result)

Error Handling

try:
    # This will raise an error (invalid density matrix)
    rho = DensityMatrix([[1, 0], [0, 2]])  # Trace > 1
except ValueError as e:
    print(f"Error: {e}")

try:
    # This will raise an error (not Hermitian)
    op = Operator([[1, 1], [0, 1]])
    entropy = op.von_neumann_entropy()
except ValueError as e:
    print(f"Error: {e}")

Contributing

Contributions are welcome! If you find a bug or have a feature request, please open an issue or submit a pull request on GitHub.


License

This project is licensed under the MIT License.


Acknowledgments

Qlibx is inspired by the mathematical foundations of quantum mechanics and quantum information theory. It aims to provide an intuitive and powerful interface for quantum computing enthusiasts, researchers, and educators.


Requirements

  • Python 3.7+
  • NumPy
  • SciPy

Install dependencies:

pip install numpy scipy

Citation

If you use Qlibx in your research or projects, please cite:

@software{qlibx2024,
  author = {Suraj},
  title = {Qlibx: A Quantum Computing Library},
  year = {2024},
  url = {https://github.com/Suraj52721/qc_lib}
}

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

qlibx-1.0.4.tar.gz (20.1 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

qlibx-1.0.4-py3-none-any.whl (14.8 kB view details)

Uploaded Python 3

File details

Details for the file qlibx-1.0.4.tar.gz.

File metadata

  • Download URL: qlibx-1.0.4.tar.gz
  • Upload date:
  • Size: 20.1 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for qlibx-1.0.4.tar.gz
Algorithm Hash digest
SHA256 fd724fc2277b9d6fa830bf23af9db17d246781599cf0d295352082f1eecfb879
MD5 a6f9660686eb4a4757a750d09da30d4e
BLAKE2b-256 0fa619524c0b211f207094f3145b26c1781555214fe5a644b3c3f70d57ffb8ff

See more details on using hashes here.

File details

Details for the file qlibx-1.0.4-py3-none-any.whl.

File metadata

  • Download URL: qlibx-1.0.4-py3-none-any.whl
  • Upload date:
  • Size: 14.8 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for qlibx-1.0.4-py3-none-any.whl
Algorithm Hash digest
SHA256 7b407bc55ee7507225b6dd4ae43da1c5c11bd6dbfeb3a866c4611050fdd60c0c
MD5 102c5ac31a51d33224d844c8c88c8405
BLAKE2b-256 269e9a0ade1e778ad3ed77629e8c77f36b5bc3e7b07230599b2aed10ebac7ae4

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page