QuPRS 0.1.0__py3-none-any.whl
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.
- QuPRS/__init__.py +12 -0
- QuPRS/_version.py +21 -0
- QuPRS/cache_manager/__init__.py +5 -0
- QuPRS/cache_manager/cache_decorator.py +18 -0
- QuPRS/cache_manager/global_cache_manager.py +24 -0
- QuPRS/config.py +14 -0
- QuPRS/interface/__init__.py +0 -0
- QuPRS/interface/gate_library.py +26 -0
- QuPRS/interface/load_qiskit.py +225 -0
- QuPRS/interface/ps2wmc.py +252 -0
- QuPRS/interface/qasm2qc.py +68 -0
- QuPRS/pathsum.py +664 -0
- QuPRS/utils/Qiskit_Circuit_Utils.py +471 -0
- QuPRS/utils/__init__.py +6 -0
- QuPRS/utils/gpmc +0 -0
- QuPRS/utils/reduction.py +104 -0
- QuPRS/utils/strategy.py +176 -0
- QuPRS/utils/util.py +184 -0
- quprs-0.1.0.dist-info/METADATA +215 -0
- quprs-0.1.0.dist-info/RECORD +24 -0
- quprs-0.1.0.dist-info/WHEEL +5 -0
- quprs-0.1.0.dist-info/licenses/LICENSE.md +10 -0
- quprs-0.1.0.dist-info/licenses/NOTICE.md +228 -0
- quprs-0.1.0.dist-info/top_level.txt +1 -0
QuPRS/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# __init__.py
|
|
2
|
+
|
|
3
|
+
try:
|
|
4
|
+
from ._version import *
|
|
5
|
+
except ImportError:
|
|
6
|
+
# Fallback for development mode or when setuptools_scm hasn't generated the file yet
|
|
7
|
+
import warnings
|
|
8
|
+
warnings.warn("Version not found in _version.py, likely in development mode or during sdist build.", stacklevel=2)
|
|
9
|
+
__version__ = "0.0.0+unknown"
|
|
10
|
+
__version_tuple__ = (0, 0, 0, "unknown")
|
|
11
|
+
version = __version__
|
|
12
|
+
version_tuple = __version_tuple__
|
QuPRS/_version.py
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# file generated by setuptools-scm
|
|
2
|
+
# don't change, don't track in version control
|
|
3
|
+
|
|
4
|
+
__all__ = ["__version__", "__version_tuple__", "version", "version_tuple"]
|
|
5
|
+
|
|
6
|
+
TYPE_CHECKING = False
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from typing import Tuple
|
|
9
|
+
from typing import Union
|
|
10
|
+
|
|
11
|
+
VERSION_TUPLE = Tuple[Union[int, str], ...]
|
|
12
|
+
else:
|
|
13
|
+
VERSION_TUPLE = object
|
|
14
|
+
|
|
15
|
+
version: str
|
|
16
|
+
__version__: str
|
|
17
|
+
__version_tuple__: VERSION_TUPLE
|
|
18
|
+
version_tuple: VERSION_TUPLE
|
|
19
|
+
|
|
20
|
+
__version__ = version = '0.1.0'
|
|
21
|
+
__version_tuple__ = version_tuple = (0, 1, 0)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# cache_manager/cache_decorator.py
|
|
2
|
+
|
|
3
|
+
from functools import lru_cache, wraps
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def tracked_lru_cache(maxsize=128):
|
|
7
|
+
|
|
8
|
+
def decorator(func):
|
|
9
|
+
from . import cache_manager
|
|
10
|
+
cached_func = lru_cache(maxsize=maxsize)(func)
|
|
11
|
+
cache_manager.register(cached_func)
|
|
12
|
+
|
|
13
|
+
@wraps(func)
|
|
14
|
+
def wrapper(*args, **kwargs):
|
|
15
|
+
return cached_func(*args, **kwargs)
|
|
16
|
+
|
|
17
|
+
return wrapper
|
|
18
|
+
return decorator
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# cache_manager/global_cache_manager.py
|
|
2
|
+
|
|
3
|
+
class GlobalCacheManager:
|
|
4
|
+
def __init__(self):
|
|
5
|
+
self._caches = []
|
|
6
|
+
|
|
7
|
+
def register(self, func):
|
|
8
|
+
self._caches.append(func)
|
|
9
|
+
def __repr__(self) -> str:
|
|
10
|
+
return str(self.cache_info)
|
|
11
|
+
@property
|
|
12
|
+
def cache(self):
|
|
13
|
+
return self._caches
|
|
14
|
+
@property
|
|
15
|
+
def cache_info(self):
|
|
16
|
+
return {f"{item.__name__}": item.cache_info() for item in self._caches}
|
|
17
|
+
@property
|
|
18
|
+
def hit_rate(self):
|
|
19
|
+
return {f"{item.__name__}": (item.cache_info().hits/(item.cache_info().misses+item.cache_info().hits) if item.cache_info().misses+item.cache_info().hits>0 else 0) for item in self._caches}
|
|
20
|
+
def clear_all_caches(self):
|
|
21
|
+
for func in self._caches:
|
|
22
|
+
func.cache_clear()
|
|
23
|
+
def clear_cache(self, func):
|
|
24
|
+
func.cache_clear()
|
QuPRS/config.py
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# config.py
|
|
2
|
+
TOLERANCE = 1e-10
|
|
3
|
+
|
|
4
|
+
def set_tolerance(new_tolerance: float):
|
|
5
|
+
global TOLERANCE
|
|
6
|
+
if not isinstance(new_tolerance, (int, float)):
|
|
7
|
+
raise ValueError("Tolerance must be a numeric type")
|
|
8
|
+
if new_tolerance <= 0:
|
|
9
|
+
raise ValueError("Tolerance must be a positive number")
|
|
10
|
+
TOLERANCE = new_tolerance
|
|
11
|
+
|
|
12
|
+
def get_tolerance() -> float:
|
|
13
|
+
|
|
14
|
+
return TOLERANCE
|
|
File without changes
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
from QuPRS.pathsum import PathSum
|
|
2
|
+
|
|
3
|
+
def support_gate_set():
|
|
4
|
+
return ('h','x','y','z', 'p', 's','sdg', 't','tdg','sx','sxdg',
|
|
5
|
+
'rx', 'ry','rz', 'u','u1','u2','u3',
|
|
6
|
+
'cx','ch','cz','cp','ccx','mcx',
|
|
7
|
+
'crx','cry','crz','cu1','cu3','cu',
|
|
8
|
+
'swap','barrier','measure' )
|
|
9
|
+
|
|
10
|
+
def gate_map(circuit:PathSum, gate_name:str, qubit, gate_params = [], is_bra: bool =False, debug = False):
|
|
11
|
+
if debug:
|
|
12
|
+
print('add gate: %s, %s, %s, %s'%(gate_name, qubit, gate_params, is_bra))
|
|
13
|
+
print('circuit in',circuit)
|
|
14
|
+
|
|
15
|
+
assert gate_name in support_gate_set(), 'Not support %s gate yet.'%gate_name
|
|
16
|
+
if gate_name in ['barrier','measure']:
|
|
17
|
+
return circuit
|
|
18
|
+
func = getattr(circuit, gate_name)
|
|
19
|
+
if gate_params == []:
|
|
20
|
+
circuit = func(*qubit, is_bra)
|
|
21
|
+
else:
|
|
22
|
+
circuit = func(*gate_params, *qubit, is_bra)
|
|
23
|
+
|
|
24
|
+
if debug:
|
|
25
|
+
print('circuit out', circuit)
|
|
26
|
+
return circuit
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
from qiskit import QuantumCircuit, qasm2, qasm3
|
|
2
|
+
import numpy as np
|
|
3
|
+
import symengine as se
|
|
4
|
+
import signal,time, os
|
|
5
|
+
|
|
6
|
+
from QuPRS import config
|
|
7
|
+
from QuPRS.pathsum import PathSum, Register
|
|
8
|
+
from QuPRS.interface.gate_library import support_gate_set, gate_map
|
|
9
|
+
|
|
10
|
+
# global support_gate_set
|
|
11
|
+
support_gate_set = support_gate_set()
|
|
12
|
+
|
|
13
|
+
def timeout_handler(signum, frame):
|
|
14
|
+
raise TimeoutError("Operation timed out")
|
|
15
|
+
signal.signal(signal.SIGALRM, timeout_handler)
|
|
16
|
+
|
|
17
|
+
def build_circuit(circuit: QuantumCircuit, initial_state: bool|list|tuple = None):
|
|
18
|
+
pathsum_circuit = initialize(circuit, initial_state)
|
|
19
|
+
gate_list = get_gates(circuit)
|
|
20
|
+
# support_gate_set = support_gate_set()
|
|
21
|
+
for gate in gate_list:
|
|
22
|
+
assert gate[0] in support_gate_set, 'Not support %s gate yet.'%gate[0]
|
|
23
|
+
# print(pathsum_circuit, gate)
|
|
24
|
+
pathsum_circuit= gate_map(pathsum_circuit, gate[0], [f'{item[0]}_{item[1]}' for item in gate[1]], gate[2])
|
|
25
|
+
|
|
26
|
+
return pathsum_circuit
|
|
27
|
+
|
|
28
|
+
def initialize(circuit: QuantumCircuit, initial_state: bool|list|tuple = None):
|
|
29
|
+
'''
|
|
30
|
+
Construct initial PathSum and the name mapping.
|
|
31
|
+
'''
|
|
32
|
+
qiskit_regs = circuit.qregs
|
|
33
|
+
regs =[]
|
|
34
|
+
for reg in qiskit_regs:
|
|
35
|
+
regs.append(Register(reg.size, reg.name ))
|
|
36
|
+
|
|
37
|
+
return PathSum.QuantumCircuit(*regs, initial_state = initial_state)
|
|
38
|
+
|
|
39
|
+
def get_gate(circuit: QuantumCircuit, gate):
|
|
40
|
+
'''Get a gate properties.'''
|
|
41
|
+
gate_name = gate.operation.name
|
|
42
|
+
gate_params = gate.operation.params
|
|
43
|
+
qubits_old = gate.qubits
|
|
44
|
+
qubits = []
|
|
45
|
+
for qubit in qubits_old:
|
|
46
|
+
QuantumRegister = circuit.find_bit(qubit).registers
|
|
47
|
+
qubits.append((QuantumRegister[0][0].name, QuantumRegister[0][1]))
|
|
48
|
+
return gate_name, tuple(qubits), tuple(gate_params)
|
|
49
|
+
|
|
50
|
+
def get_gates(circuit: QuantumCircuit):
|
|
51
|
+
gates = []
|
|
52
|
+
for gate in circuit.data:
|
|
53
|
+
gates.append(get_gate(circuit, gate))
|
|
54
|
+
return gates
|
|
55
|
+
|
|
56
|
+
def add_gate(pathsum_circuit: PathSum.QuantumCircuit, gate, is_bra = False, count = 0, debug = False) -> tuple[PathSum, int]:
|
|
57
|
+
assert gate[0] in support_gate_set, 'Not support %s gate yet.'%gate[0]
|
|
58
|
+
if debug:
|
|
59
|
+
print(f'gate:{gate}, is_bra: {is_bra}')
|
|
60
|
+
pathsum_circuit= gate_map(pathsum_circuit, gate[0], [f'{item[0]}_{item[1]}' for item in gate[1]], gate[2], is_bra)
|
|
61
|
+
|
|
62
|
+
count += 1
|
|
63
|
+
if debug:
|
|
64
|
+
print(count, pathsum_circuit)
|
|
65
|
+
return pathsum_circuit, count
|
|
66
|
+
|
|
67
|
+
def load_circuit(circuit: str) -> QuantumCircuit:
|
|
68
|
+
"""
|
|
69
|
+
Load a Qiskit circuit from a file or qasm string.
|
|
70
|
+
|
|
71
|
+
Args:
|
|
72
|
+
circuit (str): The path to the QASM file or a QASM string.
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
QuantumCircuit: The loaded Qiskit circuit.
|
|
76
|
+
"""
|
|
77
|
+
if os.path.isfile(circuit):
|
|
78
|
+
with open(circuit, 'r') as f:
|
|
79
|
+
data = f.read()
|
|
80
|
+
else:
|
|
81
|
+
data = circuit
|
|
82
|
+
flag = True if 'OPENQASM 3.0' in data else False
|
|
83
|
+
if flag:
|
|
84
|
+
qiskit_circuit = qasm3.loads(data)
|
|
85
|
+
else:
|
|
86
|
+
qiskit_circuit = qasm2.loads(data)
|
|
87
|
+
return qiskit_circuit
|
|
88
|
+
|
|
89
|
+
def qasm_eq_check(circuit1: str|QuantumCircuit, circuit2:str|QuantumCircuit, strategy ='Difference' , Benchmark_Name=None, timeout= 600):
|
|
90
|
+
tolerance = config.TOLERANCE
|
|
91
|
+
def load_circuit(circuit):
|
|
92
|
+
if isinstance(circuit, str):
|
|
93
|
+
if circuit.endswith('.qasm'):
|
|
94
|
+
f = open(circuit, 'r')
|
|
95
|
+
data = f.read()
|
|
96
|
+
if 'OPENQASM 3.0' in data:
|
|
97
|
+
return qasm3.load(circuit)
|
|
98
|
+
else:
|
|
99
|
+
return qasm2.load(circuit)
|
|
100
|
+
elif 'OPENQASM' in circuit:
|
|
101
|
+
if 'OPENQASM 3.0' in circuit:
|
|
102
|
+
return qasm3.loads(circuit)
|
|
103
|
+
else:
|
|
104
|
+
return qasm2.loads(circuit)
|
|
105
|
+
else:
|
|
106
|
+
raise ValueError("Invalid circuit format")
|
|
107
|
+
elif isinstance(circuit, QuantumCircuit):
|
|
108
|
+
return circuit
|
|
109
|
+
|
|
110
|
+
qiskit_circuit = load_circuit(circuit1)
|
|
111
|
+
qiskit_circuit2 = load_circuit(circuit2)
|
|
112
|
+
|
|
113
|
+
start_time = time.time()
|
|
114
|
+
|
|
115
|
+
initial_state = initialize(qiskit_circuit)
|
|
116
|
+
pathsum_circuit = initial_state
|
|
117
|
+
qubit_num = initial_state.num_qubits
|
|
118
|
+
|
|
119
|
+
l1 = len(qiskit_circuit.data)
|
|
120
|
+
l2 = len(qiskit_circuit2.data)
|
|
121
|
+
|
|
122
|
+
output_dict = {"Benchmark_Name": Benchmark_Name,
|
|
123
|
+
"qubit_num": qubit_num,
|
|
124
|
+
"gate_num": l1,
|
|
125
|
+
"gate_num2": l2}
|
|
126
|
+
|
|
127
|
+
if strategy == 'Proportional':
|
|
128
|
+
from QuPRS.utils.strategy import proportional
|
|
129
|
+
output_dict, pathsum_circuit = proportional(pathsum_circuit, qiskit_circuit, qiskit_circuit2, l1, l2, output_dict, timeout)
|
|
130
|
+
elif strategy == 'Naive':
|
|
131
|
+
from QuPRS.utils.strategy import naive
|
|
132
|
+
output_dict, pathsum_circuit = naive(pathsum_circuit, qiskit_circuit, qiskit_circuit2, l1, l2, output_dict, timeout)
|
|
133
|
+
elif strategy == "Straightforward":
|
|
134
|
+
from QuPRS.utils.strategy import straightforward
|
|
135
|
+
output_dict, pathsum_circuit = straightforward(pathsum_circuit, qiskit_circuit, qiskit_circuit2, l1, l2, output_dict, timeout)
|
|
136
|
+
elif strategy == "Difference":
|
|
137
|
+
from QuPRS.utils.strategy import difference
|
|
138
|
+
output_dict, pathsum_circuit = difference(pathsum_circuit, qiskit_circuit, qiskit_circuit2, l1, l2, output_dict, timeout)
|
|
139
|
+
else:
|
|
140
|
+
raise ValueError("Invalid strategy")
|
|
141
|
+
if "equivalent" in output_dict:
|
|
142
|
+
return output_dict, pathsum_circuit
|
|
143
|
+
else:
|
|
144
|
+
total_time = time.time() - start_time
|
|
145
|
+
output_dict["Time"] = round(total_time, 3)
|
|
146
|
+
|
|
147
|
+
# if pathsum_circuit.f == initial_state.f and pathsum_circuit.pathvar == initial_state.pathvar:
|
|
148
|
+
if pathsum_circuit.f == initial_state.f:
|
|
149
|
+
if pathsum_circuit.P == initial_state.P:
|
|
150
|
+
output_dict["equivalent"] = 'equivalent'
|
|
151
|
+
P_free_symbols = pathsum_circuit.P.free_symbols
|
|
152
|
+
check_P_symbol = tuple(filter(lambda x: x.name in pathsum_circuit.f.bits, P_free_symbols))
|
|
153
|
+
if len(P_free_symbols) == 0:
|
|
154
|
+
if pathsum_circuit.P < tolerance:
|
|
155
|
+
output_dict["equivalent"] = 'equivalent'
|
|
156
|
+
else:
|
|
157
|
+
output_dict["equivalent"] = 'equivalent*'
|
|
158
|
+
elif len(check_P_symbol) == 0:
|
|
159
|
+
output_dict["equivalent"] = 'equivalent*'
|
|
160
|
+
else:
|
|
161
|
+
output_dict["equivalent"] = 'not_equivalent'
|
|
162
|
+
return output_dict, pathsum_circuit
|
|
163
|
+
else:
|
|
164
|
+
output_dict["equivalent"] = 'unknown'
|
|
165
|
+
return output_dict, pathsum_circuit
|
|
166
|
+
|
|
167
|
+
def qasm_eq_check_with_wmc(circuit1: str|QuantumCircuit, circuit2:str|QuantumCircuit, strategy ='Difference' , Benchmark_Name=None, timeout= 600):
|
|
168
|
+
tolerance = config.TOLERANCE
|
|
169
|
+
|
|
170
|
+
output_dict, circuit = qasm_eq_check(circuit1=circuit1, circuit2=circuit2, strategy =strategy , Benchmark_Name=Benchmark_Name, timeout= timeout)
|
|
171
|
+
wmc_time = 0
|
|
172
|
+
log_wmc = None
|
|
173
|
+
expect = None
|
|
174
|
+
theta = None
|
|
175
|
+
to_DIMACS_time = 0
|
|
176
|
+
if output_dict["equivalent"] == "unknown":
|
|
177
|
+
from QuPRS.interface.ps2wmc import to_DIMACS, run_wmc
|
|
178
|
+
from QuPRS.utils.util import get_theta
|
|
179
|
+
|
|
180
|
+
expect = circuit.num_qubits+circuit.num_pathvar/2
|
|
181
|
+
signal.alarm(timeout)
|
|
182
|
+
start_time = time.time()
|
|
183
|
+
try:
|
|
184
|
+
to_DIMACS(circuit)
|
|
185
|
+
to_DIMACS_time = round(time.time() - start_time, 3)
|
|
186
|
+
except TimeoutError:
|
|
187
|
+
to_DIMACS_time = f'>{timeout}'
|
|
188
|
+
finally:
|
|
189
|
+
signal.alarm(0)
|
|
190
|
+
|
|
191
|
+
if to_DIMACS_time != f'>{timeout}':
|
|
192
|
+
signal.alarm(timeout)
|
|
193
|
+
start_time = time.time()
|
|
194
|
+
try:
|
|
195
|
+
complex_number = run_wmc()
|
|
196
|
+
wmc_time = round(time.time() - start_time, 3)
|
|
197
|
+
abs_num = np.sqrt(complex_number[0]**2 + complex_number[1]**2)
|
|
198
|
+
log_wmc = round(np.log2(abs_num), 3)
|
|
199
|
+
theta = get_theta(complex_number[1]/abs_num, complex_number[0]/abs_num)
|
|
200
|
+
if abs(log_wmc - expect) < tolerance:
|
|
201
|
+
if abs(theta) < tolerance * 2 * np.pi:
|
|
202
|
+
output_dict["equivalent"] = "equivalent"
|
|
203
|
+
else:
|
|
204
|
+
output_dict["equivalent"] = "equivalent*"
|
|
205
|
+
else:
|
|
206
|
+
output_dict["equivalent"] = "not_equivalent"
|
|
207
|
+
except TimeoutError:
|
|
208
|
+
wmc_time = f'>{timeout}'
|
|
209
|
+
finally:
|
|
210
|
+
signal.alarm(0)
|
|
211
|
+
|
|
212
|
+
elif output_dict["equivalent"] == "equivalent*":
|
|
213
|
+
theta = str((circuit.P* 2 * se.pi).evalf())
|
|
214
|
+
elif output_dict["equivalent"] == "equivalent":
|
|
215
|
+
theta = 0
|
|
216
|
+
|
|
217
|
+
output_dict["PathSum_time"] = output_dict["Time"]
|
|
218
|
+
output_dict["to_DIMACS_time"] = to_DIMACS_time
|
|
219
|
+
output_dict["wmc_time"] = wmc_time
|
|
220
|
+
if output_dict["equivalent"] == "Timeout" or to_DIMACS_time == f'>{timeout}' or wmc_time == f'>{timeout}':
|
|
221
|
+
output_dict["equivalent"] = "Timeout"
|
|
222
|
+
output_dict["Time"] = f'>{timeout}'
|
|
223
|
+
else:
|
|
224
|
+
output_dict["Time"] = round(wmc_time + to_DIMACS_time + output_dict["PathSum_time"],3)
|
|
225
|
+
return output_dict
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import symengine as se
|
|
2
|
+
import sympy as sp
|
|
3
|
+
from sympy.logic.boolalg import Xor, And, Or, Not
|
|
4
|
+
|
|
5
|
+
import subprocess, re
|
|
6
|
+
|
|
7
|
+
from QuPRS.utils import WMC
|
|
8
|
+
from QuPRS.utils.util import algebraic_to_logical
|
|
9
|
+
|
|
10
|
+
counter = {'idx': 0}
|
|
11
|
+
def fresh_var():
|
|
12
|
+
counter['idx'] += 1
|
|
13
|
+
return sp.Symbol(f'e_{counter["idx"]-1}')
|
|
14
|
+
def extend_symbols_map(symbols_map, new_symbol):
|
|
15
|
+
if new_symbol not in symbols_map:
|
|
16
|
+
symbols_map[new_symbol] = len(symbols_map) + 1
|
|
17
|
+
return symbols_map
|
|
18
|
+
# Convert a logical equivalence to CNF
|
|
19
|
+
def a_eq_b_cnf(a, b):
|
|
20
|
+
if a == b:
|
|
21
|
+
return sp.true
|
|
22
|
+
return sp.to_cnf(Or(And(a, b), And(Not(a), Not(b))), simplify=True, force=True)
|
|
23
|
+
def encode_equivalence(c, cnf):
|
|
24
|
+
if c == cnf:
|
|
25
|
+
return sp.true
|
|
26
|
+
cnf_clauses = []
|
|
27
|
+
# cnf = and(a1, a2, ...) or or(o1, o2, ...), a1 = or(o1, o2, ...)
|
|
28
|
+
if isinstance(cnf, And):
|
|
29
|
+
args = cnf.args
|
|
30
|
+
new_args = list(args)
|
|
31
|
+
for i, arg in enumerate(args):
|
|
32
|
+
if isinstance(arg, Or):
|
|
33
|
+
args2 = arg.args
|
|
34
|
+
new_args[i] = fresh_var()
|
|
35
|
+
cnf_clauses.append(Or(Not(new_args[i]), *args2)) # e -> or(o1, o2, ...)
|
|
36
|
+
cnf_clauses.extend([Or(Not(arg2), new_args[i]) for arg2 in args2]) # or(o1, o2, ...) -> e
|
|
37
|
+
elif isinstance(arg, sp.Symbol) or isinstance(arg, Not):
|
|
38
|
+
continue
|
|
39
|
+
else:
|
|
40
|
+
raise ValueError(f"Unsupported CNF clause {arg} type {type(arg)}")
|
|
41
|
+
|
|
42
|
+
cnf_clauses.append(Or(c, *[Not(arg) for arg in new_args])) # and(a1, a2, ...) -> c
|
|
43
|
+
cnf_clauses.extend([Or(Not(c), arg) for arg in new_args]) # c-> and(a1, a2, ...)
|
|
44
|
+
elif isinstance(cnf, Or):
|
|
45
|
+
args = cnf.args
|
|
46
|
+
cnf_clauses.append(Or(Not(c), *args)) # c -> or(o1, o2, ...)
|
|
47
|
+
cnf_clauses.extend([Or(Not(arg), c) for arg in args]) # or(o1, o2, ...) -> c
|
|
48
|
+
elif isinstance(cnf, Not) or isinstance(cnf, sp.Symbol) or isinstance(cnf, se.Symbol):
|
|
49
|
+
cnf_clauses.append(Or(Not(c), cnf)) # c -> o1
|
|
50
|
+
cnf_clauses.append(Or(Not(cnf), c)) # o1 -> c
|
|
51
|
+
elif cnf == sp.true:
|
|
52
|
+
cnf_clauses.append(c) # c -> True
|
|
53
|
+
elif cnf == sp.false:
|
|
54
|
+
cnf_clauses.append(Not(c)) # c -> False
|
|
55
|
+
else:
|
|
56
|
+
raise ValueError(f"Unsupported CNF clause {cnf} type {type(cnf)}")
|
|
57
|
+
return And(*cnf_clauses)
|
|
58
|
+
def anf_to_cnf(anf):
|
|
59
|
+
"""
|
|
60
|
+
Convert ANF to CNF using Tseitin transformation.
|
|
61
|
+
"""
|
|
62
|
+
if anf == sp.true or anf == sp.false or \
|
|
63
|
+
isinstance(anf, sp.Symbol) or \
|
|
64
|
+
isinstance(anf, se.Symbol) or \
|
|
65
|
+
isinstance(anf, And):
|
|
66
|
+
return (anf,)
|
|
67
|
+
|
|
68
|
+
clauses = []
|
|
69
|
+
def tseitin(c, anf): # c <-> anf
|
|
70
|
+
# print(f"tseitin: {c} <-> {anf}")
|
|
71
|
+
if anf == sp.true:
|
|
72
|
+
clauses.append(c)
|
|
73
|
+
elif anf == sp.false:
|
|
74
|
+
clauses.append(Not(c))
|
|
75
|
+
elif isinstance(anf, Not):
|
|
76
|
+
args = anf.args
|
|
77
|
+
if isinstance(args[0], And):
|
|
78
|
+
a = fresh_var()
|
|
79
|
+
tseitin(a, args[0])
|
|
80
|
+
elif isinstance(args[0], Xor):
|
|
81
|
+
a = fresh_var()
|
|
82
|
+
tseitin(a, Xor(*args[0].args))
|
|
83
|
+
else:
|
|
84
|
+
a = args[0]
|
|
85
|
+
clauses.append(Or(Not(c), Not(a))) # c -> Not(a)
|
|
86
|
+
clauses.append(Or(c, a)) # Not(a) -> c
|
|
87
|
+
elif isinstance(anf, Xor):
|
|
88
|
+
args = anf.args
|
|
89
|
+
if isinstance(args[0], And):
|
|
90
|
+
a = fresh_var()
|
|
91
|
+
tseitin(a, args[0])
|
|
92
|
+
else:
|
|
93
|
+
a = args[0]
|
|
94
|
+
|
|
95
|
+
if len(args) > 2:
|
|
96
|
+
b = fresh_var()
|
|
97
|
+
tseitin(b, Xor(*args[1:]))
|
|
98
|
+
elif isinstance(args[1], And):
|
|
99
|
+
b = fresh_var()
|
|
100
|
+
tseitin(b, args[1])
|
|
101
|
+
else:
|
|
102
|
+
b = args[1]
|
|
103
|
+
|
|
104
|
+
clauses.extend([
|
|
105
|
+
Or(Not(a), Not(b), Not(c)),
|
|
106
|
+
Or(a, b, Not(c)),
|
|
107
|
+
Or(a, Not(b), c),
|
|
108
|
+
Or(Not(a), b, c),
|
|
109
|
+
]) # c <-> a xor b
|
|
110
|
+
|
|
111
|
+
elif isinstance(anf, And):
|
|
112
|
+
args = anf.args
|
|
113
|
+
clauses.extend([Or(Not(c), arg) for arg in args]) # c -> anf
|
|
114
|
+
clauses.append(Or(c, *[Not(arg) for arg in args])) # anf -> c
|
|
115
|
+
elif isinstance(anf, sp.Symbol) or isinstance(anf, se.Symbol):
|
|
116
|
+
clauses.append(Or(Not(c), anf)) # c -> anf
|
|
117
|
+
clauses.append(Or(c, Not(anf))) # anf -> c
|
|
118
|
+
else:
|
|
119
|
+
raise ValueError(f"Unsupported ANF clause {anf} type {type(anf)}")
|
|
120
|
+
|
|
121
|
+
new_var = fresh_var()
|
|
122
|
+
tseitin(new_var, anf)
|
|
123
|
+
# print(f"clauses: {clauses}")
|
|
124
|
+
return (new_var, And(*clauses))
|
|
125
|
+
|
|
126
|
+
# Convert polynomial P to weighted logical expressions
|
|
127
|
+
def P_to_wlogi(P):
|
|
128
|
+
if P == 0:
|
|
129
|
+
return []
|
|
130
|
+
args = P.as_coefficients_dict()
|
|
131
|
+
output = [(v, algebraic_to_logical(k)) if v != 0 and v != 1 else (k, algebraic_to_logical(v)) for k, v in args.items() if v != 0]
|
|
132
|
+
return output
|
|
133
|
+
|
|
134
|
+
# Convert CNF to clauses
|
|
135
|
+
def cnf_to_clauses(cnf, symbols_map, flag=False):
|
|
136
|
+
if isinstance(cnf, And):
|
|
137
|
+
return [cnf_to_clauses(arg, symbols_map, flag) for arg in cnf.args]
|
|
138
|
+
elif isinstance(cnf, Or):
|
|
139
|
+
clause = ' '.join(cnf_to_clauses(arg, symbols_map, flag=True) for arg in cnf.args) + ' 0'
|
|
140
|
+
return clause
|
|
141
|
+
elif isinstance(cnf, Not):
|
|
142
|
+
return f'-{cnf_to_clauses(cnf.args[0], symbols_map, flag)}'
|
|
143
|
+
elif isinstance(cnf, sp.Symbol):
|
|
144
|
+
clause = f'{symbols_map[cnf] }'
|
|
145
|
+
return clause if flag else [clause + ' 0']
|
|
146
|
+
elif cnf == sp.true:
|
|
147
|
+
return []
|
|
148
|
+
else:
|
|
149
|
+
raise ValueError(f'Invalid CNF: {cnf}')
|
|
150
|
+
|
|
151
|
+
# Convert weighted logical expressions to clauses
|
|
152
|
+
def wlogi_to_clauses(wlogi_list, symbols_map):
|
|
153
|
+
clauses = []
|
|
154
|
+
weights = []
|
|
155
|
+
z = sp.symbols(f'z_:{len(wlogi_list)}')
|
|
156
|
+
l = len(symbols_map)
|
|
157
|
+
for symbol in z:
|
|
158
|
+
extend_symbols_map(symbols_map, symbol)
|
|
159
|
+
for i, (w, logi) in enumerate(wlogi_list):
|
|
160
|
+
weights.append(f'c p weight {l + i + 1} {se.cos(2*se.pi*w).evalf()} {se.sin(2*se.pi*w).evalf()} 0')
|
|
161
|
+
idx = counter["idx"]
|
|
162
|
+
temp = encode_equivalence(z[i], logi)
|
|
163
|
+
for i in range(idx, counter["idx"]):
|
|
164
|
+
extend_symbols_map(symbols_map, sp.Symbol(f'e_{i}'))
|
|
165
|
+
clauses.extend(cnf_to_clauses(temp, symbols_map))
|
|
166
|
+
return weights, clauses, symbols_map
|
|
167
|
+
|
|
168
|
+
# Convert PathSum object to DIMACS format
|
|
169
|
+
def to_DIMACS(pathsum, filename='wmc.cnf'):
|
|
170
|
+
counter["idx"] = 0
|
|
171
|
+
free_symbols_list = [sp.Symbol(item) for item in pathsum.bits]
|
|
172
|
+
free_symbols_list.extend(sp.sympify(free_symbol) for free_symbol in pathsum.pathvar)
|
|
173
|
+
symbols_map ={}
|
|
174
|
+
for symbol in free_symbols_list:
|
|
175
|
+
extend_symbols_map(symbols_map, symbol)
|
|
176
|
+
|
|
177
|
+
weights, clauses, symbols_map = wlogi_to_clauses(P_to_wlogi(pathsum.P), symbols_map)
|
|
178
|
+
for qubit in range(pathsum.num_qubits):
|
|
179
|
+
a = sp.Symbol(pathsum.bits[qubit])
|
|
180
|
+
idx = counter["idx"]
|
|
181
|
+
b = anf_to_cnf(pathsum.f[qubit])
|
|
182
|
+
if len(b) == 1:
|
|
183
|
+
b = b[0]
|
|
184
|
+
else:
|
|
185
|
+
for i in range(idx, counter["idx"]):
|
|
186
|
+
extend_symbols_map(symbols_map, sp.Symbol(f'e_{i}'))
|
|
187
|
+
clauses.extend(cnf_to_clauses(b[1], symbols_map))
|
|
188
|
+
b = b[0]
|
|
189
|
+
temp = encode_equivalence(a, b)
|
|
190
|
+
clauses.extend(cnf_to_clauses(temp, symbols_map))
|
|
191
|
+
|
|
192
|
+
DIMACS_text = f'p cnf {len(symbols_map)} {len(clauses)}\nc t wmc'
|
|
193
|
+
DIMACS_text += '\n' + '\n'.join(weights + clauses)
|
|
194
|
+
with open(filename, 'w') as file:
|
|
195
|
+
file.write(DIMACS_text.strip())
|
|
196
|
+
return DIMACS_text
|
|
197
|
+
|
|
198
|
+
def run_wmc(file='wmc.cnf'):
|
|
199
|
+
result = subprocess.run([str(WMC()), "-mode=1", file], capture_output=True, text=True)
|
|
200
|
+
|
|
201
|
+
output_lines = result.stdout.split("\n")
|
|
202
|
+
|
|
203
|
+
for line in output_lines:
|
|
204
|
+
if line.startswith("c s exact double prec-sci"):
|
|
205
|
+
match = re.search(r"([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)([-+]\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)i", line)
|
|
206
|
+
if match:
|
|
207
|
+
real_part = float(match.group(1))
|
|
208
|
+
imag_part = float(match.group(2))
|
|
209
|
+
return real_part, imag_part
|
|
210
|
+
match = re.search(r"([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)", line)
|
|
211
|
+
if match:
|
|
212
|
+
real_part = float(match.group(1))
|
|
213
|
+
imag_part = 0
|
|
214
|
+
return real_part, imag_part
|
|
215
|
+
match = re.search(r"([-+]\d+(?:\.\d+)?(?:[eE][-+]?\d+)?)i", line)
|
|
216
|
+
if match:
|
|
217
|
+
real_part = 0
|
|
218
|
+
imag_part = float(match.group(1))
|
|
219
|
+
return real_part, imag_part
|
|
220
|
+
return None
|
|
221
|
+
|
|
222
|
+
if __name__ == "__main__":
|
|
223
|
+
# from pathsum.pathsum import PathSum, F, Register
|
|
224
|
+
# y = se.symbols('y_:2')
|
|
225
|
+
# x = se.symbols('x_:2')
|
|
226
|
+
# P = se.Rational(1, 2) * x[0] * y[0] + se.Rational(1, 2) * y[0] * y[1]
|
|
227
|
+
|
|
228
|
+
# reg = Register(2)
|
|
229
|
+
# data = {reg[0].name: y[1]}
|
|
230
|
+
# bits = tuple(data.keys())
|
|
231
|
+
# f = F(reg, data=data, bits=bits)
|
|
232
|
+
|
|
233
|
+
# pathvar = frozenset(y)
|
|
234
|
+
# circuit = PathSum(P, f, pathvar)
|
|
235
|
+
|
|
236
|
+
# print(to_DIMACS(circuit, filename='wmc.cnf'))
|
|
237
|
+
# print("result:",run_wmc(file='wmc.cnf'))
|
|
238
|
+
|
|
239
|
+
x = sp.symbols('x_:10')
|
|
240
|
+
# A = sp.Symbol('A')
|
|
241
|
+
# B = And(Or(x,y),z)
|
|
242
|
+
A = x[0]
|
|
243
|
+
B = (x[3] | x[4] | ~x[1]) & (x[3] | x[1] | ~x[4]) & (x[4] | x[1] | ~x[3])
|
|
244
|
+
# & (x[4] | x[2] | ~x[5]) & (x[4] | x[5] | ~x[2]) & (x[2] | x[5] | ~x[4]) & (~x[3] | ~x[4] | ~x[1]) & (~x[4] | ~x[2] | ~x[5])
|
|
245
|
+
|
|
246
|
+
eq_cnf = encode_equivalence(A, B)
|
|
247
|
+
print(eq_cnf)
|
|
248
|
+
|
|
249
|
+
# anf = Xor(*x)
|
|
250
|
+
# print('anf:', anf)
|
|
251
|
+
# cnf = anf_to_cnf(anf)
|
|
252
|
+
# print('cnf:', cnf)
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import re
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
def convert_qasm_to_qc(oldfile, output_dir=None):
|
|
6
|
+
# Determine the output directory
|
|
7
|
+
if output_dir:
|
|
8
|
+
output_dir = Path(output_dir)
|
|
9
|
+
else:
|
|
10
|
+
output_dir = Path(oldfile).parent
|
|
11
|
+
|
|
12
|
+
# Define the new file name in the specified or default directory
|
|
13
|
+
newfile = output_dir / f"{Path(oldfile).stem}.qc"
|
|
14
|
+
|
|
15
|
+
# Copy the content from the old file to the new file
|
|
16
|
+
with open(oldfile, 'r') as infile, open(newfile, 'w') as outfile:
|
|
17
|
+
for line in infile:
|
|
18
|
+
# Ignore specific lines
|
|
19
|
+
if 'OPENQASM 2.0;' in line or 'include "qelib1.inc";' in line:
|
|
20
|
+
continue
|
|
21
|
+
# Write the line to the new file
|
|
22
|
+
outfile.write(line)
|
|
23
|
+
|
|
24
|
+
# Read the contents of the new file
|
|
25
|
+
with open(newfile, 'r') as file:
|
|
26
|
+
content = file.read()
|
|
27
|
+
|
|
28
|
+
# Replacement patterns
|
|
29
|
+
replacements = [
|
|
30
|
+
(r'qreg [^\d]*\[(\d+)\];', lambda m: f".v {' '.join(map(str, range(int(m.group(1)))))}\nBEGIN"),
|
|
31
|
+
(r'h qubits\[(\d+)\];', r'H \1'),
|
|
32
|
+
(r'x qubits\[(\d+)\];', r'X \1'),
|
|
33
|
+
(r'y qubits\[(\d+)\];', r'Y \1'),
|
|
34
|
+
(r'z qubits\[(\d+)\];', r'Z \1'),
|
|
35
|
+
(r'cx qubits\[(\d+)\][^a-z]+qubits\[(\d+)\];', r'tof \1 \2'),
|
|
36
|
+
(r'cz qubits\[(\d+)\][^a-z]+qubits\[(\d+)\];', r'CZ \1 \2'),
|
|
37
|
+
(r's qubits\[(\d+)\];', r'S \1'),
|
|
38
|
+
(r'sdg qubits\[(\d+)\];', r'S* \1'),
|
|
39
|
+
(r't qubits\[(\d+)\];', r'T \1'),
|
|
40
|
+
(r'tdg qubits\[(\d+)\];', r'T* \1'),
|
|
41
|
+
(r'swap qubits\[(\d+)\][^a-z]+qubits\[(\d+)\];', r'Swap \1 \2'),
|
|
42
|
+
(r'rz\((.+)\) qubits\[(\d+)\];', r'Rz \1 \2'),
|
|
43
|
+
(r'rx\((.+)\) qubits\[(\d+)\];', r'Rx \1 \2'),
|
|
44
|
+
(r'ry\((.+)\) qubits\[(\d+)\];', r'Ry \1 \2'),
|
|
45
|
+
(r'ccx qubits\[(\d+)\][^a-z]+qubits\[(\d+)\][^a-z]+qubits\[(\d+)\];', r'tof \1 \2 \3'),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
# Apply the replacements
|
|
50
|
+
for pattern, replacement in replacements:
|
|
51
|
+
content = re.sub(pattern, replacement, content)
|
|
52
|
+
# Append "END" to the file
|
|
53
|
+
content += "END"
|
|
54
|
+
|
|
55
|
+
# Write the modified content back to the file
|
|
56
|
+
with open(newfile, 'w') as file:
|
|
57
|
+
file.write(content)
|
|
58
|
+
|
|
59
|
+
print(f"Conversion completed. Output saved to: {newfile}")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == "__main__":
|
|
63
|
+
if len(sys.argv) < 2 or len(sys.argv) > 3:
|
|
64
|
+
print("Usage: python script.py <oldfile.qasm> [output_dir]")
|
|
65
|
+
else:
|
|
66
|
+
oldfile = sys.argv[1]
|
|
67
|
+
output_dir = sys.argv[2] if len(sys.argv) == 3 else None
|
|
68
|
+
convert_qasm_to_qc(oldfile, output_dir)
|