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:
KetandBraclasses 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:
Operatorclass 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:
DensityMatrixclass for mixed quantum states- Automatic validation of density matrix properties
- Fidelity calculation between states
- Time evolution under unitary operators
- Partial trace support
-
Quantum Channels:
QuantumChannelclass 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
- Pauli matrices (
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
fd724fc2277b9d6fa830bf23af9db17d246781599cf0d295352082f1eecfb879
|
|
| MD5 |
a6f9660686eb4a4757a750d09da30d4e
|
|
| BLAKE2b-256 |
0fa619524c0b211f207094f3145b26c1781555214fe5a644b3c3f70d57ffb8ff
|
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
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b407bc55ee7507225b6dd4ae43da1c5c11bd6dbfeb3a866c4611050fdd60c0c
|
|
| MD5 |
102c5ac31a51d33224d844c8c88c8405
|
|
| BLAKE2b-256 |
269e9a0ade1e778ad3ed77629e8c77f36b5bc3e7b07230599b2aed10ebac7ae4
|