mirror of
https://github.com/blackboxprogramming/native-ai-quantum-energy.git
synced 2026-03-18 03:34:03 -05:00
Remove NumPy dependency from quantum simulator and expand tests
This commit is contained in:
53
README.md
53
README.md
@@ -1,7 +1,7 @@
|
|||||||
# Native AI Quantum Energy Lab
|
# Native AI Quantum Energy Lab
|
||||||
|
|
||||||
**Native AI Quantum Energy Lab** is an experimental educational project that combines a simple
|
**Native AI Quantum Energy Lab** is an experimental educational project that combines a simple
|
||||||
quantum‑computing simulator with an exploration of long‑standing unsolved mathematical
|
quantum-computing simulator with an exploration of long-standing unsolved mathematical
|
||||||
problems and a small library of energy and particle simulations. It is inspired by the
|
problems and a small library of energy and particle simulations. It is inspired by the
|
||||||
idea of building a “native AI quantum computer” – a software system capable of running
|
idea of building a “native AI quantum computer” – a software system capable of running
|
||||||
quantum circuits, thinking about deep mathematical questions and even modeling energy
|
quantum circuits, thinking about deep mathematical questions and even modeling energy
|
||||||
@@ -14,11 +14,11 @@ ten famous unsolved problems in mathematics:
|
|||||||
|
|
||||||
1. **Quantum Computing Simulation** – a minimal yet functional simulator for quantum
|
1. **Quantum Computing Simulation** – a minimal yet functional simulator for quantum
|
||||||
circuits written in pure Python (found in `quantum_simulator.py`). The simulator
|
circuits written in pure Python (found in `quantum_simulator.py`). The simulator
|
||||||
supports a small set of single‑qubit gates (Hadamard and Pauli‑X), a
|
supports a small set of single-qubit gates (Hadamard and Pauli-X), a
|
||||||
two‑qubit controlled‑NOT (CNOT) gate and measurement. You can create
|
two-qubit controlled-NOT (CNOT) gate and measurement. You can create
|
||||||
circuits, apply gates, and measure qubits to observe the probabilistic outcomes
|
circuits, apply gates, and measure qubits to observe the probabilistic outcomes
|
||||||
expected from quantum mechanics. The code uses the vector–state model and
|
expected from quantum mechanics. The code uses the vector–state model implemented
|
||||||
linear algebra via NumPy.
|
directly with Python lists and complex numbers, so it has no third-party dependencies.
|
||||||
|
|
||||||
2. **Energy and Particle Simulation** – a simple set of utilities (in
|
2. **Energy and Particle Simulation** – a simple set of utilities (in
|
||||||
`energy_simulator.py`) that model energy generation and consumption as well as
|
`energy_simulator.py`) that model energy generation and consumption as well as
|
||||||
@@ -29,8 +29,8 @@ ten famous unsolved problems in mathematics:
|
|||||||
* `battery_discharge(capacity_mAh, load_mA, hours)` – estimates the remaining
|
* `battery_discharge(capacity_mAh, load_mA, hours)` – estimates the remaining
|
||||||
battery capacity after discharging at a given load.
|
battery capacity after discharging at a given load.
|
||||||
* `simulate_particle_collision(m1, v1, m2, v2)` – performs a simple
|
* `simulate_particle_collision(m1, v1, m2, v2)` – performs a simple
|
||||||
one‑dimensional elastic collision between two particles and returns their
|
one-dimensional elastic collision between two particles and returns their
|
||||||
post‑collision velocities.
|
post-collision velocities.
|
||||||
|
|
||||||
These routines are not intended to be accurate physical simulations but
|
These routines are not intended to be accurate physical simulations but
|
||||||
demonstrate how one might model energy and particle dynamics in software.
|
demonstrate how one might model energy and particle dynamics in software.
|
||||||
@@ -40,19 +40,28 @@ ten famous unsolved problems in mathematics:
|
|||||||
problem entry includes a succinct description and, where appropriate, links
|
problem entry includes a succinct description and, where appropriate, links
|
||||||
to authoritative sources for further reading. The list includes all of the
|
to authoritative sources for further reading. The list includes all of the
|
||||||
Clay Mathematics Institute (CMI) Millennium Prize problems (such as the
|
Clay Mathematics Institute (CMI) Millennium Prize problems (such as the
|
||||||
Riemann Hypothesis and P vs NP) plus additional conjectures from number
|
Riemann Hypothesis and P vs NP) plus additional conjectures from number
|
||||||
theory and analysis.
|
theory and analysis.
|
||||||
|
|
||||||
|
## Documentation and type hints
|
||||||
|
|
||||||
|
Every public function and method in the simulators is documented with detailed
|
||||||
|
NumPy-style docstrings that explain arguments, return values, units, edge cases,
|
||||||
|
and provide runnable examples. All modules use Python type hints to aid static
|
||||||
|
analysis and make the APIs self-documenting when used in IDEs.
|
||||||
|
|
||||||
## Getting started
|
## Getting started
|
||||||
|
|
||||||
To use the simulators you need Python 3 and NumPy installed. Clone this
|
Clone this repository and ensure you have Python 3.8 or later. No external
|
||||||
repository and run the Python modules directly, or import the functions into
|
libraries are required; the simulators depend only on the Python standard library.
|
||||||
your own scripts. For example, to create a simple quantum circuit:
|
You can run the modules directly or import the functions into your own scripts.
|
||||||
|
|
||||||
|
For example, to create a simple quantum circuit:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from native_ai_quantum_energy.quantum_simulator import QuantumCircuit
|
from native_ai_quantum_energy.quantum_simulator import QuantumCircuit
|
||||||
|
|
||||||
# Create a two‑qubit circuit
|
# Create a two-qubit circuit
|
||||||
qc = QuantumCircuit(2)
|
qc = QuantumCircuit(2)
|
||||||
|
|
||||||
# Put the first qubit into superposition and entangle with the second qubit
|
# Put the first qubit into superposition and entangle with the second qubit
|
||||||
@@ -63,22 +72,36 @@ qc.apply_cnot(0, 1)
|
|||||||
qc.measure_all()
|
qc.measure_all()
|
||||||
|
|
||||||
print("Measurement results:", qc.measurements)
|
print("Measurement results:", qc.measurements)
|
||||||
|
```
|
||||||
|
|
||||||
Similarly, to simulate energy production:
|
Similarly, to simulate energy production:
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from native_ai_quantum_energy.energy_simulator import solar_panel_output, battery_discharge
|
from native_ai_quantum_energy.energy_simulator import solar_panel_output, battery_discharge
|
||||||
|
|
||||||
# 100 W solar panel running for 5 hours at 15 % efficiency
|
# 100 W solar panel running for 5 hours at 15 % efficiency
|
||||||
energy_joules = solar_panel_output(100, 5, 0.15)
|
energy_joules = solar_panel_output(100, 5, 0.15)
|
||||||
print("Energy produced (J):", energy_joules)
|
print("Energy produced (J):", energy_joules)
|
||||||
|
|
||||||
# Battery with 2000 mAh capacity delivering 500 mA for 3 hours
|
# Battery with 2000 mAh capacity delivering 500 mA for 3 hours
|
||||||
remaining = battery_discharge(2000, 500, 3)
|
remaining = battery_discharge(2000, 500, 3)
|
||||||
print("Remaining capacity (mAh):", remaining)
|
print("Remaining capacity (mAh):", remaining)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Discla
|
## Running tests
|
||||||
|
|
||||||
|
The project ships with a comprehensive `pytest` test suite that covers both the
|
||||||
|
quantum and energy simulators. After installing the development dependencies
|
||||||
|
(`pip install -r requirements-dev.txt` if available, or simply `pip install pytest`),
|
||||||
|
run the tests with:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pytest
|
||||||
|
```
|
||||||
|
|
||||||
|
All tests should pass without requiring any additional configuration.
|
||||||
|
|
||||||
|
## Disclaimer
|
||||||
|
|
||||||
The “harness energy and particles” portion of this project is a purely digital
|
The “harness energy and particles” portion of this project is a purely digital
|
||||||
exercise. The simulations here do **not** allow a computer to collect real
|
exercise. The simulations here do **not** allow a computer to collect real
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
"""Quantum computing simulator for the Native AI Quantum Energy Lab.
|
"""Quantum computing simulator for the Native AI Quantum Energy Lab.
|
||||||
|
|
||||||
This module implements a tiny quantum circuit simulator based on the
|
This module implements a tiny quantum circuit simulator based on the
|
||||||
state‑vector formalism. It supports constructing a register of qubits,
|
state-vector formalism. It supports constructing a register of qubits,
|
||||||
applying single‑qubit gates (Hadamard and Pauli‑X) and a controlled‑NOT (CNOT)
|
applying single-qubit gates (Hadamard and Pauli-X) and a controlled-NOT (CNOT)
|
||||||
gate, and performing measurements. The simulator is intentionally small and
|
gate, and performing measurements. The simulator is intentionally small and
|
||||||
focused on clarity rather than performance.
|
focused on clarity rather than performance.
|
||||||
|
|
||||||
@@ -12,7 +12,7 @@ Example
|
|||||||
```python
|
```python
|
||||||
from native_ai_quantum_energy.quantum_simulator import QuantumCircuit
|
from native_ai_quantum_energy.quantum_simulator import QuantumCircuit
|
||||||
|
|
||||||
# Create a two‑qubit circuit
|
# Create a two-qubit circuit
|
||||||
qc = QuantumCircuit(2)
|
qc = QuantumCircuit(2)
|
||||||
|
|
||||||
# Put qubit 0 into superposition and entangle it with qubit 1
|
# Put qubit 0 into superposition and entangle it with qubit 1
|
||||||
@@ -24,24 +24,28 @@ result = qc.measure_all()
|
|||||||
print("Measurement outcomes:", result)
|
print("Measurement outcomes:", result)
|
||||||
```
|
```
|
||||||
|
|
||||||
The code uses NumPy for linear algebra. Install NumPy via `pip install numpy`.
|
The implementation is written using only the Python standard library so it can
|
||||||
|
run without third-party numerical dependencies.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import math
|
import math
|
||||||
import random
|
import random
|
||||||
from typing import List
|
from typing import List, Sequence, Tuple
|
||||||
|
|
||||||
import numpy as np
|
GateMatrix = Tuple[Tuple[complex, complex], Tuple[complex, complex]]
|
||||||
|
|
||||||
|
|
||||||
class QuantumCircuit:
|
class QuantumCircuit:
|
||||||
"""A minimal quantum circuit simulator based on state vectors."""
|
"""A minimal quantum circuit simulator based on state vectors."""
|
||||||
|
|
||||||
# Gate matrices for convenience
|
# Gate matrices for convenience
|
||||||
H_GATE: np.ndarray = (1 / math.sqrt(2)) * np.array([[1, 1], [1, -1]], dtype=complex)
|
H_GATE: GateMatrix = (
|
||||||
X_GATE: np.ndarray = np.array([[0, 1], [1, 0]], dtype=complex)
|
(1 / math.sqrt(2) + 0j, 1 / math.sqrt(2) + 0j),
|
||||||
|
(1 / math.sqrt(2) + 0j, -(1 / math.sqrt(2)) + 0j),
|
||||||
|
)
|
||||||
|
X_GATE: GateMatrix = ((0 + 0j, 1 + 0j), (1 + 0j, 0 + 0j))
|
||||||
|
|
||||||
def __init__(self, num_qubits: int) -> None:
|
def __init__(self, num_qubits: int) -> None:
|
||||||
if num_qubits < 1:
|
if num_qubits < 1:
|
||||||
@@ -49,56 +53,62 @@ class QuantumCircuit:
|
|||||||
self.num_qubits = num_qubits
|
self.num_qubits = num_qubits
|
||||||
# Start in the |0...0⟩ state
|
# Start in the |0...0⟩ state
|
||||||
dim = 2 ** num_qubits
|
dim = 2 ** num_qubits
|
||||||
self.state: np.ndarray = np.zeros(dim, dtype=complex)
|
self.state: List[complex] = [0j] * dim
|
||||||
self.state[0] = 1.0
|
self.state[0] = 1.0 + 0j
|
||||||
# Store measurement results
|
# Store measurement results
|
||||||
self.measurements: List[int] = []
|
self.measurements: List[int] = []
|
||||||
|
|
||||||
def _apply_single_qubit_gate(self, gate: np.ndarray, qubit: int) -> None:
|
def _apply_single_qubit_gate(self, gate: GateMatrix, qubit: int) -> None:
|
||||||
"""Apply a single‑qubit gate to the specified qubit.
|
"""Apply a single-qubit gate to the specified qubit.
|
||||||
|
|
||||||
This constructs the full operator via tensor products of identities and
|
This constructs the full operator implicitly by iterating over the
|
||||||
the given gate, then applies it to the state vector.
|
amplitudes associated with the target qubit and updating the state
|
||||||
|
vector in place.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if qubit < 0 or qubit >= self.num_qubits:
|
if qubit < 0 or qubit >= self.num_qubits:
|
||||||
raise IndexError("Qubit index out of range")
|
raise IndexError("Qubit index out of range")
|
||||||
# Build operator by Kronecker product: identity on other qubits, gate on target
|
|
||||||
op = 1
|
mask = 1 << (self.num_qubits - 1 - qubit)
|
||||||
for i in range(self.num_qubits):
|
new_state = self.state.copy()
|
||||||
if i == qubit:
|
for index in range(len(self.state)):
|
||||||
op = np.kron(op, gate)
|
if index & mask:
|
||||||
else:
|
continue # processed as the partner of an earlier index
|
||||||
op = np.kron(op, np.eye(2))
|
partner = index | mask
|
||||||
self.state = op @ self.state
|
amp0 = self.state[index]
|
||||||
|
amp1 = self.state[partner]
|
||||||
|
new_state[index] = gate[0][0] * amp0 + gate[0][1] * amp1
|
||||||
|
new_state[partner] = gate[1][0] * amp0 + gate[1][1] * amp1
|
||||||
|
self.state = new_state
|
||||||
|
|
||||||
def apply_hadamard(self, qubit: int) -> None:
|
def apply_hadamard(self, qubit: int) -> None:
|
||||||
"""Apply a Hadamard gate (H) to one qubit."""
|
"""Apply a Hadamard gate (H) to one qubit."""
|
||||||
|
|
||||||
self._apply_single_qubit_gate(self.H_GATE, qubit)
|
self._apply_single_qubit_gate(self.H_GATE, qubit)
|
||||||
|
|
||||||
def apply_pauli_x(self, qubit: int) -> None:
|
def apply_pauli_x(self, qubit: int) -> None:
|
||||||
"""Apply a Pauli‑X (NOT) gate to one qubit."""
|
"""Apply a Pauli-X (NOT) gate to one qubit."""
|
||||||
|
|
||||||
self._apply_single_qubit_gate(self.X_GATE, qubit)
|
self._apply_single_qubit_gate(self.X_GATE, qubit)
|
||||||
|
|
||||||
def apply_cnot(self, control: int, target: int) -> None:
|
def apply_cnot(self, control: int, target: int) -> None:
|
||||||
"""Apply a controlled‑NOT (CNOT) gate.
|
"""Apply a controlled-NOT (CNOT) gate.
|
||||||
|
|
||||||
The X gate is applied to the `target` qubit if and only if the
|
The X gate is applied to the ``target`` qubit if and only if the
|
||||||
`control` qubit is in state |1⟩.
|
``control`` qubit is in state |1⟩.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if control == target:
|
if control == target:
|
||||||
raise ValueError("Control and target must be different for CNOT")
|
raise ValueError("Control and target must be different for CNOT")
|
||||||
if any(q < 0 or q >= self.num_qubits for q in (control, target)):
|
if any(q < 0 or q >= self.num_qubits for q in (control, target)):
|
||||||
raise IndexError("Qubit index out of range")
|
raise IndexError("Qubit index out of range")
|
||||||
dimension = len(self.state)
|
|
||||||
new_state = np.zeros_like(self.state)
|
control_mask = 1 << (self.num_qubits - 1 - control)
|
||||||
# For each basis state, flip the target bit if the control bit is 1
|
target_mask = 1 << (self.num_qubits - 1 - target)
|
||||||
|
new_state = [0j] * len(self.state)
|
||||||
for index, amplitude in enumerate(self.state):
|
for index, amplitude in enumerate(self.state):
|
||||||
# Determine bit value of control qubit (most significant bit index 0)
|
if index & control_mask:
|
||||||
bit_val = (index >> (self.num_qubits - 1 - control)) & 1
|
new_index = index ^ target_mask
|
||||||
if bit_val == 1:
|
|
||||||
# Flip target bit using XOR mask
|
|
||||||
mask = 1 << (self.num_qubits - 1 - target)
|
|
||||||
new_index = index ^ mask
|
|
||||||
else:
|
else:
|
||||||
new_index = index
|
new_index = index
|
||||||
new_state[new_index] += amplitude
|
new_state[new_index] += amplitude
|
||||||
@@ -111,30 +121,32 @@ class QuantumCircuit:
|
|||||||
the state vector is collapsed (renormalized) consistent with the
|
the state vector is collapsed (renormalized) consistent with the
|
||||||
observed result.
|
observed result.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if qubit < 0 or qubit >= self.num_qubits:
|
if qubit < 0 or qubit >= self.num_qubits:
|
||||||
raise IndexError("Qubit index out of range")
|
raise IndexError("Qubit index out of range")
|
||||||
|
|
||||||
prob0 = 0.0
|
prob0 = 0.0
|
||||||
prob1 = 0.0
|
prob1 = 0.0
|
||||||
mask = 1 << (self.num_qubits - 1 - qubit)
|
mask = 1 << (self.num_qubits - 1 - qubit)
|
||||||
# Compute probabilities by summing squared amplitudes
|
|
||||||
for idx, amp in enumerate(self.state):
|
for idx, amp in enumerate(self.state):
|
||||||
if idx & mask:
|
if idx & mask:
|
||||||
prob1 += abs(amp) ** 2
|
prob1 += abs(amp) ** 2
|
||||||
else:
|
else:
|
||||||
prob0 += abs(amp) ** 2
|
prob0 += abs(amp) ** 2
|
||||||
# Sample outcome
|
|
||||||
rand = random.random()
|
rand = random.random()
|
||||||
outcome = 1 if rand < prob1 else 0
|
outcome = 1 if rand < prob1 else 0
|
||||||
# Collapse state
|
|
||||||
new_state = np.zeros_like(self.state)
|
new_state = [0j] * len(self.state)
|
||||||
for idx, amp in enumerate(self.state):
|
for idx, amp in enumerate(self.state):
|
||||||
bit = 1 if idx & mask else 0
|
bit = 1 if idx & mask else 0
|
||||||
if bit == outcome:
|
if bit == outcome:
|
||||||
new_state[idx] = amp
|
new_state[idx] = amp
|
||||||
# Renormalize
|
|
||||||
norm = math.sqrt(prob1 if outcome == 1 else prob0)
|
norm = math.sqrt(prob1 if outcome == 1 else prob0)
|
||||||
if norm > 0:
|
if norm > 0:
|
||||||
new_state /= norm
|
new_state = [amp / norm for amp in new_state]
|
||||||
|
|
||||||
self.state = new_state
|
self.state = new_state
|
||||||
return outcome
|
return outcome
|
||||||
|
|
||||||
@@ -142,16 +154,28 @@ class QuantumCircuit:
|
|||||||
"""Measure all qubits sequentially, returning a bitstring.
|
"""Measure all qubits sequentially, returning a bitstring.
|
||||||
|
|
||||||
The measurement outcomes are stored in the instance attribute
|
The measurement outcomes are stored in the instance attribute
|
||||||
`measurements` and returned as a concatenated string (qubit 0 first).
|
``measurements`` and returned as a concatenated string (qubit 0 first).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
self.measurements = []
|
self.measurements = []
|
||||||
bits: List[int] = []
|
bits: List[int] = []
|
||||||
for q in range(self.num_qubits):
|
for q in range(self.num_qubits):
|
||||||
bit = self.measure(q)
|
bit = self.measure(q)
|
||||||
bits.append(bit)
|
bits.append(bit)
|
||||||
self.measurements.append(bit)
|
self.measurements.append(bit)
|
||||||
return ''.join(str(b) for b in bits)
|
return "".join(str(b) for b in bits)
|
||||||
|
|
||||||
def statevector(self) -> np.ndarray:
|
def statevector(self) -> List[complex]:
|
||||||
"""Return a copy of the current state vector."""
|
"""Return a copy of the current state vector."""
|
||||||
|
|
||||||
return self.state.copy()
|
return self.state.copy()
|
||||||
|
|
||||||
|
def probabilities(self) -> List[float]:
|
||||||
|
"""Return measurement probabilities for each basis state."""
|
||||||
|
|
||||||
|
return [abs(amp) ** 2 for amp in self.state]
|
||||||
|
|
||||||
|
def amplitudes(self) -> Sequence[complex]:
|
||||||
|
"""Return a tuple view of the current amplitudes."""
|
||||||
|
|
||||||
|
return tuple(self.state)
|
||||||
|
|||||||
6
tests/conftest.py
Normal file
6
tests/conftest.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
import pathlib
|
||||||
|
import sys
|
||||||
|
|
||||||
|
PROJECT_ROOT = pathlib.Path(__file__).resolve().parents[1]
|
||||||
|
if str(PROJECT_ROOT) not in sys.path:
|
||||||
|
sys.path.insert(0, str(PROJECT_ROOT))
|
||||||
@@ -1,10 +1,10 @@
|
|||||||
import pathlib
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
from native_ai_quantum_energy.energy_simulator import (
|
||||||
|
battery_discharge,
|
||||||
|
simulate_particle_collision,
|
||||||
|
solar_panel_output,
|
||||||
|
)
|
||||||
|
|
||||||
sys.path.insert(0, str(pathlib.Path(__file__).resolve().parents[1]))
|
|
||||||
from native_ai_quantum_energy.energy_simulator import solar_panel_output
|
|
||||||
|
|
||||||
|
|
||||||
def test_solar_panel_output_valid():
|
def test_solar_panel_output_valid():
|
||||||
@@ -20,3 +20,24 @@ def test_solar_panel_output_negative_power():
|
|||||||
def test_solar_panel_output_negative_hours():
|
def test_solar_panel_output_negative_hours():
|
||||||
with pytest.raises(ValueError):
|
with pytest.raises(ValueError):
|
||||||
solar_panel_output(10, -1)
|
solar_panel_output(10, -1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_battery_discharge_partial_consumption():
|
||||||
|
remaining = battery_discharge(2000, 500, 2)
|
||||||
|
assert remaining == pytest.approx(2000 - 1000)
|
||||||
|
|
||||||
|
|
||||||
|
def test_battery_discharge_invalid_inputs():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
battery_discharge(-1, 100, 1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_particle_collision_conserves_symmetry():
|
||||||
|
v1_final, v2_final = simulate_particle_collision(1.0, 1.0, 1.0, -1.0)
|
||||||
|
assert v1_final == pytest.approx(-1.0)
|
||||||
|
assert v2_final == pytest.approx(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_particle_collision_requires_positive_mass():
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
simulate_particle_collision(0.0, 1.0, 1.0, -1.0)
|
||||||
|
|||||||
61
tests/test_quantum_simulator.py
Normal file
61
tests/test_quantum_simulator.py
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import math
|
||||||
|
import random
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from native_ai_quantum_energy.quantum_simulator import QuantumCircuit
|
||||||
|
|
||||||
|
|
||||||
|
def assert_complex_approx(value: complex, expected_real: float, expected_imag: float = 0.0) -> None:
|
||||||
|
assert value.real == pytest.approx(expected_real)
|
||||||
|
assert value.imag == pytest.approx(expected_imag)
|
||||||
|
|
||||||
|
|
||||||
|
def test_hadamard_creates_superposition():
|
||||||
|
qc = QuantumCircuit(1)
|
||||||
|
qc.apply_hadamard(0)
|
||||||
|
state = qc.statevector()
|
||||||
|
assert_complex_approx(state[0], 1 / math.sqrt(2))
|
||||||
|
assert_complex_approx(state[1], 1 / math.sqrt(2))
|
||||||
|
|
||||||
|
|
||||||
|
def test_pauli_x_flips_qubit():
|
||||||
|
qc = QuantumCircuit(1)
|
||||||
|
qc.apply_pauli_x(0)
|
||||||
|
state = qc.statevector()
|
||||||
|
assert_complex_approx(state[0], 0.0)
|
||||||
|
assert_complex_approx(state[1], 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cnot_entangles_qubits():
|
||||||
|
qc = QuantumCircuit(2)
|
||||||
|
qc.apply_hadamard(0)
|
||||||
|
qc.apply_cnot(0, 1)
|
||||||
|
probs = qc.probabilities()
|
||||||
|
assert probs[0] == pytest.approx(0.5, rel=1e-6)
|
||||||
|
assert probs[3] == pytest.approx(0.5, rel=1e-6)
|
||||||
|
assert probs[1] == pytest.approx(0.0, abs=1e-12)
|
||||||
|
assert probs[2] == pytest.approx(0.0, abs=1e-12)
|
||||||
|
|
||||||
|
|
||||||
|
def test_measure_collapses_state(monkeypatch):
|
||||||
|
qc = QuantumCircuit(1)
|
||||||
|
qc.apply_hadamard(0)
|
||||||
|
monkeypatch.setattr(random, "random", lambda: 0.25)
|
||||||
|
outcome = qc.measure(0)
|
||||||
|
assert outcome == 1
|
||||||
|
state = qc.statevector()
|
||||||
|
assert_complex_approx(state[0], 0.0)
|
||||||
|
assert_complex_approx(state[1], 1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_invalid_qubit_index():
|
||||||
|
qc = QuantumCircuit(1)
|
||||||
|
with pytest.raises(IndexError):
|
||||||
|
qc.apply_hadamard(2)
|
||||||
|
|
||||||
|
|
||||||
|
def test_cnot_requires_distinct_qubits():
|
||||||
|
qc = QuantumCircuit(2)
|
||||||
|
with pytest.raises(ValueError):
|
||||||
|
qc.apply_cnot(0, 0)
|
||||||
Reference in New Issue
Block a user