Quantum Nodes¶
This guide provides detailed information about quantum nodes, their properties, and how to work with quantum superposition in probabilistic reasoning.
Understanding Quantum Nodes¶
Quantum nodes represent variables that exist in quantum superposition, allowing them to be in multiple states simultaneously with complex probability amplitudes.
Quantum Superposition¶
Unlike classical variables that have definite values, quantum nodes can exist in superposition:
import numpy as np
from probabilistic_quantum_reasoner import QuantumBayesianNetwork
from probabilistic_quantum_reasoner.backends import ClassicalSimulator
backend = ClassicalSimulator()
network = QuantumBayesianNetwork("QuantumDemo", backend)
# Classical node - definite state
classical_coin = network.add_stochastic_node(
"classical_coin",
outcome_space=["heads", "tails"]
)
classical_coin.prior_distribution = np.array([0.5, 0.5])
# Quantum node - superposition state
quantum_coin = network.add_quantum_node(
"quantum_coin",
outcome_space=["heads", "tails"],
initial_amplitudes=np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex)
)
print(f"Classical probabilities: {classical_coin.prior_distribution}")
print(f"Quantum amplitudes: {quantum_coin.quantum_state.amplitudes}")
print(f"Quantum probabilities: {quantum_coin.probability_distribution}")
Complex Amplitudes¶
Quantum amplitudes are complex numbers that encode both magnitude and phase:
# Amplitude with phase
phase = np.pi / 4
quantum_node = network.add_quantum_node(
"phase_node",
outcome_space=["state_0", "state_1"],
initial_amplitudes=np.array([
1/np.sqrt(2), # Real amplitude
np.exp(1j * phase) / np.sqrt(2) # Complex amplitude with phase
])
)
amplitudes = quantum_node.quantum_state.amplitudes
print(f"Amplitude magnitudes: {np.abs(amplitudes)}")
print(f"Amplitude phases: {np.angle(amplitudes)}")
print(f"Born rule probabilities: {np.abs(amplitudes)**2}")
Quantum State Operations¶
Applying Quantum Gates¶
Transform quantum states using unitary operators:
from probabilistic_quantum_reasoner.core.operators import QuantumGate
# Create quantum node in |0⟩ state
qubit = network.add_quantum_node(
"qubit",
outcome_space=[0, 1],
initial_amplitudes=np.array([1, 0], dtype=complex)
)
print(f"Initial state: {qubit.quantum_state.amplitudes}")
# Apply Hadamard gate to create superposition
hadamard = QuantumGate.hadamard()
network.apply_quantum_operator("qubit", hadamard)
print(f"After Hadamard: {qubit.quantum_state.amplitudes}")
# Apply Pauli-X gate (bit flip)
pauli_x = QuantumGate.pauli_x()
network.apply_quantum_operator("qubit", pauli_x)
print(f"After Pauli-X: {qubit.quantum_state.amplitudes}")
# Apply rotation gate
rotation_y = QuantumGate.rotation_y(np.pi/3)
network.apply_quantum_operator("qubit", rotation_y)
print(f"After Y rotation: {qubit.quantum_state.amplitudes}")
Custom Quantum Operations¶
Create and apply custom unitary operations:
from probabilistic_quantum_reasoner.core.operators import UnitaryOperator
# Custom 2x2 unitary matrix
custom_matrix = np.array([
[0.6, 0.8],
[0.8, -0.6]
], dtype=complex)
# Verify it's unitary
assert np.allclose(custom_matrix @ custom_matrix.conj().T, np.eye(2))
# Create and apply custom operator
custom_operator = UnitaryOperator(custom_matrix)
network.apply_quantum_operator("qubit", custom_operator)
print(f"After custom operation: {qubit.quantum_state.amplitudes}")
Multi-State Quantum Nodes¶
Qutrit Example (3-Level System)¶
# Three-level quantum system
qutrit = network.add_quantum_node(
"qutrit",
outcome_space=["low", "medium", "high"],
initial_amplitudes=np.array([1, 1, 1], dtype=complex) / np.sqrt(3)
)
print(f"Qutrit state: {qutrit.quantum_state.amplitudes}")
print(f"Qutrit probabilities: {qutrit.probability_distribution}")
# Custom 3x3 unitary (discrete Fourier transform)
dft_3 = np.array([
[1, 1, 1],
[1, np.exp(2j*np.pi/3), np.exp(4j*np.pi/3)],
[1, np.exp(4j*np.pi/3), np.exp(8j*np.pi/3)]
], dtype=complex) / np.sqrt(3)
dft_operator = UnitaryOperator(dft_3)
network.apply_quantum_operator("qutrit", dft_operator)
print(f"After DFT: {qutrit.quantum_state.amplitudes}")
Many-Level Systems¶
# High-dimensional quantum node
n_levels = 8
high_dim_node = network.add_quantum_node(
"high_dim",
outcome_space=list(range(n_levels)),
initial_amplitudes=np.ones(n_levels, dtype=complex) / np.sqrt(n_levels)
)
# Apply random unitary transformation
from probabilistic_quantum_reasoner.core.operators import QuantumGate
random_unitary = QuantumGate.random_unitary(n_levels)
network.apply_quantum_operator("high_dim", random_unitary)
print(f"High-dimensional state dimension: {high_dim_node.quantum_state.dimension}")
Quantum Measurements¶
Computational Basis Measurement¶
from probabilistic_quantum_reasoner.core.operators import MeasurementOperator
# Create measurement operator
computational_measurement = MeasurementOperator.computational_basis(2)
# Measure quantum node in superposition
superposition_node = network.add_quantum_node(
"measurement_demo",
outcome_space=[0, 1],
initial_amplitudes=np.array([0.6, 0.8], dtype=complex)
)
# Perform measurement
result = network.measure_node("measurement_demo")
print(f"Measurement outcome: {result['outcome']}")
print(f"Measurement probability: {result['probability']}")
print(f"Post-measurement state: {result['post_measurement_state']}")
# State has collapsed
print(f"Node state after measurement: {superposition_node.quantum_state.amplitudes}")
Observable Measurements¶
# Pauli-Z measurement (energy eigenvalues)
pauli_z_measurement = MeasurementOperator.pauli_z()
# Create node in superposition
energy_node = network.add_quantum_node(
"energy",
outcome_space=["low_energy", "high_energy"],
initial_amplitudes=np.array([0.8, 0.6], dtype=complex)
)
# Measure energy
energy_result = pauli_z_measurement.measure(energy_node.quantum_state.amplitudes)
print(f"Energy measurement: {energy_result}")
# Pauli-X measurement (spin in X direction)
pauli_x_measurement = MeasurementOperator.pauli_x()
spin_result = pauli_x_measurement.measure(energy_node.quantum_state.amplitudes)
print(f"Spin-X measurement: {spin_result}")
Expectation Values¶
# Compute expectation values without measurement
def compute_expectation_value(node, observable_matrix):
"""Compute expectation value ⟨ψ|O|ψ⟩."""
amplitudes = node.quantum_state.amplitudes
return np.real(np.conj(amplitudes) @ observable_matrix @ amplitudes)
# Pauli-Z expectation value
pauli_z = np.array([[1, 0], [0, -1]], dtype=complex)
z_expectation = compute_expectation_value(energy_node, pauli_z)
print(f"⟨Z⟩ = {z_expectation}")
# Pauli-X expectation value
pauli_x = np.array([[0, 1], [1, 0]], dtype=complex)
x_expectation = compute_expectation_value(energy_node, pauli_x)
print(f"⟨X⟩ = {x_expectation}")
# Pauli-Y expectation value
pauli_y = np.array([[0, -1j], [1j, 0]], dtype=complex)
y_expectation = compute_expectation_value(energy_node, pauli_y)
print(f"⟨Y⟩ = {y_expectation}")
Quantum Entanglement¶
Creating Entangled States¶
# Two quantum nodes to be entangled
alice = network.add_quantum_node(
"alice",
outcome_space=[0, 1],
initial_amplitudes=np.array([1, 0], dtype=complex)
)
bob = network.add_quantum_node(
"bob",
outcome_space=[0, 1],
initial_amplitudes=np.array([1, 0], dtype=complex)
)
# Create entanglement (Bell state)
network.entangle([alice, bob])
# Check joint quantum state
joint_state = network.get_joint_quantum_state([alice, bob])
print(f"Joint entangled state: {joint_state.amplitudes}")
# This should be |00⟩ + |11⟩ (Bell state)
expected_bell = np.array([1/np.sqrt(2), 0, 0, 1/np.sqrt(2)], dtype=complex)
print(f"Expected Bell state: {expected_bell}")
Measuring Entangled Systems¶
# Measure Alice's qubit
alice_result = network.measure_node("alice")
print(f"Alice measured: {alice_result['outcome']}")
# Bob's state is now correlated
bob_state_after = bob.quantum_state.amplitudes
print(f"Bob's state after Alice's measurement: {bob_state_after}")
# Bob's measurement will be perfectly correlated
bob_result = network.measure_node("bob")
print(f"Bob measured: {bob_result['outcome']}")
print(f"Correlation: {alice_result['outcome'] == bob_result['outcome']}")
Multi-Particle Entanglement¶
# Create GHZ state (three-particle entanglement)
particle1 = network.add_quantum_node("particle1", [0, 1])
particle2 = network.add_quantum_node("particle2", [0, 1])
particle3 = network.add_quantum_node("particle3", [0, 1])
# Entangle all three particles
network.entangle([particle1, particle2, particle3])
# Apply operations to create GHZ state |000⟩ + |111⟩
hadamard = QuantumGate.hadamard()
cnot = QuantumGate.controlled_not()
# H on first particle, then CNOT chain
network.apply_quantum_operator("particle1", hadamard)
network.apply_two_qubit_gate("particle1", "particle2", cnot)
network.apply_two_qubit_gate("particle2", "particle3", cnot)
ghz_state = network.get_joint_quantum_state([particle1, particle2, particle3])
print(f"GHZ state: {ghz_state.amplitudes}")
Quantum Decoherence¶
Modeling Decoherence¶
# Simulate decoherence effects
def apply_decoherence(node, decoherence_rate, time_step):
"""Apply simple decoherence model."""
amplitudes = node.quantum_state.amplitudes
# Exponential decay of off-diagonal elements (T2 process)
decay_factor = np.exp(-time_step / decoherence_rate)
# Keep population (diagonal) terms, decay coherence (off-diagonal)
probabilities = np.abs(amplitudes) ** 2
new_amplitudes = np.sqrt(probabilities)
# Add small amount of phase noise
phase_noise = np.random.normal(0, 0.1, len(amplitudes))
new_amplitudes = new_amplitudes * np.exp(1j * phase_noise * decay_factor)
node.quantum_state.amplitudes = new_amplitudes
node.quantum_state.normalize()
# Example: decoherence in quantum node
decoherent_node = network.add_quantum_node(
"decoherent",
outcome_space=[0, 1],
initial_amplitudes=np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex)
)
print(f"Before decoherence: {decoherent_node.quantum_state.amplitudes}")
# Apply decoherence over time
for t in range(5):
apply_decoherence(decoherent_node, decoherence_rate=10.0, time_step=1.0)
print(f"After {t+1} time steps: {decoherent_node.quantum_state.amplitudes}")
Quantum State Analysis¶
State Properties¶
def analyze_quantum_state(node):
"""Analyze properties of a quantum state."""
amplitudes = node.quantum_state.amplitudes
probabilities = np.abs(amplitudes) ** 2
# Purity (how mixed vs pure the state is)
purity = np.sum(probabilities ** 2)
# Entropy (measure of uncertainty)
entropy = -np.sum(probabilities * np.log2(probabilities + 1e-10))
# Maximum coherence (off-diagonal terms)
coherence = np.sum(np.abs(amplitudes[np.triu_indices(len(amplitudes), k=1)]))
return {
"purity": purity,
"entropy": entropy,
"coherence": coherence,
"dimension": len(amplitudes)
}
# Analyze different quantum states
pure_state = network.add_quantum_node("pure", [0, 1], np.array([1, 0], dtype=complex))
mixed_state = network.add_quantum_node("mixed", [0, 1], np.array([0.7, 0.7], dtype=complex))
superposition = network.add_quantum_node("super", [0, 1], np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex))
for node_name, node in [("pure", pure_state), ("mixed", mixed_state), ("superposition", superposition)]:
analysis = analyze_quantum_state(node)
print(f"{node_name} state analysis: {analysis}")
Quantum Fidelity¶
def quantum_fidelity(state1, state2):
"""Compute fidelity between two quantum states."""
amplitudes1 = state1.quantum_state.amplitudes
amplitudes2 = state2.quantum_state.amplitudes
return abs(np.vdot(amplitudes1, amplitudes2)) ** 2
# Compare quantum states
state_a = network.add_quantum_node("state_a", [0, 1], np.array([1, 0], dtype=complex))
state_b = network.add_quantum_node("state_b", [0, 1], np.array([0, 1], dtype=complex))
state_c = network.add_quantum_node("state_c", [0, 1], np.array([1/np.sqrt(2), 1/np.sqrt(2)], dtype=complex))
print(f"Fidelity(A, B): {quantum_fidelity(state_a, state_b)}") # Orthogonal states
print(f"Fidelity(A, C): {quantum_fidelity(state_a, state_c)}") # Partial overlap
print(f"Fidelity(A, A): {quantum_fidelity(state_a, state_a)}") # Identical states
Best Practices¶
State Preparation¶
# Good: Normalized amplitudes
good_amplitudes = np.array([0.6, 0.8], dtype=complex)
good_amplitudes /= np.linalg.norm(good_amplitudes)
# Bad: Unnormalized amplitudes (will be auto-normalized but prints warning)
bad_amplitudes = np.array([1, 1], dtype=complex) # Should be [1/√2, 1/√2]
# Always verify normalization
assert abs(np.sum(np.abs(good_amplitudes)**2) - 1.0) < 1e-10
Memory Management¶
# Monitor quantum state sizes
def check_memory_usage(network):
"""Check memory usage of quantum states."""
total_amplitudes = 0
for node_id, node in network.nodes.items():
if hasattr(node, 'quantum_state'):
n_amplitudes = len(node.quantum_state.amplitudes)
total_amplitudes += n_amplitudes
print(f"Node {node_id}: {n_amplitudes} amplitudes")
memory_mb = total_amplitudes * 16 / 1024 / 1024 # 16 bytes per complex number
print(f"Total quantum memory: {memory_mb:.2f} MB")
return memory_mb
# Check before creating large quantum systems
memory_usage = check_memory_usage(network)
if memory_usage > 1000: # 1 GB limit
print("Warning: Large memory usage detected")
Performance Optimization¶
# Efficient quantum operations
class QuantumNodeManager:
def __init__(self, network):
self.network = network
self._cached_states = {}
def batch_apply_operations(self, operations):
"""Apply multiple quantum operations efficiently."""
for node_id, operator in operations:
self.network.apply_quantum_operator(node_id, operator)
def cache_quantum_state(self, node_id):
"""Cache quantum state for fast access."""
node = self.network.nodes[node_id]
self._cached_states[node_id] = node.quantum_state.amplitudes.copy()
def restore_quantum_state(self, node_id):
"""Restore cached quantum state."""
if node_id in self._cached_states:
node = self.network.nodes[node_id]
node.quantum_state.amplitudes = self._cached_states[node_id].copy()
Common Pitfalls¶
- Unnormalized States: Always ensure amplitudes are normalized
- Phase Ignoring: Remember that quantum phases matter for interference
- Measurement Timing: Quantum states collapse after measurement
- Memory Scaling: State space grows exponentially with system size
- Classical Thinking: Quantum superposition is not just probability
Next Steps¶
- Learn about Inference Methods
- Explore Causal Reasoning with quantum variables
- See Variational Methods for optimization
- Check Examples for practical applications