psiqit 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- psiqit-1.0.0/PKG-INFO +13 -0
- psiqit-1.0.0/README.md +0 -0
- psiqit-1.0.0/psiqit/__init__.py +19 -0
- psiqit-1.0.0/psiqit/algorithms/__init__.py +30 -0
- psiqit-1.0.0/psiqit/algorithms/bernstein_vazirani.py +97 -0
- psiqit-1.0.0/psiqit/algorithms/deutsch_jozsa.py +214 -0
- psiqit-1.0.0/psiqit/algorithms/grover.py +245 -0
- psiqit-1.0.0/psiqit/algorithms/qft.py +171 -0
- psiqit-1.0.0/psiqit/algorithms/qpe.py +92 -0
- psiqit-1.0.0/psiqit/algorithms/shor.py +176 -0
- psiqit-1.0.0/psiqit/algorithms/simon.py +149 -0
- psiqit-1.0.0/psiqit/circuits/__init__.py +14 -0
- psiqit-1.0.0/psiqit/circuits/circuit.py +411 -0
- psiqit-1.0.0/psiqit/circuits/optical_circuits.py +175 -0
- psiqit-1.0.0/psiqit/circuits/qubit.py +84 -0
- psiqit-1.0.0/psiqit/circuits/register.py +122 -0
- psiqit-1.0.0/psiqit/dynamics/__init__.py +32 -0
- psiqit-1.0.0/psiqit/dynamics/adiabatic.py +150 -0
- psiqit-1.0.0/psiqit/dynamics/heisenberg.py +85 -0
- psiqit-1.0.0/psiqit/dynamics/interaction.py +102 -0
- psiqit-1.0.0/psiqit/dynamics/lindblad.py +169 -0
- psiqit-1.0.0/psiqit/dynamics/monte_carlo.py +185 -0
- psiqit-1.0.0/psiqit/dynamics/schrodinger.py +395 -0
- psiqit-1.0.0/psiqit/dynamics/time_evolution.py +170 -0
- psiqit-1.0.0/psiqit/error_correction/__init__.py +22 -0
- psiqit-1.0.0/psiqit/error_correction/codes.py +293 -0
- psiqit-1.0.0/psiqit/info/__init__.py +26 -0
- psiqit-1.0.0/psiqit/info/entanglement.py +295 -0
- psiqit-1.0.0/psiqit/info/entropy.py +215 -0
- psiqit-1.0.0/psiqit/noise_canceling/__init__.py +28 -0
- psiqit-1.0.0/psiqit/noise_canceling/noise_models.py +285 -0
- psiqit-1.0.0/psiqit/qml/__init__.py +0 -0
- psiqit-1.0.0/psiqit/qml/qgan.py +306 -0
- psiqit-1.0.0/psiqit/qml/qnn.py +308 -0
- psiqit-1.0.0/psiqit/qml/qsvm.py +339 -0
- psiqit-1.0.0/psiqit/qml/quantum_kernel.py +227 -0
- psiqit-1.0.0/psiqit/qml/vqc.py +473 -0
- psiqit-1.0.0/psiqit/quantum/__init__.py +30 -0
- psiqit-1.0.0/psiqit/quantum/interference.py +259 -0
- psiqit-1.0.0/psiqit/quantum/measurement.py +544 -0
- psiqit-1.0.0/psiqit/quantum/operator.py +382 -0
- psiqit-1.0.0/psiqit/quantum/parties.py +249 -0
- psiqit-1.0.0/psiqit/quantum/state.py +546 -0
- psiqit-1.0.0/psiqit/setup.py +14 -0
- psiqit-1.0.0/psiqit/version.py +160 -0
- psiqit-1.0.0/psiqit.egg-info/PKG-INFO +13 -0
- psiqit-1.0.0/psiqit.egg-info/SOURCES.txt +87 -0
- psiqit-1.0.0/psiqit.egg-info/dependency_links.txt +1 -0
- psiqit-1.0.0/psiqit.egg-info/requires.txt +2 -0
- psiqit-1.0.0/psiqit.egg-info/top_level.txt +1 -0
- psiqit-1.0.0/pyproject.toml +18 -0
- psiqit-1.0.0/setup.cfg +4 -0
- psiqit-1.0.0/setup.py +14 -0
- psiqit-1.0.0/tests/test1.py +44 -0
- psiqit-1.0.0/tests/test10.py +164 -0
- psiqit-1.0.0/tests/test11.py +194 -0
- psiqit-1.0.0/tests/test12.py +257 -0
- psiqit-1.0.0/tests/test13.py +211 -0
- psiqit-1.0.0/tests/test14.py +234 -0
- psiqit-1.0.0/tests/test15.py +226 -0
- psiqit-1.0.0/tests/test16.py +191 -0
- psiqit-1.0.0/tests/test17.py +179 -0
- psiqit-1.0.0/tests/test18.py +225 -0
- psiqit-1.0.0/tests/test19.py +272 -0
- psiqit-1.0.0/tests/test2.py +215 -0
- psiqit-1.0.0/tests/test20.py +217 -0
- psiqit-1.0.0/tests/test21.py +174 -0
- psiqit-1.0.0/tests/test22.py +125 -0
- psiqit-1.0.0/tests/test23.py +28 -0
- psiqit-1.0.0/tests/test24.py +28 -0
- psiqit-1.0.0/tests/test25.py +46 -0
- psiqit-1.0.0/tests/test26.py +126 -0
- psiqit-1.0.0/tests/test27.py +89 -0
- psiqit-1.0.0/tests/test28.py +60 -0
- psiqit-1.0.0/tests/test29.py +139 -0
- psiqit-1.0.0/tests/test3.py +122 -0
- psiqit-1.0.0/tests/test30.py +193 -0
- psiqit-1.0.0/tests/test31.py +172 -0
- psiqit-1.0.0/tests/test32.py +166 -0
- psiqit-1.0.0/tests/test33.py +251 -0
- psiqit-1.0.0/tests/test34.py +107 -0
- psiqit-1.0.0/tests/test35.py +30 -0
- psiqit-1.0.0/tests/test36.py +61 -0
- psiqit-1.0.0/tests/test4.py +321 -0
- psiqit-1.0.0/tests/test5.py +38 -0
- psiqit-1.0.0/tests/test6.py +181 -0
- psiqit-1.0.0/tests/test7.py +183 -0
- psiqit-1.0.0/tests/test8.py +155 -0
- psiqit-1.0.0/tests/test9.py +138 -0
psiqit-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: psiqit
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Quantum Information Toolkit
|
|
5
|
+
Author: mahdi azad marzabadi , Dr.mahdi mirzaee , Dr.karim ghorbani
|
|
6
|
+
Author-email: QIT Development Team <qit@example.com>
|
|
7
|
+
License-Expression: MIT
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: numpy>=1.21.0
|
|
11
|
+
Requires-Dist: scipy>=1.7.0
|
|
12
|
+
Dynamic: author
|
|
13
|
+
Dynamic: requires-python
|
psiqit-1.0.0/README.md
ADDED
|
File without changes
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""
|
|
2
|
+
psyqit - Quantum Information Toolkit
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
__version__ = "0.1.0"
|
|
6
|
+
__author__ = "mahdi azadmarzabadi , Dr.mahdi mirzaee , Dr.karim ghorbani"
|
|
7
|
+
|
|
8
|
+
# Core exports
|
|
9
|
+
from .quantum.state import Ket, Bra, zero, one, plus, minus
|
|
10
|
+
from .quantum.operator import hadamard, pauli_x, pauli_y, pauli_z, cnot
|
|
11
|
+
from .circuits.circuit import QuantumCircuit
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
'__version__',
|
|
15
|
+
'__author__',
|
|
16
|
+
'Ket', 'Bra', 'zero', 'one', 'plus', 'minus',
|
|
17
|
+
'hadamard', 'pauli_x', 'pauli_y', 'pauli_z', 'cnot',
|
|
18
|
+
'QuantumCircuit',
|
|
19
|
+
]
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qit/algorithms/__init__.py
|
|
3
|
+
Quantum algorithms module
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from .grover import Grover, grover_search, grover_search_multiple
|
|
7
|
+
from .deutsch_jozsa import (
|
|
8
|
+
DeutschJozsaResult,
|
|
9
|
+
DeutschJozsa,
|
|
10
|
+
DeutschAlgorithm,
|
|
11
|
+
deutsch_jozsa_constant,
|
|
12
|
+
deutsch_jozsa_balanced,
|
|
13
|
+
deutsch_algorithm_constant,
|
|
14
|
+
deutsch_algorithm_balanced,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
__all__ = [
|
|
18
|
+
# Grover
|
|
19
|
+
'Grover',
|
|
20
|
+
'grover_search',
|
|
21
|
+
'grover_search_multiple',
|
|
22
|
+
# Deutsch-Jozsa
|
|
23
|
+
'DeutschJozsaResult',
|
|
24
|
+
'DeutschJozsa',
|
|
25
|
+
'DeutschAlgorithm',
|
|
26
|
+
'deutsch_jozsa_constant',
|
|
27
|
+
'deutsch_jozsa_balanced',
|
|
28
|
+
'deutsch_algorithm_constant',
|
|
29
|
+
'deutsch_algorithm_balanced',
|
|
30
|
+
]
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qit/algorithms/bernstein_vazirani.py
|
|
3
|
+
Bernstein-Vazirani Algorithm - Fixed
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from typing import Callable, List, Optional
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
|
|
9
|
+
from ..circuits.circuit import QuantumCircuit
|
|
10
|
+
from ..quantum.state import Ket, basis
|
|
11
|
+
from ..quantum.operator import hadamard, cnot, pauli_x
|
|
12
|
+
from ..quantum.measurement import measure
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass
|
|
16
|
+
class BernsteinVaziraniResult:
|
|
17
|
+
hidden_string: int
|
|
18
|
+
n_qubits: int
|
|
19
|
+
success: bool
|
|
20
|
+
|
|
21
|
+
def __str__(self) -> str:
|
|
22
|
+
bits = format(self.hidden_string, f'0{self.n_qubits}b')
|
|
23
|
+
return f"Hidden string: {bits} (decimal: {self.hidden_string})"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class BernsteinVazirani:
|
|
27
|
+
"""
|
|
28
|
+
Bernstein-Vazirani algorithm for finding hidden bit string.
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self, n_qubits: int):
|
|
32
|
+
self.n = n_qubits
|
|
33
|
+
self.total_qubits = n_qubits + 1
|
|
34
|
+
|
|
35
|
+
def build_circuit(self, oracle: Callable) -> QuantumCircuit:
|
|
36
|
+
"""Build Bernstein-Vazirani circuit."""
|
|
37
|
+
circuit = QuantumCircuit(self.total_qubits)
|
|
38
|
+
|
|
39
|
+
# Initialize ancilla to |1⟩
|
|
40
|
+
circuit.x(self.n)
|
|
41
|
+
|
|
42
|
+
# Apply Hadamard to all qubits
|
|
43
|
+
for i in range(self.total_qubits):
|
|
44
|
+
circuit.h(i)
|
|
45
|
+
|
|
46
|
+
# Apply oracle (simplified - just CNOTs)
|
|
47
|
+
for i in range(self.n):
|
|
48
|
+
circuit.cx(i, self.n)
|
|
49
|
+
|
|
50
|
+
# Apply Hadamard to input qubits
|
|
51
|
+
for i in range(self.n):
|
|
52
|
+
circuit.h(i)
|
|
53
|
+
|
|
54
|
+
return circuit
|
|
55
|
+
|
|
56
|
+
def run(self) -> BernsteinVaziraniResult:
|
|
57
|
+
"""Run Bernstein-Vazirani algorithm."""
|
|
58
|
+
circuit = self.build_circuit(None)
|
|
59
|
+
state = circuit.run()
|
|
60
|
+
|
|
61
|
+
# Measure input qubits
|
|
62
|
+
measurements = []
|
|
63
|
+
for i in range(self.n):
|
|
64
|
+
prob_0 = 0
|
|
65
|
+
dim = state.dim
|
|
66
|
+
for j in range(dim):
|
|
67
|
+
bit = (j >> (self.total_qubits - 1 - i)) & 1
|
|
68
|
+
if bit == 0:
|
|
69
|
+
prob_0 += abs(state.data[j]) ** 2
|
|
70
|
+
measurements.append(0 if prob_0 > 0.5 else 1)
|
|
71
|
+
|
|
72
|
+
# Convert to integer
|
|
73
|
+
hidden_string = 0
|
|
74
|
+
for i, bit in enumerate(measurements):
|
|
75
|
+
hidden_string = (hidden_string << 1) | bit
|
|
76
|
+
|
|
77
|
+
return BernsteinVaziraniResult(
|
|
78
|
+
hidden_string=hidden_string,
|
|
79
|
+
n_qubits=self.n,
|
|
80
|
+
success=True
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def bernstein_vazirani(n_qubits: int) -> int:
|
|
85
|
+
"""
|
|
86
|
+
Convenience function for Bernstein-Vazirani algorithm.
|
|
87
|
+
"""
|
|
88
|
+
bv = BernsteinVazirani(n_qubits)
|
|
89
|
+
result = bv.run()
|
|
90
|
+
return result.hidden_string
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
__all__ = [
|
|
94
|
+
'BernsteinVaziraniResult',
|
|
95
|
+
'BernsteinVazirani',
|
|
96
|
+
'bernstein_vazirani',
|
|
97
|
+
]
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qit/algorithms/deutsch_jozsa.py
|
|
3
|
+
Deutsch-Jozsa Algorithm - Fixed with circuit.measure
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
from typing import List, Optional, Union, Callable, Dict
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
from ..circuits.circuit import QuantumCircuit
|
|
11
|
+
from ..quantum.state import Ket, zero, one, basis
|
|
12
|
+
from ..quantum.operator import hadamard, pauli_x, cnot, toffoli
|
|
13
|
+
from ..quantum.measurement import measure
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class DeutschJozsaResult:
|
|
18
|
+
is_constant: bool
|
|
19
|
+
is_balanced: bool
|
|
20
|
+
measurement_result: int
|
|
21
|
+
n_qubits: int
|
|
22
|
+
oracle_type: str
|
|
23
|
+
confidence: float = 1.0
|
|
24
|
+
|
|
25
|
+
def __str__(self) -> str:
|
|
26
|
+
if self.is_constant:
|
|
27
|
+
return f"Function is CONSTANT (f(x) = 0 for all x)"
|
|
28
|
+
else:
|
|
29
|
+
return f"Function is BALANCED (exactly half 0s, half 1s)"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class DeutschJozsa:
|
|
33
|
+
"""
|
|
34
|
+
Deutsch-Jozsa algorithm implementation.
|
|
35
|
+
"""
|
|
36
|
+
|
|
37
|
+
def __init__(self, n_qubits: int,
|
|
38
|
+
oracle_type: str = 'constant',
|
|
39
|
+
constant_value: int = 0,
|
|
40
|
+
custom_oracle: Optional[Callable] = None):
|
|
41
|
+
|
|
42
|
+
self.n_qubits = n_qubits
|
|
43
|
+
self.oracle_type = oracle_type
|
|
44
|
+
self.constant_value = constant_value
|
|
45
|
+
|
|
46
|
+
if custom_oracle:
|
|
47
|
+
self.oracle = custom_oracle
|
|
48
|
+
self.oracle_type = 'custom'
|
|
49
|
+
else:
|
|
50
|
+
self.oracle = self._build_oracle()
|
|
51
|
+
|
|
52
|
+
def _build_oracle(self) -> Callable:
|
|
53
|
+
"""Build the oracle for the Deutsch-Jozsa algorithm."""
|
|
54
|
+
|
|
55
|
+
def constant_oracle(circuit: QuantumCircuit):
|
|
56
|
+
"""Constant oracle: f(x) = constant_value for all x."""
|
|
57
|
+
if self.constant_value == 1:
|
|
58
|
+
circuit.x(self.n_qubits)
|
|
59
|
+
|
|
60
|
+
def balanced_oracle(circuit: QuantumCircuit):
|
|
61
|
+
"""Balanced oracle: f(x) = x_0 (first bit determines output)"""
|
|
62
|
+
circuit.cx(0, self.n_qubits)
|
|
63
|
+
|
|
64
|
+
if self.oracle_type == 'constant':
|
|
65
|
+
return constant_oracle
|
|
66
|
+
elif self.oracle_type == 'balanced':
|
|
67
|
+
return balanced_oracle
|
|
68
|
+
else:
|
|
69
|
+
raise ValueError(f"Unknown oracle type: {self.oracle_type}")
|
|
70
|
+
|
|
71
|
+
def build_circuit(self) -> QuantumCircuit:
|
|
72
|
+
"""Build the complete Deutsch-Jozsa circuit."""
|
|
73
|
+
total_qubits = self.n_qubits + 1
|
|
74
|
+
circuit = QuantumCircuit(total_qubits)
|
|
75
|
+
|
|
76
|
+
# Initialize ancilla qubit to |1⟩
|
|
77
|
+
circuit.x(self.n_qubits)
|
|
78
|
+
|
|
79
|
+
# Apply Hadamard to all qubits
|
|
80
|
+
for i in range(total_qubits):
|
|
81
|
+
circuit.h(i)
|
|
82
|
+
|
|
83
|
+
# Apply oracle
|
|
84
|
+
self.oracle(circuit)
|
|
85
|
+
|
|
86
|
+
# Apply Hadamard to input qubits only
|
|
87
|
+
for i in range(self.n_qubits):
|
|
88
|
+
circuit.h(i)
|
|
89
|
+
|
|
90
|
+
return circuit
|
|
91
|
+
|
|
92
|
+
def run(self, shots: int = 100) -> DeutschJozsaResult:
|
|
93
|
+
"""
|
|
94
|
+
Run the Deutsch-Jozsa algorithm.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
shots: Number of measurements (higher = more accurate)
|
|
98
|
+
"""
|
|
99
|
+
circuit = self.build_circuit()
|
|
100
|
+
|
|
101
|
+
# Measure all qubits
|
|
102
|
+
results = circuit.measure(shots=shots)
|
|
103
|
+
|
|
104
|
+
# For Deutsch-Jozsa, we only care about the input qubits
|
|
105
|
+
# We check if all measurements of input qubits are 0
|
|
106
|
+
n = self.n_qubits
|
|
107
|
+
|
|
108
|
+
# Count how many measurements have all input qubits = 0
|
|
109
|
+
zero_count = 0
|
|
110
|
+
for bitstring, count in results.items():
|
|
111
|
+
# Check if first n bits are all '0'
|
|
112
|
+
if bitstring[:n] == '0' * n:
|
|
113
|
+
zero_count += count
|
|
114
|
+
|
|
115
|
+
# If more than 50% of measurements are all zeros, function is constant
|
|
116
|
+
# (In theory, it should be 100% for constant, 0% for balanced)
|
|
117
|
+
is_constant = (zero_count / shots) > 0.5
|
|
118
|
+
|
|
119
|
+
return DeutschJozsaResult(
|
|
120
|
+
is_constant=is_constant,
|
|
121
|
+
is_balanced=not is_constant,
|
|
122
|
+
measurement_result=0 if is_constant else 1,
|
|
123
|
+
n_qubits=self.n_qubits,
|
|
124
|
+
oracle_type=self.oracle_type,
|
|
125
|
+
confidence=zero_count / shots
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
# ============================================================
|
|
130
|
+
# Simplified version for 1-qubit (Deutsch algorithm)
|
|
131
|
+
# ============================================================
|
|
132
|
+
|
|
133
|
+
class DeutschAlgorithm:
|
|
134
|
+
"""Simplified Deutsch algorithm (1-qubit version)."""
|
|
135
|
+
|
|
136
|
+
def __init__(self, oracle_type: str = 'constant', constant_value: int = 0):
|
|
137
|
+
self.oracle_type = oracle_type
|
|
138
|
+
self.constant_value = constant_value
|
|
139
|
+
|
|
140
|
+
def _apply_oracle(self, circuit: QuantumCircuit):
|
|
141
|
+
"""Apply 1-qubit oracle."""
|
|
142
|
+
if self.oracle_type == 'constant':
|
|
143
|
+
if self.constant_value == 1:
|
|
144
|
+
circuit.x(1)
|
|
145
|
+
else: # balanced
|
|
146
|
+
circuit.cx(0, 1)
|
|
147
|
+
|
|
148
|
+
def build_circuit(self) -> QuantumCircuit:
|
|
149
|
+
"""Build Deutsch circuit."""
|
|
150
|
+
circuit = QuantumCircuit(2)
|
|
151
|
+
|
|
152
|
+
circuit.x(1)
|
|
153
|
+
circuit.h(0)
|
|
154
|
+
circuit.h(1)
|
|
155
|
+
self._apply_oracle(circuit)
|
|
156
|
+
circuit.h(0)
|
|
157
|
+
|
|
158
|
+
return circuit
|
|
159
|
+
|
|
160
|
+
def run(self, shots: int = 100) -> Dict:
|
|
161
|
+
"""Run Deutsch algorithm."""
|
|
162
|
+
circuit = self.build_circuit()
|
|
163
|
+
results = circuit.measure(shots=shots)
|
|
164
|
+
|
|
165
|
+
# Count measurements where first qubit is 0
|
|
166
|
+
zero_count = 0
|
|
167
|
+
for bitstring, count in results.items():
|
|
168
|
+
if bitstring[0] == '0':
|
|
169
|
+
zero_count += count
|
|
170
|
+
|
|
171
|
+
is_constant = (zero_count / shots) > 0.5
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
'is_constant': is_constant,
|
|
175
|
+
'is_balanced': not is_constant,
|
|
176
|
+
'measurement': '0' if is_constant else '1',
|
|
177
|
+
'oracle_type': self.oracle_type,
|
|
178
|
+
'confidence': zero_count / shots if is_constant else (shots - zero_count) / shots
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
|
|
182
|
+
# ============================================================
|
|
183
|
+
# Convenience Functions
|
|
184
|
+
# ============================================================
|
|
185
|
+
|
|
186
|
+
def deutsch_jozsa_constant(n_qubits: int = 3, constant_value: int = 0, shots: int = 100) -> DeutschJozsaResult:
|
|
187
|
+
dj = DeutschJozsa(n_qubits, oracle_type='constant', constant_value=constant_value)
|
|
188
|
+
return dj.run(shots)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def deutsch_jozsa_balanced(n_qubits: int = 3, shots: int = 100) -> DeutschJozsaResult:
|
|
192
|
+
dj = DeutschJozsa(n_qubits, oracle_type='balanced')
|
|
193
|
+
return dj.run(shots)
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def deutsch_algorithm_constant(constant_value: int = 0, shots: int = 100) -> Dict:
|
|
197
|
+
da = DeutschAlgorithm(oracle_type='constant', constant_value=constant_value)
|
|
198
|
+
return da.run(shots)
|
|
199
|
+
|
|
200
|
+
|
|
201
|
+
def deutsch_algorithm_balanced(shots: int = 100) -> Dict:
|
|
202
|
+
da = DeutschAlgorithm(oracle_type='balanced')
|
|
203
|
+
return da.run(shots)
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
__all__ = [
|
|
207
|
+
'DeutschJozsaResult',
|
|
208
|
+
'DeutschJozsa',
|
|
209
|
+
'DeutschAlgorithm',
|
|
210
|
+
'deutsch_jozsa_constant',
|
|
211
|
+
'deutsch_jozsa_balanced',
|
|
212
|
+
'deutsch_algorithm_constant',
|
|
213
|
+
'deutsch_algorithm_balanced',
|
|
214
|
+
]
|
|
@@ -0,0 +1,245 @@
|
|
|
1
|
+
"""
|
|
2
|
+
qit/algorithms/grover.py
|
|
3
|
+
Grover's Search Algorithm - Complete Implementation
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import math
|
|
7
|
+
import random
|
|
8
|
+
from typing import List, Optional, Union, Dict
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
|
|
11
|
+
from ..circuits.circuit import QuantumCircuit
|
|
12
|
+
from ..quantum.state import Ket, basis
|
|
13
|
+
from ..quantum.operator import hadamard, pauli_z, cnot, toffoli, cz
|
|
14
|
+
from ..quantum.measurement import measure
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
@dataclass
|
|
18
|
+
class GroverResult:
|
|
19
|
+
counts: Dict[str, int]
|
|
20
|
+
most_likely: int
|
|
21
|
+
marked_states: List[int]
|
|
22
|
+
success: bool
|
|
23
|
+
n_iterations: int
|
|
24
|
+
theoretical_prob: float
|
|
25
|
+
|
|
26
|
+
def __str__(self) -> str:
|
|
27
|
+
return (f"GroverResult(most_likely={self.most_likely}, "
|
|
28
|
+
f"success={self.success}, iterations={self.n_iterations})")
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Grover:
|
|
32
|
+
"""
|
|
33
|
+
Grover's search algorithm implementation.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, n_qubits: int, marked_states: Union[int, List[int]]):
|
|
37
|
+
self.n_qubits = n_qubits
|
|
38
|
+
self.N = 2 ** n_qubits
|
|
39
|
+
|
|
40
|
+
if isinstance(marked_states, int):
|
|
41
|
+
self.marked_states = [marked_states]
|
|
42
|
+
else:
|
|
43
|
+
self.marked_states = marked_states
|
|
44
|
+
|
|
45
|
+
self.M = len(self.marked_states)
|
|
46
|
+
|
|
47
|
+
# Calculate optimal iterations
|
|
48
|
+
if self.M <= 0 or self.M >= self.N:
|
|
49
|
+
self.n_iterations = 0
|
|
50
|
+
else:
|
|
51
|
+
theta = math.asin(math.sqrt(self.M / self.N))
|
|
52
|
+
self.n_iterations = max(1, int(round((math.pi / (4 * theta)) - 0.5)))
|
|
53
|
+
|
|
54
|
+
def _get_binary(self, state: int) -> str:
|
|
55
|
+
"""Convert state index to binary string."""
|
|
56
|
+
return format(state, f'0{self.n_qubits}b')
|
|
57
|
+
|
|
58
|
+
def build_circuit(self) -> QuantumCircuit:
|
|
59
|
+
"""
|
|
60
|
+
Build the complete Grover circuit.
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
QuantumCircuit: The Grover circuit
|
|
64
|
+
"""
|
|
65
|
+
circuit = QuantumCircuit(self.n_qubits)
|
|
66
|
+
|
|
67
|
+
# Initialize in uniform superposition
|
|
68
|
+
for i in range(self.n_qubits):
|
|
69
|
+
circuit.h(i)
|
|
70
|
+
|
|
71
|
+
# Apply Grover iterations
|
|
72
|
+
for _ in range(self.n_iterations):
|
|
73
|
+
# Oracle (simplified for now)
|
|
74
|
+
for target in self.marked_states:
|
|
75
|
+
binary = self._get_binary(target)
|
|
76
|
+
for i, bit in enumerate(binary):
|
|
77
|
+
if bit == '0':
|
|
78
|
+
circuit.x(i)
|
|
79
|
+
circuit.cz(0, 1) # Simplified multi-controlled Z
|
|
80
|
+
for i, bit in enumerate(binary):
|
|
81
|
+
if bit == '0':
|
|
82
|
+
circuit.x(i)
|
|
83
|
+
|
|
84
|
+
# Diffuser
|
|
85
|
+
for i in range(self.n_qubits):
|
|
86
|
+
circuit.h(i)
|
|
87
|
+
for i in range(self.n_qubits):
|
|
88
|
+
circuit.x(i)
|
|
89
|
+
circuit.cz(0, 1)
|
|
90
|
+
for i in range(self.n_qubits):
|
|
91
|
+
circuit.x(i)
|
|
92
|
+
for i in range(self.n_qubits):
|
|
93
|
+
circuit.h(i)
|
|
94
|
+
|
|
95
|
+
return circuit
|
|
96
|
+
|
|
97
|
+
def get_probabilities(self) -> List[float]:
|
|
98
|
+
"""
|
|
99
|
+
Calculate theoretical probabilities for each state.
|
|
100
|
+
|
|
101
|
+
Returns:
|
|
102
|
+
List of probabilities
|
|
103
|
+
"""
|
|
104
|
+
probs = [0.0] * self.N
|
|
105
|
+
|
|
106
|
+
if self.M == 0 or self.M == self.N:
|
|
107
|
+
return [1.0 / self.N] * self.N
|
|
108
|
+
|
|
109
|
+
theta = math.asin(math.sqrt(self.M / self.N))
|
|
110
|
+
prob_marked = math.sin((2 * self.n_iterations + 1) * theta) ** 2
|
|
111
|
+
prob_unmarked = (1 - prob_marked) / (self.N - self.M) if self.N > self.M else 0
|
|
112
|
+
|
|
113
|
+
for state in self.marked_states:
|
|
114
|
+
probs[state] = prob_marked / self.M
|
|
115
|
+
for i in range(self.N):
|
|
116
|
+
if i not in self.marked_states:
|
|
117
|
+
probs[i] = prob_unmarked
|
|
118
|
+
|
|
119
|
+
return probs
|
|
120
|
+
|
|
121
|
+
def _apply_gate_to_state(self, state: Ket, gate_matrix, qubits: List[int]) -> Ket:
|
|
122
|
+
"""Apply a gate to specific qubits."""
|
|
123
|
+
n = self.n_qubits
|
|
124
|
+
dim = state.dim
|
|
125
|
+
new_data = [0j] * dim
|
|
126
|
+
|
|
127
|
+
for i in range(dim):
|
|
128
|
+
bits = [(i >> (n - 1 - k)) & 1 for k in range(n)]
|
|
129
|
+
|
|
130
|
+
if len(qubits) == 1:
|
|
131
|
+
q = qubits[0]
|
|
132
|
+
row = bits[q]
|
|
133
|
+
for j in range(2):
|
|
134
|
+
new_bits = bits.copy()
|
|
135
|
+
new_bits[q] = j
|
|
136
|
+
new_idx = 0
|
|
137
|
+
for k, b in enumerate(new_bits):
|
|
138
|
+
new_idx = (new_idx << 1) | b
|
|
139
|
+
new_data[new_idx] += gate_matrix[row][j] * state.data[i]
|
|
140
|
+
|
|
141
|
+
elif len(qubits) == 2 and len(gate_matrix) == 4:
|
|
142
|
+
q1, q2 = qubits
|
|
143
|
+
row = (bits[q1] << 1) | bits[q2]
|
|
144
|
+
for j1 in range(2):
|
|
145
|
+
for j2 in range(2):
|
|
146
|
+
col = (j1 << 1) | j2
|
|
147
|
+
new_bits = bits.copy()
|
|
148
|
+
new_bits[q1] = j1
|
|
149
|
+
new_bits[q2] = j2
|
|
150
|
+
new_idx = 0
|
|
151
|
+
for k, b in enumerate(new_bits):
|
|
152
|
+
new_idx = (new_idx << 1) | b
|
|
153
|
+
new_data[new_idx] += gate_matrix[row][col] * state.data[i]
|
|
154
|
+
|
|
155
|
+
return Ket(new_data)
|
|
156
|
+
|
|
157
|
+
def _oracle(self, state: Ket) -> Ket:
|
|
158
|
+
"""Apply phase oracle to mark target states."""
|
|
159
|
+
new_data = state.data.copy()
|
|
160
|
+
for target in self.marked_states:
|
|
161
|
+
new_data[target] = -new_data[target]
|
|
162
|
+
return Ket(new_data)
|
|
163
|
+
|
|
164
|
+
def _diffuser(self, state: Ket) -> Ket:
|
|
165
|
+
"""Apply Grover diffusion operator."""
|
|
166
|
+
n = self.N
|
|
167
|
+
avg = sum(state.data) / n
|
|
168
|
+
new_data = [2 * avg - a for a in state.data]
|
|
169
|
+
return Ket(new_data)
|
|
170
|
+
|
|
171
|
+
def run(self, shots: int = 1024) -> GroverResult:
|
|
172
|
+
"""Run Grover's algorithm."""
|
|
173
|
+
# Start from |0...0⟩
|
|
174
|
+
state = basis(self.N, 0)
|
|
175
|
+
|
|
176
|
+
# Apply Hadamard to all qubits
|
|
177
|
+
from ..quantum.operator import hadamard
|
|
178
|
+
H = hadamard()
|
|
179
|
+
for i in range(self.n_qubits):
|
|
180
|
+
state = self._apply_gate_to_state(state, H.data, [i])
|
|
181
|
+
|
|
182
|
+
# Apply Grover iterations
|
|
183
|
+
for _ in range(self.n_iterations):
|
|
184
|
+
state = self._oracle(state)
|
|
185
|
+
state = self._diffuser(state)
|
|
186
|
+
|
|
187
|
+
# Measure
|
|
188
|
+
probs = [abs(a)**2 for a in state.data]
|
|
189
|
+
total = sum(probs)
|
|
190
|
+
probs = [p / total for p in probs]
|
|
191
|
+
|
|
192
|
+
outcomes = []
|
|
193
|
+
for _ in range(shots):
|
|
194
|
+
r = random.random()
|
|
195
|
+
cumsum = 0
|
|
196
|
+
for i, p in enumerate(probs):
|
|
197
|
+
cumsum += p
|
|
198
|
+
if r < cumsum:
|
|
199
|
+
outcomes.append(i)
|
|
200
|
+
break
|
|
201
|
+
|
|
202
|
+
outcome_strings = [format(o, f'0{self.n_qubits}b') for o in outcomes]
|
|
203
|
+
|
|
204
|
+
counts = {}
|
|
205
|
+
for s in outcome_strings:
|
|
206
|
+
counts[s] = counts.get(s, 0) + 1
|
|
207
|
+
|
|
208
|
+
most_likely = max(counts, key=counts.get) if counts else None
|
|
209
|
+
most_likely_int = int(most_likely, 2) if most_likely else None
|
|
210
|
+
|
|
211
|
+
# Theoretical probability
|
|
212
|
+
if self.M > 0 and self.M < self.N:
|
|
213
|
+
theta = math.asin(math.sqrt(self.M / self.N))
|
|
214
|
+
prob_marked = math.sin((2 * self.n_iterations + 1) * theta) ** 2
|
|
215
|
+
else:
|
|
216
|
+
prob_marked = 1.0 if self.M == self.N else 0.0
|
|
217
|
+
|
|
218
|
+
success = most_likely_int in self.marked_states if most_likely_int is not None else False
|
|
219
|
+
|
|
220
|
+
return GroverResult(
|
|
221
|
+
counts=counts,
|
|
222
|
+
most_likely=most_likely_int,
|
|
223
|
+
marked_states=self.marked_states,
|
|
224
|
+
success=success,
|
|
225
|
+
n_iterations=self.n_iterations,
|
|
226
|
+
theoretical_prob=prob_marked
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
def grover_search(n_qubits: int, target: int, shots: int = 1024) -> GroverResult:
|
|
231
|
+
grover = Grover(n_qubits, target)
|
|
232
|
+
return grover.run(shots)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def grover_search_multiple(n_qubits: int, targets: List[int], shots: int = 1024) -> GroverResult:
|
|
236
|
+
grover = Grover(n_qubits, targets)
|
|
237
|
+
return grover.run(shots)
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
__all__ = [
|
|
241
|
+
'GroverResult',
|
|
242
|
+
'Grover',
|
|
243
|
+
'grover_search',
|
|
244
|
+
'grover_search_multiple',
|
|
245
|
+
]
|