QFIE 1.2.1__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.
- QFIE/FuzzyEngines.py +1330 -0
- QFIE/QFS.py +213 -0
- QFIE/__init__.py +3 -0
- QFIE/fuzzy_partitions.py +67 -0
- QFIE/utils.py +167 -0
- qfie-1.2.1.dist-info/METADATA +175 -0
- qfie-1.2.1.dist-info/RECORD +10 -0
- qfie-1.2.1.dist-info/WHEEL +5 -0
- qfie-1.2.1.dist-info/licenses/LICENSE.md +21 -0
- qfie-1.2.1.dist-info/top_level.txt +1 -0
QFIE/FuzzyEngines.py
ADDED
|
@@ -0,0 +1,1330 @@
|
|
|
1
|
+
""" This module implements the base class for setting up the quantum fuzzy inference engine proposed in doi: 10.1109/TFUZZ.2022.3202348. """
|
|
2
|
+
import numpy as np
|
|
3
|
+
import skfuzzy as fuzz
|
|
4
|
+
import math
|
|
5
|
+
import warnings
|
|
6
|
+
from copy import deepcopy
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from qiskit import (
|
|
9
|
+
ClassicalRegister,
|
|
10
|
+
QuantumRegister,
|
|
11
|
+
)
|
|
12
|
+
try:
|
|
13
|
+
from qiskit_aer import AerSimulator
|
|
14
|
+
except ImportError:
|
|
15
|
+
AerSimulator = None
|
|
16
|
+
from qiskit.visualization import plot_histogram
|
|
17
|
+
from qiskit.quantum_info import Statevector
|
|
18
|
+
from qiskit import transpile
|
|
19
|
+
from itertools import cycle, islice, repeat, product as cartesian_product
|
|
20
|
+
from concurrent.futures import ThreadPoolExecutor
|
|
21
|
+
import time
|
|
22
|
+
from sympy import false, symbols, true
|
|
23
|
+
from sympy.logic.boolalg import And, Not, Or, SOPform
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
from . import fuzzy_partitions as fp
|
|
27
|
+
from . import QFS as QFS
|
|
28
|
+
#import fuzzy_partitions as fp
|
|
29
|
+
#import QFS as QFS
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _prepare_draw_filename(filename, label=None):
|
|
33
|
+
path = Path(filename).expanduser()
|
|
34
|
+
if label is not None:
|
|
35
|
+
path = path.with_name(f"{label}_{path.name}")
|
|
36
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
37
|
+
return str(path)
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _bitstring_to_minterm(bitstring):
|
|
41
|
+
return int(bitstring, 2) if bitstring else 0
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _padded_code(code, qreg_size):
|
|
45
|
+
return code + ("0" * (qreg_size - len(code)))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _partition_valid_codes(partition, qreg_size):
|
|
49
|
+
return [
|
|
50
|
+
_padded_code(code, qreg_size)
|
|
51
|
+
for code in partition.associate_quantum_states().values()
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def _all_bitstrings(n_bits):
|
|
56
|
+
return [
|
|
57
|
+
"".join(bits)
|
|
58
|
+
for bits in cartesian_product("01", repeat=n_bits)
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def _gray_code(index):
|
|
63
|
+
return index ^ (index >> 1)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def _output_register_size(output_partition, output_encoding):
|
|
67
|
+
if output_encoding == "gray":
|
|
68
|
+
return max(1, math.ceil(math.log(output_partition.len_partition(), 2)))
|
|
69
|
+
return output_partition.len_partition()
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def _output_code_for_index(output_index, output_partition, output_encoding):
|
|
73
|
+
if output_encoding == "gray":
|
|
74
|
+
register_size = _output_register_size(output_partition, output_encoding)
|
|
75
|
+
binary_format = "{0:0" + str(register_size) + "b}"
|
|
76
|
+
return binary_format.format(_gray_code(output_index))
|
|
77
|
+
|
|
78
|
+
bits = ["0" for _ in range(output_partition.len_partition())]
|
|
79
|
+
bits[output_index] = "1"
|
|
80
|
+
return "".join(bits)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _output_codes(output_partition, output_encoding):
|
|
84
|
+
return [
|
|
85
|
+
_output_code_for_index(output_index, output_partition, output_encoding)
|
|
86
|
+
for output_index in range(output_partition.len_partition())
|
|
87
|
+
]
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _collect_valid_and_invalid_input_codes(partitions, qreg_sizes):
|
|
91
|
+
local_valid_codes = [
|
|
92
|
+
_partition_valid_codes(partition, qreg_sizes[partition.name])
|
|
93
|
+
for partition in partitions
|
|
94
|
+
]
|
|
95
|
+
local_all_codes = [
|
|
96
|
+
_all_bitstrings(qreg_sizes[partition.name])
|
|
97
|
+
for partition in partitions
|
|
98
|
+
]
|
|
99
|
+
|
|
100
|
+
valid_inputs = {
|
|
101
|
+
"".join(codes)
|
|
102
|
+
for codes in cartesian_product(*local_valid_codes)
|
|
103
|
+
}
|
|
104
|
+
all_inputs = {
|
|
105
|
+
"".join(codes)
|
|
106
|
+
for codes in cartesian_product(*local_all_codes)
|
|
107
|
+
}
|
|
108
|
+
invalid_inputs = all_inputs - valid_inputs
|
|
109
|
+
return sorted(valid_inputs), sorted(invalid_inputs), sorted(all_inputs)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _rule_product_and_output_index(rule, input_partitions, output_partition, encoding):
|
|
113
|
+
all_partitions = input_partitions.copy()
|
|
114
|
+
all_partitions.append(output_partition)
|
|
115
|
+
converted_rule = fp.fuzzy_rules().add_rules(rule, all_partitions)
|
|
116
|
+
original_rule = list(filter(("is").__ne__, rule.split()))
|
|
117
|
+
|
|
118
|
+
product = []
|
|
119
|
+
output_index = None
|
|
120
|
+
|
|
121
|
+
for index, token in enumerate(converted_rule):
|
|
122
|
+
if token != "and" and token != "then":
|
|
123
|
+
continue
|
|
124
|
+
|
|
125
|
+
if encoding == "linear" and converted_rule[index - 2] == "not":
|
|
126
|
+
var_name = converted_rule[index - 3]
|
|
127
|
+
code = converted_rule[index - 1]
|
|
128
|
+
qubit_index = code[::-1].index("1")
|
|
129
|
+
product.append((f"{var_name}_{qubit_index}", 0))
|
|
130
|
+
else:
|
|
131
|
+
var_name = converted_rule[index - 2]
|
|
132
|
+
code = converted_rule[index - 1]
|
|
133
|
+
if encoding == "linear":
|
|
134
|
+
qubit_index = code[::-1].index("1")
|
|
135
|
+
product.append((f"{var_name}_{qubit_index}", 1))
|
|
136
|
+
else:
|
|
137
|
+
for qubit_index, bit_value in enumerate(code):
|
|
138
|
+
product.append((f"{var_name}_{qubit_index}", int(bit_value)))
|
|
139
|
+
|
|
140
|
+
if token == "then":
|
|
141
|
+
output_index = output_partition.sets.index(original_rule[index + 2])
|
|
142
|
+
|
|
143
|
+
return product, output_index
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _product_matches(product, assignment):
|
|
147
|
+
return all(assignment[var_name] == bit_value for var_name, bit_value in product)
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def _build_rule_truth_table(
|
|
151
|
+
rules,
|
|
152
|
+
input_partitions,
|
|
153
|
+
output_partition,
|
|
154
|
+
output_encoding,
|
|
155
|
+
encoding,
|
|
156
|
+
valid_inputs,
|
|
157
|
+
invalid_inputs,
|
|
158
|
+
variable_order,
|
|
159
|
+
):
|
|
160
|
+
parsed_rules = [
|
|
161
|
+
_rule_product_and_output_index(rule, input_partitions, output_partition, encoding)
|
|
162
|
+
for rule in rules
|
|
163
|
+
]
|
|
164
|
+
output_codes = _output_codes(output_partition, output_encoding)
|
|
165
|
+
n_output_bits = len(output_codes[0])
|
|
166
|
+
ones_by_output = [[] for _ in range(n_output_bits)]
|
|
167
|
+
dontcares_by_output = [
|
|
168
|
+
[_bitstring_to_minterm(bitstring) for bitstring in invalid_inputs]
|
|
169
|
+
for _ in range(n_output_bits)
|
|
170
|
+
]
|
|
171
|
+
|
|
172
|
+
for bitstring in valid_inputs:
|
|
173
|
+
assignment = {
|
|
174
|
+
variable_order[i]: int(bitstring[i])
|
|
175
|
+
for i in range(len(variable_order))
|
|
176
|
+
}
|
|
177
|
+
matched_outputs = set()
|
|
178
|
+
for product, output_index in parsed_rules:
|
|
179
|
+
if _product_matches(product, assignment):
|
|
180
|
+
matched_outputs.add(output_index)
|
|
181
|
+
|
|
182
|
+
for output_index in matched_outputs:
|
|
183
|
+
output_code = output_codes[output_index]
|
|
184
|
+
for bit_index, bit_value in enumerate(output_code):
|
|
185
|
+
if bit_value == "1":
|
|
186
|
+
ones_by_output[bit_index].append(_bitstring_to_minterm(bitstring))
|
|
187
|
+
|
|
188
|
+
return ones_by_output, dontcares_by_output, parsed_rules
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
def _minimize_output_bit_sop(variables, ones, dontcares):
|
|
192
|
+
return SOPform(variables, ones, dontcares)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
def _sympy_expr_to_products(expr):
|
|
196
|
+
if expr == false:
|
|
197
|
+
return []
|
|
198
|
+
if expr == true:
|
|
199
|
+
return [[]]
|
|
200
|
+
|
|
201
|
+
terms = expr.args if isinstance(expr, Or) else (expr,)
|
|
202
|
+
products = []
|
|
203
|
+
for term in terms:
|
|
204
|
+
factors = term.args if isinstance(term, And) else (term,)
|
|
205
|
+
product = []
|
|
206
|
+
for factor in factors:
|
|
207
|
+
if isinstance(factor, Not):
|
|
208
|
+
product.append((str(factor.args[0]), 0))
|
|
209
|
+
else:
|
|
210
|
+
product.append((str(factor), 1))
|
|
211
|
+
products.append(product)
|
|
212
|
+
return products
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def _product_to_string(product):
|
|
216
|
+
if len(product) == 0:
|
|
217
|
+
return "1"
|
|
218
|
+
return " & ".join(
|
|
219
|
+
var_name if bit_value == 1 else f"~{var_name}"
|
|
220
|
+
for var_name, bit_value in product
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def _products_to_sop_string(products):
|
|
225
|
+
if len(products) == 0:
|
|
226
|
+
return "0"
|
|
227
|
+
terms = []
|
|
228
|
+
for product in products:
|
|
229
|
+
term = _product_to_string(product)
|
|
230
|
+
if len(product) > 1:
|
|
231
|
+
term = f"({term})"
|
|
232
|
+
terms.append(term)
|
|
233
|
+
return " | ".join(terms)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def _original_products_by_output(parsed_rules, output_partition, output_encoding):
|
|
237
|
+
output_codes = _output_codes(output_partition, output_encoding)
|
|
238
|
+
products_by_output = [[] for _ in range(len(output_codes[0]))]
|
|
239
|
+
for product, output_index in parsed_rules:
|
|
240
|
+
for bit_index, bit_value in enumerate(output_codes[output_index]):
|
|
241
|
+
if bit_value == "1":
|
|
242
|
+
products_by_output[bit_index].append(product)
|
|
243
|
+
return products_by_output
|
|
244
|
+
|
|
245
|
+
|
|
246
|
+
def _print_fuzzy_set_basis_mapping(input_partitions, output_partition, output_encoding):
|
|
247
|
+
print("Fuzzy-set basis-state mapping")
|
|
248
|
+
print(" Input registers use bitstrings in register order q[0]..q[n-1].")
|
|
249
|
+
for partition in input_partitions:
|
|
250
|
+
print(f" Input {partition.name}:")
|
|
251
|
+
for set_name, bitstring in partition.associate_quantum_states().items():
|
|
252
|
+
qubit_values = ", ".join(
|
|
253
|
+
f"{partition.name}_{index}={bit_value}"
|
|
254
|
+
for index, bit_value in enumerate(bitstring)
|
|
255
|
+
)
|
|
256
|
+
print(f" {set_name}: |{bitstring}> ({qubit_values})")
|
|
257
|
+
|
|
258
|
+
if output_encoding == "gray":
|
|
259
|
+
print(f" Output {output_partition.name} uses Gray-encoded register bits q[0]..q[n-1]:")
|
|
260
|
+
else:
|
|
261
|
+
print(f" Output {output_partition.name} uses one-hot register bits q[0]..q[n-1]:")
|
|
262
|
+
|
|
263
|
+
for output_index, set_name in enumerate(output_partition.sets):
|
|
264
|
+
bitstring = _output_code_for_index(
|
|
265
|
+
output_index,
|
|
266
|
+
output_partition,
|
|
267
|
+
output_encoding,
|
|
268
|
+
)
|
|
269
|
+
print(
|
|
270
|
+
f" {set_name}: |{bitstring}> "
|
|
271
|
+
f"({', '.join(f'{output_partition.name}_{i}={bit}' for i, bit in enumerate(bitstring))})"
|
|
272
|
+
)
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def _print_boolean_optimization_report(
|
|
276
|
+
input_partitions,
|
|
277
|
+
output_partition,
|
|
278
|
+
output_encoding,
|
|
279
|
+
optimization_data,
|
|
280
|
+
output_indices=None,
|
|
281
|
+
ancilla=False,
|
|
282
|
+
):
|
|
283
|
+
if output_indices is None:
|
|
284
|
+
output_indices = range(len(optimization_data["products_by_output"]))
|
|
285
|
+
|
|
286
|
+
original_products = _original_products_by_output(
|
|
287
|
+
optimization_data["parsed_rules"],
|
|
288
|
+
output_partition,
|
|
289
|
+
output_encoding,
|
|
290
|
+
)
|
|
291
|
+
|
|
292
|
+
_print_fuzzy_set_basis_mapping(input_partitions, output_partition, output_encoding)
|
|
293
|
+
print("Boolean minimization report")
|
|
294
|
+
for output_index in output_indices:
|
|
295
|
+
if output_encoding == "gray":
|
|
296
|
+
output_label = f"{output_partition.name}_bit_{output_index}"
|
|
297
|
+
else:
|
|
298
|
+
output_label = output_partition.sets[output_index]
|
|
299
|
+
optimized_products = optimization_data["products_by_output"][output_index]
|
|
300
|
+
products_are_disjoint = _products_are_disjoint_on_valid_inputs(
|
|
301
|
+
optimized_products,
|
|
302
|
+
optimization_data["valid_inputs"],
|
|
303
|
+
optimization_data["variable_order"],
|
|
304
|
+
)
|
|
305
|
+
if products_are_disjoint:
|
|
306
|
+
synthesis = "optimized SOP synthesized directly with MCX gates"
|
|
307
|
+
elif ancilla:
|
|
308
|
+
synthesis = "optimized SOP synthesized as OR-of-products with ancillas"
|
|
309
|
+
else:
|
|
310
|
+
synthesis = "optimized SOP products overlap; rule-by-rule fallback is used"
|
|
311
|
+
|
|
312
|
+
print(f"Output {output_partition.name}[{output_index}] ({output_label})")
|
|
313
|
+
print(f" Original SOP: {_products_to_sop_string(original_products[output_index])}")
|
|
314
|
+
print(f" Optimized SOP: {_products_to_sop_string(optimized_products)}")
|
|
315
|
+
print(f" Synthesis: {synthesis}")
|
|
316
|
+
|
|
317
|
+
|
|
318
|
+
def _apply_product_as_mcx(qc, product, target, var_to_qubit):
|
|
319
|
+
if len(product) == 0:
|
|
320
|
+
qc.x(target)
|
|
321
|
+
return
|
|
322
|
+
|
|
323
|
+
controls = [var_to_qubit[var_name] for var_name, _ in product]
|
|
324
|
+
negative_controls = [
|
|
325
|
+
var_to_qubit[var_name]
|
|
326
|
+
for var_name, bit_value in product
|
|
327
|
+
if bit_value == 0
|
|
328
|
+
]
|
|
329
|
+
|
|
330
|
+
for qubit in negative_controls:
|
|
331
|
+
qc.x(qubit)
|
|
332
|
+
|
|
333
|
+
if len(controls) == 1:
|
|
334
|
+
qc.cx(controls[0], target)
|
|
335
|
+
elif len(controls) == 2:
|
|
336
|
+
qc.ccx(controls[0], controls[1], target)
|
|
337
|
+
else:
|
|
338
|
+
qc.mcx(controls, target)
|
|
339
|
+
|
|
340
|
+
for qubit in reversed(negative_controls):
|
|
341
|
+
qc.x(qubit)
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
def _apply_product_to_ancilla(qc, product, ancilla, var_to_qubit):
|
|
345
|
+
_apply_product_as_mcx(qc, product, ancilla, var_to_qubit)
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
def _products_are_disjoint_on_valid_inputs(products, valid_inputs, variable_order):
|
|
349
|
+
for bitstring in valid_inputs:
|
|
350
|
+
assignment = {
|
|
351
|
+
variable_order[i]: int(bitstring[i])
|
|
352
|
+
for i in range(len(variable_order))
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
active_count = 0
|
|
356
|
+
for product in products:
|
|
357
|
+
if _product_matches(product, assignment):
|
|
358
|
+
active_count += 1
|
|
359
|
+
|
|
360
|
+
if active_count > 1:
|
|
361
|
+
return False
|
|
362
|
+
|
|
363
|
+
return True
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def _apply_or_many_to_target(qc, term_qubits, target):
|
|
367
|
+
if len(term_qubits) == 0:
|
|
368
|
+
return
|
|
369
|
+
|
|
370
|
+
if len(term_qubits) == 1:
|
|
371
|
+
qc.cx(term_qubits[0], target)
|
|
372
|
+
return
|
|
373
|
+
|
|
374
|
+
for qubit in term_qubits:
|
|
375
|
+
qc.x(qubit)
|
|
376
|
+
|
|
377
|
+
if len(term_qubits) == 2:
|
|
378
|
+
qc.ccx(term_qubits[0], term_qubits[1], target)
|
|
379
|
+
else:
|
|
380
|
+
qc.mcx(term_qubits, target)
|
|
381
|
+
|
|
382
|
+
qc.x(target)
|
|
383
|
+
|
|
384
|
+
for qubit in reversed(term_qubits):
|
|
385
|
+
qc.x(qubit)
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
def _synthesize_sop_to_target_with_ancillas(
|
|
389
|
+
qc,
|
|
390
|
+
products,
|
|
391
|
+
target,
|
|
392
|
+
var_to_qubit,
|
|
393
|
+
term_ancillas,
|
|
394
|
+
):
|
|
395
|
+
if len(products) == 0:
|
|
396
|
+
return
|
|
397
|
+
|
|
398
|
+
if len(products) == 1:
|
|
399
|
+
_apply_product_as_mcx(qc, products[0], target, var_to_qubit)
|
|
400
|
+
return
|
|
401
|
+
|
|
402
|
+
if len(term_ancillas) < len(products):
|
|
403
|
+
raise ValueError("Not enough ancillas for SOP synthesis.")
|
|
404
|
+
|
|
405
|
+
used_ancillas = term_ancillas[:len(products)]
|
|
406
|
+
|
|
407
|
+
for product, anc in zip(products, used_ancillas):
|
|
408
|
+
_apply_product_to_ancilla(qc, product, anc, var_to_qubit)
|
|
409
|
+
|
|
410
|
+
_apply_or_many_to_target(qc, used_ancillas, target)
|
|
411
|
+
|
|
412
|
+
for product, anc in reversed(list(zip(products, used_ancillas))):
|
|
413
|
+
_apply_product_to_ancilla(qc, product, anc, var_to_qubit)
|
|
414
|
+
|
|
415
|
+
|
|
416
|
+
def _build_optimization_data(qc, rules, input_partitions, output_partition, output_encoding, encoding):
|
|
417
|
+
qreg_sizes = {
|
|
418
|
+
partition.name: QFS.select_qreg_by_name(qc, partition.name).size
|
|
419
|
+
for partition in input_partitions
|
|
420
|
+
}
|
|
421
|
+
variable_order = []
|
|
422
|
+
var_to_qubit = {}
|
|
423
|
+
for partition in input_partitions:
|
|
424
|
+
qreg = QFS.select_qreg_by_name(qc, partition.name)
|
|
425
|
+
for qubit_index in range(qreg.size):
|
|
426
|
+
var_name = f"{partition.name}_{qubit_index}"
|
|
427
|
+
variable_order.append(var_name)
|
|
428
|
+
var_to_qubit[var_name] = qreg[qubit_index]
|
|
429
|
+
|
|
430
|
+
valid_inputs, invalid_inputs, _ = _collect_valid_and_invalid_input_codes(
|
|
431
|
+
input_partitions,
|
|
432
|
+
qreg_sizes,
|
|
433
|
+
)
|
|
434
|
+
ones_by_output, dontcares_by_output, parsed_rules = _build_rule_truth_table(
|
|
435
|
+
rules,
|
|
436
|
+
input_partitions,
|
|
437
|
+
output_partition,
|
|
438
|
+
output_encoding,
|
|
439
|
+
encoding,
|
|
440
|
+
valid_inputs,
|
|
441
|
+
invalid_inputs,
|
|
442
|
+
variable_order,
|
|
443
|
+
)
|
|
444
|
+
sympy_variables = symbols(" ".join(variable_order))
|
|
445
|
+
if len(variable_order) == 1:
|
|
446
|
+
sympy_variables = (sympy_variables,)
|
|
447
|
+
products_by_output = [
|
|
448
|
+
_sympy_expr_to_products(
|
|
449
|
+
_minimize_output_bit_sop(sympy_variables, ones, dontcares)
|
|
450
|
+
)
|
|
451
|
+
for ones, dontcares in zip(ones_by_output, dontcares_by_output)
|
|
452
|
+
]
|
|
453
|
+
|
|
454
|
+
return {
|
|
455
|
+
"products_by_output": products_by_output,
|
|
456
|
+
"parsed_rules": parsed_rules,
|
|
457
|
+
"valid_inputs": valid_inputs,
|
|
458
|
+
"variable_order": variable_order,
|
|
459
|
+
"var_to_qubit": var_to_qubit,
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
def _rules_for_output_index(parsed_rules, rules, output_index):
|
|
464
|
+
return [
|
|
465
|
+
rule
|
|
466
|
+
for rule, (_, rule_output_index) in zip(rules, parsed_rules)
|
|
467
|
+
if rule_output_index == output_index
|
|
468
|
+
]
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def _rules_for_output_bit(parsed_rules, rules, output_bit_index, output_partition, output_encoding):
|
|
472
|
+
return [
|
|
473
|
+
rule
|
|
474
|
+
for rule, (_, rule_output_index) in zip(rules, parsed_rules)
|
|
475
|
+
if _output_code_for_index(rule_output_index, output_partition, output_encoding)[output_bit_index] == "1"
|
|
476
|
+
]
|
|
477
|
+
|
|
478
|
+
|
|
479
|
+
def _apply_rule_with_encoded_output(
|
|
480
|
+
qc,
|
|
481
|
+
rule,
|
|
482
|
+
input_partitions,
|
|
483
|
+
output_partition,
|
|
484
|
+
output_encoding,
|
|
485
|
+
encoding,
|
|
486
|
+
var_to_qubit,
|
|
487
|
+
output_qreg,
|
|
488
|
+
):
|
|
489
|
+
product, output_index = _rule_product_and_output_index(
|
|
490
|
+
rule,
|
|
491
|
+
input_partitions,
|
|
492
|
+
output_partition,
|
|
493
|
+
encoding,
|
|
494
|
+
)
|
|
495
|
+
output_code = _output_code_for_index(output_index, output_partition, output_encoding)
|
|
496
|
+
applied_gate = False
|
|
497
|
+
for output_bit_index, bit_value in enumerate(output_code):
|
|
498
|
+
if bit_value == "1":
|
|
499
|
+
_apply_product_as_mcx(
|
|
500
|
+
qc,
|
|
501
|
+
product,
|
|
502
|
+
output_qreg[output_bit_index],
|
|
503
|
+
var_to_qubit,
|
|
504
|
+
)
|
|
505
|
+
applied_gate = True
|
|
506
|
+
return applied_gate
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def _var_to_qubit_for_inputs(qc, input_partitions):
|
|
510
|
+
var_to_qubit = {}
|
|
511
|
+
for partition in input_partitions:
|
|
512
|
+
qreg = QFS.select_qreg_by_name(qc, partition.name)
|
|
513
|
+
for qubit_index in range(qreg.size):
|
|
514
|
+
var_to_qubit[f"{partition.name}_{qubit_index}"] = qreg[qubit_index]
|
|
515
|
+
return var_to_qubit
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
class QuantumFuzzyEngine:
|
|
519
|
+
"""
|
|
520
|
+
|
|
521
|
+
Class implementing the Quantum Fuzzy Inference Engine proposed in:
|
|
522
|
+
|
|
523
|
+
G. Acampora, R. Schiattarella and A. Vitiello, "On the Implementation of Fuzzy Inference Engines on Quantum Computers,"
|
|
524
|
+
in IEEE Transactions on Fuzzy Systems, 2022, doi: 10.1109/TFUZZ.2022.3202348.
|
|
525
|
+
|
|
526
|
+
|
|
527
|
+
"""
|
|
528
|
+
|
|
529
|
+
def __init__(self, verbose=True, encoding='logaritmic'):
|
|
530
|
+
self.input_ranges = {}
|
|
531
|
+
self.output_range = {}
|
|
532
|
+
self.input_fuzzysets = {}
|
|
533
|
+
self.output_fuzzyset = {}
|
|
534
|
+
self.input_partitions = {}
|
|
535
|
+
self.output_partition = {}
|
|
536
|
+
self.variables = {}
|
|
537
|
+
self.rules = []
|
|
538
|
+
self.rule_subsets = {}
|
|
539
|
+
self.qc = {}
|
|
540
|
+
self.verbose = verbose
|
|
541
|
+
self.transpile_info = verbose
|
|
542
|
+
self.encoding = encoding
|
|
543
|
+
|
|
544
|
+
def input_variable(self, name, range):
|
|
545
|
+
"""Define the input variable "name" of the system.
|
|
546
|
+
|
|
547
|
+
Args:
|
|
548
|
+
name (str): Name of the variable as string.
|
|
549
|
+
range (np array): Universe of the discourse for the input variable.
|
|
550
|
+
|
|
551
|
+
Returns:
|
|
552
|
+
None
|
|
553
|
+
"""
|
|
554
|
+
if name in list(self.input_ranges.keys()):
|
|
555
|
+
raise Exception("Variable name must be unambiguos")
|
|
556
|
+
else:
|
|
557
|
+
self.input_ranges[name] = range
|
|
558
|
+
self.input_fuzzysets[name] = []
|
|
559
|
+
self.input_partitions[name] = ""
|
|
560
|
+
|
|
561
|
+
def output_variable(self, name, range):
|
|
562
|
+
"""Define the output variable "name" of the system.
|
|
563
|
+
|
|
564
|
+
Args:
|
|
565
|
+
name (str): Name of the variable as string.
|
|
566
|
+
range (np array): Universe of the discourse for the output variable.
|
|
567
|
+
|
|
568
|
+
Returns:
|
|
569
|
+
None
|
|
570
|
+
"""
|
|
571
|
+
self.output_range[name] = range
|
|
572
|
+
self.output_fuzzyset[name] = []
|
|
573
|
+
self.output_partition[name] = ""
|
|
574
|
+
|
|
575
|
+
def add_input_fuzzysets(self, var_name, set_names, sets):
|
|
576
|
+
"""Set the partition for the input fuzzy variable 'var_name'.
|
|
577
|
+
|
|
578
|
+
Args:
|
|
579
|
+
var_name (str): name of the fuzzy variable defined with input_variable method previously.
|
|
580
|
+
set_names (list): list of fuzzy sets' name as str.
|
|
581
|
+
sets (list): list of scikit-fuzzy membership function objects.
|
|
582
|
+
|
|
583
|
+
Returns:
|
|
584
|
+
None
|
|
585
|
+
"""
|
|
586
|
+
for set in sets:
|
|
587
|
+
self.input_fuzzysets[var_name].append(set)
|
|
588
|
+
self.input_partitions[var_name] = fp.fuzzy_partition(
|
|
589
|
+
var_name,
|
|
590
|
+
set_names,
|
|
591
|
+
encoding=self.encoding,
|
|
592
|
+
minimize_hamming=self.encoding == 'logaritmic',
|
|
593
|
+
)
|
|
594
|
+
|
|
595
|
+
def add_output_fuzzysets(self, var_name, set_names, sets):
|
|
596
|
+
"""Set the partition for the output fuzzy variable 'var_name'.
|
|
597
|
+
|
|
598
|
+
Args:
|
|
599
|
+
var_name (str): name of the fuzzy variable defined with output_variable method previously.
|
|
600
|
+
set_names (list): list of fuzzy sets' name as str.
|
|
601
|
+
sets (list): list of scikit-fuzzy membership function objects.
|
|
602
|
+
Returns:
|
|
603
|
+
None
|
|
604
|
+
"""
|
|
605
|
+
for set in sets:
|
|
606
|
+
self.output_fuzzyset[var_name].append(set)
|
|
607
|
+
self.output_partition[var_name] = fp.fuzzy_partition(var_name, set_names)
|
|
608
|
+
|
|
609
|
+
def set_rules(self, rules):
|
|
610
|
+
"""Set the rule-base of the system. \n
|
|
611
|
+
Rules must be formatted as follows: 'if var_1 is x_i and var_2 is x_k and and var_n is x_l then out_1 is y_k'
|
|
612
|
+
|
|
613
|
+
Args:
|
|
614
|
+
rules (list): list of rules as strings.
|
|
615
|
+
|
|
616
|
+
Returns:
|
|
617
|
+
None
|
|
618
|
+
"""
|
|
619
|
+
|
|
620
|
+
self.rules = rules
|
|
621
|
+
|
|
622
|
+
|
|
623
|
+
def filter_rules(self, rules, output_term):
|
|
624
|
+
"""Searches the rule list and picks only the rules corresponding to the same output value (y_k at fixed k). \n
|
|
625
|
+
Rules must be formatted as follows: 'if var_1 is x_i and var_2 is x_k and and var_n is x_l then out_1 is y_k'
|
|
626
|
+
|
|
627
|
+
Args:
|
|
628
|
+
rules (list): list of rules as strings.
|
|
629
|
+
output_term (str): single output term y_k at fixed k as string.
|
|
630
|
+
Returns:
|
|
631
|
+
Filtered rules as a new list.
|
|
632
|
+
"""
|
|
633
|
+
rules_subset = []
|
|
634
|
+
for rule in rules:
|
|
635
|
+
if f"then {list(self.output_fuzzyset.keys())[0]} is {output_term}" in rule:
|
|
636
|
+
rules_subset.append(rule)
|
|
637
|
+
return rules_subset
|
|
638
|
+
|
|
639
|
+
def truncate(self, n, decimals=0):
|
|
640
|
+
multiplier = 10**decimals
|
|
641
|
+
return math.floor(n * multiplier + 0.5) / multiplier
|
|
642
|
+
|
|
643
|
+
def counts_evaluator(self, n_qubits, counts):
|
|
644
|
+
"""Function returning the alpha values for alpha-cutting the output fuzzy sets according to the
|
|
645
|
+
probability of measuring the related basis states on the output quantum register.
|
|
646
|
+
|
|
647
|
+
Args:
|
|
648
|
+
n_qubits (int): number of qubits in the output quantum register.
|
|
649
|
+
counts (dict): counting dictionary of the output quantum register measurement.
|
|
650
|
+
|
|
651
|
+
Returns:
|
|
652
|
+
alpha values for alpha-cutting the output fuzzy sets as 'dict'.
|
|
653
|
+
"""
|
|
654
|
+
|
|
655
|
+
output = {}
|
|
656
|
+
n_shots = sum(list(counts.values()))
|
|
657
|
+
counts = {k: v / n_shots for k, v in counts.items()}
|
|
658
|
+
for i in range(n_qubits):
|
|
659
|
+
state = [0 * k for k in range(n_qubits)]
|
|
660
|
+
n = i + 1
|
|
661
|
+
state[-n] = 1
|
|
662
|
+
stringb = ""
|
|
663
|
+
for b in state:
|
|
664
|
+
stringb = str(b) + stringb
|
|
665
|
+
output[stringb] = 0
|
|
666
|
+
counts_keys = list(counts.keys())
|
|
667
|
+
for key in counts_keys:
|
|
668
|
+
if key in list(output.keys()):
|
|
669
|
+
output[key] = counts[key] + output[key]
|
|
670
|
+
else:
|
|
671
|
+
sum_1s = 0
|
|
672
|
+
for bit in key:
|
|
673
|
+
if bit == "1":
|
|
674
|
+
sum_1s = sum_1s + 1
|
|
675
|
+
for num_bit in range(n_qubits):
|
|
676
|
+
if key[num_bit] == "1":
|
|
677
|
+
for selected_state in list(output.keys()):
|
|
678
|
+
if selected_state[num_bit] == "1":
|
|
679
|
+
output[selected_state] = output[selected_state] + (
|
|
680
|
+
counts[key] / sum_1s
|
|
681
|
+
)
|
|
682
|
+
|
|
683
|
+
return output
|
|
684
|
+
|
|
685
|
+
def build_inference_qc(
|
|
686
|
+
self,
|
|
687
|
+
input_values,
|
|
688
|
+
distributed=False,
|
|
689
|
+
draw_qc=False,
|
|
690
|
+
optimize=False,
|
|
691
|
+
ancilla=False,
|
|
692
|
+
verbose=False,
|
|
693
|
+
output_encoding="one-hot",
|
|
694
|
+
**kwargs
|
|
695
|
+
):
|
|
696
|
+
"""This function builds the quantum circuit implementing the QFIE, initializing the input quantum registers
|
|
697
|
+
according to the 'input_value' argument.
|
|
698
|
+
|
|
699
|
+
Args:
|
|
700
|
+
input_values (dict): dictionary containing the crisp input values of the system.
|
|
701
|
+
E.g. {'var_name_1' (str): x_1 (float), , 'var_name_n' (str): x_n (float)}
|
|
702
|
+
draw_qc (Bool - default:False): True for drawing the quantum circuit built. False otherwise.
|
|
703
|
+
distributed (Boolean): True to implement the distributed version of the quantum oracle. False otherwise.
|
|
704
|
+
optimize (Boolean): True to minimize the Boolean function induced by the fuzzy rule base before
|
|
705
|
+
synthesizing each one-hot output bit. Invalid unused encodings are treated as don't-cares, while
|
|
706
|
+
valid inputs not covered by any rule preserve the current no-flip default behavior. Optimized
|
|
707
|
+
circuits preserve the original oracle behavior on valid input encodings.
|
|
708
|
+
ancilla (Boolean): Only used when optimize=True. True allows overlapping minimized SOP products
|
|
709
|
+
to be synthesized as a true OR-of-products network with temporary ancillas, which are uncomputed
|
|
710
|
+
to |0>. Disjoint products are still synthesized directly with MCX gates. With optimize=True and
|
|
711
|
+
ancilla=False, overlapping products fall back to the original rule-by-rule construction and emit
|
|
712
|
+
a RuntimeWarning.
|
|
713
|
+
verbose (Boolean): Only used when optimize=True. True prints, for each output bit, the original
|
|
714
|
+
SOP induced by the rules, the minimized SOP, and the synthesis strategy selected.
|
|
715
|
+
output_encoding (str): 'one-hot' keeps the legacy one-hot output register with one target qubit per
|
|
716
|
+
output fuzzy set. 'gray' uses a compressed Gray-encoded output register and builds one Boolean
|
|
717
|
+
function for each output-code bit.
|
|
718
|
+
:keyword filename (str): file path to save image to.
|
|
719
|
+
|
|
720
|
+
Returns:
|
|
721
|
+
None
|
|
722
|
+
"""
|
|
723
|
+
if output_encoding not in ("one-hot", "gray"):
|
|
724
|
+
raise ValueError("output_encoding must be 'one-hot' or 'gray'.")
|
|
725
|
+
if distributed and output_encoding != "one-hot":
|
|
726
|
+
raise NotImplementedError(
|
|
727
|
+
"Distributed QFIE is supported only with output_encoding='one-hot'."
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
self.distributed = distributed
|
|
731
|
+
self.output_encoding = output_encoding
|
|
732
|
+
self.transpile_info = verbose
|
|
733
|
+
|
|
734
|
+
# Print Crisp Inputs
|
|
735
|
+
if self.verbose:
|
|
736
|
+
print(input_values)
|
|
737
|
+
|
|
738
|
+
# FUZZIFICATION
|
|
739
|
+
fuzzyfied_values = {}
|
|
740
|
+
for var_name in list(input_values.keys()):
|
|
741
|
+
fuzzyfied_values[var_name] = [
|
|
742
|
+
fuzz.interp_membership(
|
|
743
|
+
self.input_ranges[var_name], i, input_values[var_name]
|
|
744
|
+
)
|
|
745
|
+
for i in self.input_fuzzysets[var_name]
|
|
746
|
+
]
|
|
747
|
+
if self.verbose:
|
|
748
|
+
print("Input values ", fuzzyfied_values)
|
|
749
|
+
|
|
750
|
+
# CIRCUIT SETUPS
|
|
751
|
+
# Not Distributed QFIE
|
|
752
|
+
if not distributed:
|
|
753
|
+
self.qc["full_circuit"] = QFS.generate_circuit(
|
|
754
|
+
list(self.input_partitions.values()), encoding = self.encoding
|
|
755
|
+
)
|
|
756
|
+
self.qc["full_circuit"] = QFS.output_register(
|
|
757
|
+
self.qc["full_circuit"],
|
|
758
|
+
list(self.output_partition.values())[0],
|
|
759
|
+
output_encoding=output_encoding,
|
|
760
|
+
)
|
|
761
|
+
# Distributed QFIE
|
|
762
|
+
else:
|
|
763
|
+
self.out_register_name = []
|
|
764
|
+
# Use output linguistic terms as labels (keys) to identify the corresponding distributed circuits
|
|
765
|
+
qc_labels = self.output_partition[list(self.output_fuzzyset.keys())[0]].sets
|
|
766
|
+
for label in qc_labels:
|
|
767
|
+
# Create a quantum circuit corresponding to each label
|
|
768
|
+
self.qc[label] = QFS.generate_circuit(
|
|
769
|
+
list(self.input_partitions.values()), encoding = self.encoding
|
|
770
|
+
)
|
|
771
|
+
self.qc[label] = QFS.output_single_qubit_register(self.qc[label], label)
|
|
772
|
+
# Create a subset of rules corresponding to each label
|
|
773
|
+
self.rule_subsets[label] = self.filter_rules(self.rules, label)
|
|
774
|
+
|
|
775
|
+
# COMPUTING AMPLITUDES FROM FUZZIFIED VALUES
|
|
776
|
+
initial_state = {}
|
|
777
|
+
for var_name in list(input_values.keys()):
|
|
778
|
+
if self.encoding == 'logaritmic':
|
|
779
|
+
required_len = QFS.select_qreg_by_name(
|
|
780
|
+
list(self.qc.values())[0], var_name
|
|
781
|
+
).size
|
|
782
|
+
initial_state[var_name] = [0 for _ in range(2**required_len)]
|
|
783
|
+
used_indexes = set()
|
|
784
|
+
quantum_states = self.input_partitions[var_name].associate_quantum_states()
|
|
785
|
+
set_names = self.input_partitions[var_name].sets
|
|
786
|
+
for set_index, set_name in enumerate(set_names):
|
|
787
|
+
bitstring = _padded_code(
|
|
788
|
+
quantum_states[set_name],
|
|
789
|
+
required_len,
|
|
790
|
+
)
|
|
791
|
+
basis_index = int(bitstring[::-1], 2)
|
|
792
|
+
used_indexes.add(basis_index)
|
|
793
|
+
initial_state[var_name][basis_index] = math.sqrt(
|
|
794
|
+
fuzzyfied_values[var_name][set_index]
|
|
795
|
+
)
|
|
796
|
+
|
|
797
|
+
default_indexes = [
|
|
798
|
+
index
|
|
799
|
+
for index in range(2**required_len)
|
|
800
|
+
if index not in used_indexes
|
|
801
|
+
]
|
|
802
|
+
if default_indexes:
|
|
803
|
+
initial_state[var_name][default_indexes[0]] = math.sqrt(
|
|
804
|
+
1 - sum(fuzzyfied_values[var_name])
|
|
805
|
+
)
|
|
806
|
+
for circ in list(self.qc.values()):
|
|
807
|
+
circ.initialize(
|
|
808
|
+
initial_state[var_name], QFS.select_qreg_by_name(circ, var_name)
|
|
809
|
+
)
|
|
810
|
+
|
|
811
|
+
if self.encoding == 'linear':
|
|
812
|
+
|
|
813
|
+
def linear_encoding(fuzzified_values):
|
|
814
|
+
#print(sum(fuzzified_values).__round__(6))
|
|
815
|
+
input_list = [math.sqrt(i) for i in fuzzified_values]
|
|
816
|
+
n = len(input_list) # Number of input elements
|
|
817
|
+
output_size = 2 ** n # Size of the output list
|
|
818
|
+
output_list = [0] * output_size # Initialize the output list with zeros
|
|
819
|
+
|
|
820
|
+
for i in range(n):
|
|
821
|
+
# Find the index that corresponds to the binary string with only the i-th bit set to 1
|
|
822
|
+
index = 1 << i # This is equivalent to 2**i
|
|
823
|
+
output_list[index] = input_list[i] # Substitute the value from the input list
|
|
824
|
+
|
|
825
|
+
return output_list
|
|
826
|
+
|
|
827
|
+
initial_state[var_name] = linear_encoding(fuzzyfied_values[var_name])
|
|
828
|
+
initial_state[var_name][0] = math.sqrt(1 - sum(fuzzyfied_values[var_name]))
|
|
829
|
+
for circ in list(self.qc.values()):
|
|
830
|
+
circ.initialize(
|
|
831
|
+
initial_state[var_name], QFS.select_qreg_by_name(circ, var_name)
|
|
832
|
+
)
|
|
833
|
+
#print(Statevector(circ).probabilities_dict())
|
|
834
|
+
#print(self.qc['full_circuit'])
|
|
835
|
+
#print('stop')
|
|
836
|
+
|
|
837
|
+
|
|
838
|
+
# BUILDING ORACLES
|
|
839
|
+
if not distributed:
|
|
840
|
+
output_partition = list(self.output_partition.values())[0]
|
|
841
|
+
input_partitions = list(self.input_partitions.values())
|
|
842
|
+
if not optimize:
|
|
843
|
+
if output_encoding == "one-hot":
|
|
844
|
+
for rule in self.rules:
|
|
845
|
+
QFS.convert_rule(
|
|
846
|
+
qc=self.qc["full_circuit"],
|
|
847
|
+
fuzzy_rule=rule,
|
|
848
|
+
partitions=input_partitions,
|
|
849
|
+
output_partition=output_partition,
|
|
850
|
+
encoding=self.encoding
|
|
851
|
+
)
|
|
852
|
+
self.qc["full_circuit"].barrier()
|
|
853
|
+
else:
|
|
854
|
+
output_qreg = QFS.select_qreg_by_name(
|
|
855
|
+
self.qc["full_circuit"],
|
|
856
|
+
output_partition.name,
|
|
857
|
+
)
|
|
858
|
+
var_to_qubit = _var_to_qubit_for_inputs(
|
|
859
|
+
self.qc["full_circuit"],
|
|
860
|
+
input_partitions,
|
|
861
|
+
)
|
|
862
|
+
for rule in self.rules:
|
|
863
|
+
applied_gate = _apply_rule_with_encoded_output(
|
|
864
|
+
self.qc["full_circuit"],
|
|
865
|
+
rule,
|
|
866
|
+
input_partitions,
|
|
867
|
+
output_partition,
|
|
868
|
+
output_encoding,
|
|
869
|
+
self.encoding,
|
|
870
|
+
var_to_qubit,
|
|
871
|
+
output_qreg,
|
|
872
|
+
)
|
|
873
|
+
if applied_gate:
|
|
874
|
+
self.qc["full_circuit"].barrier()
|
|
875
|
+
else:
|
|
876
|
+
optimization_data = _build_optimization_data(
|
|
877
|
+
self.qc["full_circuit"],
|
|
878
|
+
self.rules,
|
|
879
|
+
input_partitions,
|
|
880
|
+
output_partition,
|
|
881
|
+
output_encoding,
|
|
882
|
+
self.encoding,
|
|
883
|
+
)
|
|
884
|
+
if verbose:
|
|
885
|
+
_print_boolean_optimization_report(
|
|
886
|
+
input_partitions,
|
|
887
|
+
output_partition,
|
|
888
|
+
output_encoding,
|
|
889
|
+
optimization_data,
|
|
890
|
+
ancilla=ancilla,
|
|
891
|
+
)
|
|
892
|
+
products_by_output = optimization_data["products_by_output"]
|
|
893
|
+
term_ancillas = []
|
|
894
|
+
if ancilla:
|
|
895
|
+
max_products = max(
|
|
896
|
+
[
|
|
897
|
+
len(products)
|
|
898
|
+
for products in products_by_output
|
|
899
|
+
if not _products_are_disjoint_on_valid_inputs(
|
|
900
|
+
products,
|
|
901
|
+
optimization_data["valid_inputs"],
|
|
902
|
+
optimization_data["variable_order"],
|
|
903
|
+
)
|
|
904
|
+
],
|
|
905
|
+
default=0,
|
|
906
|
+
)
|
|
907
|
+
if max_products > 1:
|
|
908
|
+
anc = QuantumRegister(max_products, "anc")
|
|
909
|
+
self.qc["full_circuit"].add_register(anc)
|
|
910
|
+
term_ancillas = list(anc)
|
|
911
|
+
|
|
912
|
+
output_qreg = QFS.select_qreg_by_name(
|
|
913
|
+
self.qc["full_circuit"],
|
|
914
|
+
output_partition.name,
|
|
915
|
+
)
|
|
916
|
+
for output_index, products in enumerate(products_by_output):
|
|
917
|
+
applied_gate = False
|
|
918
|
+
added_barrier = False
|
|
919
|
+
products_are_disjoint = _products_are_disjoint_on_valid_inputs(
|
|
920
|
+
products,
|
|
921
|
+
optimization_data["valid_inputs"],
|
|
922
|
+
optimization_data["variable_order"],
|
|
923
|
+
)
|
|
924
|
+
if products_are_disjoint:
|
|
925
|
+
for product in products:
|
|
926
|
+
_apply_product_as_mcx(
|
|
927
|
+
self.qc["full_circuit"],
|
|
928
|
+
product,
|
|
929
|
+
output_qreg[output_index],
|
|
930
|
+
optimization_data["var_to_qubit"],
|
|
931
|
+
)
|
|
932
|
+
applied_gate = True
|
|
933
|
+
elif ancilla:
|
|
934
|
+
_synthesize_sop_to_target_with_ancillas(
|
|
935
|
+
self.qc["full_circuit"],
|
|
936
|
+
products,
|
|
937
|
+
output_qreg[output_index],
|
|
938
|
+
optimization_data["var_to_qubit"],
|
|
939
|
+
term_ancillas,
|
|
940
|
+
)
|
|
941
|
+
applied_gate = len(products) > 0
|
|
942
|
+
else:
|
|
943
|
+
warnings.warn(
|
|
944
|
+
"Optimized SOP products overlap on valid inputs; "
|
|
945
|
+
"falling back to original rule-by-rule synthesis for this output bit. "
|
|
946
|
+
"Use ancilla=True to synthesize the minimized SOP as an OR network.",
|
|
947
|
+
RuntimeWarning,
|
|
948
|
+
)
|
|
949
|
+
if output_encoding == "one-hot":
|
|
950
|
+
fallback_rules = _rules_for_output_index(
|
|
951
|
+
optimization_data["parsed_rules"],
|
|
952
|
+
self.rules,
|
|
953
|
+
output_index,
|
|
954
|
+
)
|
|
955
|
+
for rule in fallback_rules:
|
|
956
|
+
QFS.convert_rule(
|
|
957
|
+
qc=self.qc["full_circuit"],
|
|
958
|
+
fuzzy_rule=rule,
|
|
959
|
+
partitions=input_partitions,
|
|
960
|
+
output_partition=output_partition,
|
|
961
|
+
encoding=self.encoding
|
|
962
|
+
)
|
|
963
|
+
self.qc["full_circuit"].barrier()
|
|
964
|
+
applied_gate = True
|
|
965
|
+
added_barrier = True
|
|
966
|
+
else:
|
|
967
|
+
fallback_rules = _rules_for_output_bit(
|
|
968
|
+
optimization_data["parsed_rules"],
|
|
969
|
+
self.rules,
|
|
970
|
+
output_index,
|
|
971
|
+
output_partition,
|
|
972
|
+
output_encoding,
|
|
973
|
+
)
|
|
974
|
+
for rule in fallback_rules:
|
|
975
|
+
product, _ = _rule_product_and_output_index(
|
|
976
|
+
rule,
|
|
977
|
+
input_partitions,
|
|
978
|
+
output_partition,
|
|
979
|
+
self.encoding,
|
|
980
|
+
)
|
|
981
|
+
_apply_product_as_mcx(
|
|
982
|
+
self.qc["full_circuit"],
|
|
983
|
+
product,
|
|
984
|
+
output_qreg[output_index],
|
|
985
|
+
optimization_data["var_to_qubit"],
|
|
986
|
+
)
|
|
987
|
+
applied_gate = True
|
|
988
|
+
if applied_gate:
|
|
989
|
+
self.qc["full_circuit"].barrier()
|
|
990
|
+
added_barrier = True
|
|
991
|
+
if draw_qc and applied_gate and not added_barrier:
|
|
992
|
+
self.qc["full_circuit"].barrier()
|
|
993
|
+
|
|
994
|
+
self.out_register_name = list(self.output_fuzzyset.keys())[0]
|
|
995
|
+
output_register = QFS.select_qreg_by_name(
|
|
996
|
+
self.qc["full_circuit"],
|
|
997
|
+
self.out_register_name,
|
|
998
|
+
)
|
|
999
|
+
out = ClassicalRegister(output_register.size)
|
|
1000
|
+
self.qc["full_circuit"].add_register(out)
|
|
1001
|
+
self.qc["full_circuit"].measure(
|
|
1002
|
+
output_register,
|
|
1003
|
+
out,
|
|
1004
|
+
)
|
|
1005
|
+
if draw_qc:
|
|
1006
|
+
print('draw')
|
|
1007
|
+
if "filename" in kwargs:
|
|
1008
|
+
self.qc["full_circuit"].draw(
|
|
1009
|
+
"mpl",
|
|
1010
|
+
filename=_prepare_draw_filename(kwargs["filename"]),
|
|
1011
|
+
)
|
|
1012
|
+
else:
|
|
1013
|
+
print('draw1')
|
|
1014
|
+
self.qc["full_circuit"].draw("mpl").show()
|
|
1015
|
+
else:
|
|
1016
|
+
self.out_register_name = []
|
|
1017
|
+
# Use output linguistic terms as labels (keys) to identify the corresponding distributed circuits
|
|
1018
|
+
qc_labels = self.output_partition[list(self.output_fuzzyset.keys())[0]].sets
|
|
1019
|
+
output_partition = list(self.output_partition.values())[0]
|
|
1020
|
+
input_partitions = list(self.input_partitions.values())
|
|
1021
|
+
for label in qc_labels:
|
|
1022
|
+
modified_output_partition = deepcopy(
|
|
1023
|
+
output_partition
|
|
1024
|
+
)
|
|
1025
|
+
modified_output_partition.sets = [label]
|
|
1026
|
+
if not optimize:
|
|
1027
|
+
for rule in self.rule_subsets[label]:
|
|
1028
|
+
QFS.convert_rule(
|
|
1029
|
+
qc=self.qc[label],
|
|
1030
|
+
fuzzy_rule=rule,
|
|
1031
|
+
partitions=input_partitions,
|
|
1032
|
+
output_partition=modified_output_partition,
|
|
1033
|
+
encoding=self.encoding
|
|
1034
|
+
)
|
|
1035
|
+
self.qc[label].barrier()
|
|
1036
|
+
else:
|
|
1037
|
+
optimization_data = _build_optimization_data(
|
|
1038
|
+
self.qc[label],
|
|
1039
|
+
self.rules,
|
|
1040
|
+
input_partitions,
|
|
1041
|
+
output_partition,
|
|
1042
|
+
output_encoding,
|
|
1043
|
+
self.encoding,
|
|
1044
|
+
)
|
|
1045
|
+
label_output_index = output_partition.sets.index(label)
|
|
1046
|
+
if verbose:
|
|
1047
|
+
_print_boolean_optimization_report(
|
|
1048
|
+
input_partitions,
|
|
1049
|
+
output_partition,
|
|
1050
|
+
output_encoding,
|
|
1051
|
+
optimization_data,
|
|
1052
|
+
output_indices=[label_output_index],
|
|
1053
|
+
ancilla=ancilla,
|
|
1054
|
+
)
|
|
1055
|
+
products = optimization_data["products_by_output"][label_output_index]
|
|
1056
|
+
products_are_disjoint = _products_are_disjoint_on_valid_inputs(
|
|
1057
|
+
products,
|
|
1058
|
+
optimization_data["valid_inputs"],
|
|
1059
|
+
optimization_data["variable_order"],
|
|
1060
|
+
)
|
|
1061
|
+
term_ancillas = []
|
|
1062
|
+
if ancilla and not products_are_disjoint and len(products) > 1:
|
|
1063
|
+
anc = QuantumRegister(len(products), "anc")
|
|
1064
|
+
self.qc[label].add_register(anc)
|
|
1065
|
+
term_ancillas = list(anc)
|
|
1066
|
+
|
|
1067
|
+
output_qreg = QFS.select_qreg_by_name(self.qc[label], label)
|
|
1068
|
+
if products_are_disjoint:
|
|
1069
|
+
for product in products:
|
|
1070
|
+
_apply_product_as_mcx(
|
|
1071
|
+
self.qc[label],
|
|
1072
|
+
product,
|
|
1073
|
+
output_qreg[0],
|
|
1074
|
+
optimization_data["var_to_qubit"],
|
|
1075
|
+
)
|
|
1076
|
+
elif ancilla:
|
|
1077
|
+
_synthesize_sop_to_target_with_ancillas(
|
|
1078
|
+
self.qc[label],
|
|
1079
|
+
products,
|
|
1080
|
+
output_qreg[0],
|
|
1081
|
+
optimization_data["var_to_qubit"],
|
|
1082
|
+
term_ancillas,
|
|
1083
|
+
)
|
|
1084
|
+
else:
|
|
1085
|
+
warnings.warn(
|
|
1086
|
+
"Optimized SOP products overlap on valid inputs; "
|
|
1087
|
+
"falling back to original rule-by-rule synthesis for this output bit. "
|
|
1088
|
+
"Use ancilla=True to synthesize the minimized SOP as an OR network.",
|
|
1089
|
+
RuntimeWarning,
|
|
1090
|
+
)
|
|
1091
|
+
for rule in self.rule_subsets[label]:
|
|
1092
|
+
QFS.convert_rule(
|
|
1093
|
+
qc=self.qc[label],
|
|
1094
|
+
fuzzy_rule=rule,
|
|
1095
|
+
partitions=input_partitions,
|
|
1096
|
+
output_partition=modified_output_partition,
|
|
1097
|
+
encoding=self.encoding
|
|
1098
|
+
)
|
|
1099
|
+
self.qc[label].barrier()
|
|
1100
|
+
self.out_register_name.append(
|
|
1101
|
+
list(self.output_fuzzyset.keys())[0] + " " + label
|
|
1102
|
+
)
|
|
1103
|
+
out = ClassicalRegister(1)
|
|
1104
|
+
self.qc[label].add_register(out)
|
|
1105
|
+
self.qc[label].measure(
|
|
1106
|
+
QFS.select_qreg_by_name(self.qc[label], self.out_register_name[-1]),
|
|
1107
|
+
out,
|
|
1108
|
+
)
|
|
1109
|
+
if draw_qc:
|
|
1110
|
+
#self.qc[label].draw("mpl").show()
|
|
1111
|
+
if "filename" in kwargs:
|
|
1112
|
+
self.qc[label].draw(
|
|
1113
|
+
"mpl",
|
|
1114
|
+
filename=_prepare_draw_filename(kwargs["filename"], label),
|
|
1115
|
+
)
|
|
1116
|
+
else:
|
|
1117
|
+
self.qc[label].draw("mpl")
|
|
1118
|
+
|
|
1119
|
+
def execute(self, n_shots: int, plot_histo=False, GPU=False, **kwargs):
|
|
1120
|
+
"""Run the inference engine.
|
|
1121
|
+
|
|
1122
|
+
Args:
|
|
1123
|
+
n_shots (int): Number of shots.
|
|
1124
|
+
plot_histo (Bool- default False): True for plotting the counts histogram.
|
|
1125
|
+
GPU (Bool- default False): True for using GPU for simulation. Use False if backend is a real device.
|
|
1126
|
+
|
|
1127
|
+
:keyword backend: quantum backend to run the quantum circuit. If not specified, qasm simulator is used.
|
|
1128
|
+
:keyword transpile_info (bool): True for getting information about transpiled qc.
|
|
1129
|
+
If not specified, defaults to the verbose value passed to the latest build_inference_qc call.
|
|
1130
|
+
:keyword optimization_level (int - default 3): Select a Value from 1 to 3 to set the optimization level in the transpiling
|
|
1131
|
+
:keyword defuzzification (str): name of the Defuzzification algorithm to use. If not specified, 'centroid' is used.
|
|
1132
|
+
Return:
|
|
1133
|
+
Crisp output of the system.
|
|
1134
|
+
"""
|
|
1135
|
+
# Selecting the backend
|
|
1136
|
+
if "backend" in kwargs:
|
|
1137
|
+
backend = kwargs["backend"]
|
|
1138
|
+
else:
|
|
1139
|
+
if AerSimulator is None:
|
|
1140
|
+
raise ImportError(
|
|
1141
|
+
"qiskit_aer is required when execute() is called without a backend. "
|
|
1142
|
+
"Install qiskit-aer or pass a backend explicitly."
|
|
1143
|
+
)
|
|
1144
|
+
backend = AerSimulator()
|
|
1145
|
+
|
|
1146
|
+
#Checking Transpilation Command
|
|
1147
|
+
if "transpile_info" in kwargs:
|
|
1148
|
+
transp_info = bool(kwargs["transpile_info"])
|
|
1149
|
+
else:
|
|
1150
|
+
transp_info = bool(self.transpile_info)
|
|
1151
|
+
|
|
1152
|
+
if "optimization_level" in kwargs and kwargs["optimization_level"] != 3: optimization_level = kwargs["optimization_level"]
|
|
1153
|
+
else: optimization_level = 3
|
|
1154
|
+
|
|
1155
|
+
|
|
1156
|
+
|
|
1157
|
+
|
|
1158
|
+
# Creating backend list if QFIE is distributed:
|
|
1159
|
+
if self.distributed:
|
|
1160
|
+
if type(backend) != list: backends_list=[backend]
|
|
1161
|
+
else: backends_list = backend
|
|
1162
|
+
backends_list = list(islice(cycle(backends_list), len(list(self.qc.keys()))))
|
|
1163
|
+
|
|
1164
|
+
if GPU:
|
|
1165
|
+
try:
|
|
1166
|
+
backend.set_options(device="GPU")
|
|
1167
|
+
except:
|
|
1168
|
+
print(
|
|
1169
|
+
"Not possible use GPU for this quantum backend or your device is not equipped with GPUs"
|
|
1170
|
+
)
|
|
1171
|
+
|
|
1172
|
+
# COMPUTE NOT DISTRIBUTED ALGORITHM
|
|
1173
|
+
if len(self.qc) == 1:
|
|
1174
|
+
if type(backend) == list:
|
|
1175
|
+
raise 'Please to run the not distributed quantum circuit specify an unique backend not as list'
|
|
1176
|
+
|
|
1177
|
+
# Execute quantum circuit
|
|
1178
|
+
self.counts_ = list(QFS.compute_qc(backend, self.qc["full_circuit"], "full_circuit", n_shots, self.verbose, transpilation_info=transp_info, optimization_level=optimization_level).values())[0]
|
|
1179
|
+
|
|
1180
|
+
# COMPUTE DISTRIBUTED ALGORITHM
|
|
1181
|
+
else:
|
|
1182
|
+
# Distributed version
|
|
1183
|
+
subcounts = {}
|
|
1184
|
+
|
|
1185
|
+
# Execute quantum circuits
|
|
1186
|
+
counts_list = list(map(QFS.compute_qc, backends_list,
|
|
1187
|
+
list(self.qc.values()), list(self.qc.keys()),
|
|
1188
|
+
repeat(n_shots), repeat(self.verbose),
|
|
1189
|
+
repeat(transp_info), repeat(optimization_level)))
|
|
1190
|
+
|
|
1191
|
+
for count in counts_list:
|
|
1192
|
+
subcounts.update(count)
|
|
1193
|
+
|
|
1194
|
+
self.counts_ = QFS.merge_subcounts(
|
|
1195
|
+
subcounts, self.output_partition[list(self.output_fuzzyset.keys())[0]]
|
|
1196
|
+
)
|
|
1197
|
+
|
|
1198
|
+
# Plot Counts
|
|
1199
|
+
if plot_histo:
|
|
1200
|
+
plot_histogram(
|
|
1201
|
+
self.counts_, color="midnightblue", figsize=(7, 10)
|
|
1202
|
+
).show()
|
|
1203
|
+
|
|
1204
|
+
output_partition = self.output_partition[list(self.output_fuzzyset.keys())[0]]
|
|
1205
|
+
if getattr(self, "output_encoding", "one-hot") == "gray":
|
|
1206
|
+
self.n_q = _output_register_size(output_partition, "gray")
|
|
1207
|
+
n_shots = sum(list(self.counts_.values()))
|
|
1208
|
+
normalized_counts = {
|
|
1209
|
+
key: value / n_shots
|
|
1210
|
+
for key, value in self.counts_.items()
|
|
1211
|
+
}
|
|
1212
|
+
output_dict = {
|
|
1213
|
+
set_name: _output_code_for_index(
|
|
1214
|
+
output_index,
|
|
1215
|
+
output_partition,
|
|
1216
|
+
"gray",
|
|
1217
|
+
)[::-1]
|
|
1218
|
+
for output_index, set_name in enumerate(output_partition.sets)
|
|
1219
|
+
}
|
|
1220
|
+
else:
|
|
1221
|
+
self.n_q = len(self.output_fuzzyset[list(self.output_fuzzyset.keys())[0]])
|
|
1222
|
+
counts = self.counts_evaluator(n_qubits=self.n_q, counts=self.counts_)
|
|
1223
|
+
normalized_counts = counts
|
|
1224
|
+
output_dict = {
|
|
1225
|
+
i: []
|
|
1226
|
+
for i in output_partition.sets
|
|
1227
|
+
}
|
|
1228
|
+
|
|
1229
|
+
counter = 0
|
|
1230
|
+
for set in list(output_dict.keys()):
|
|
1231
|
+
counter = counter + 1
|
|
1232
|
+
for i in range(self.n_q):
|
|
1233
|
+
if i == self.n_q - counter:
|
|
1234
|
+
output_dict[set].append("1")
|
|
1235
|
+
else:
|
|
1236
|
+
output_dict[set].append("0")
|
|
1237
|
+
output_dict[set] = "".join(output_dict[set])
|
|
1238
|
+
|
|
1239
|
+
memberships = {}
|
|
1240
|
+
for state in list(output_dict.values()):
|
|
1241
|
+
if state in list(normalized_counts.keys()):
|
|
1242
|
+
memberships[state] = normalized_counts[state]
|
|
1243
|
+
else:
|
|
1244
|
+
memberships[state] = 0
|
|
1245
|
+
|
|
1246
|
+
# DEFUZZIFICATION
|
|
1247
|
+
if "defuzzification" in kwargs:
|
|
1248
|
+
defuzz = kwargs["defuzzification"]
|
|
1249
|
+
else: defuzz = 'centroid'
|
|
1250
|
+
|
|
1251
|
+
norm_memberships = memberships
|
|
1252
|
+
if self.verbose:
|
|
1253
|
+
print("Output Counts", memberships)
|
|
1254
|
+
activation = {}
|
|
1255
|
+
set_number = 0
|
|
1256
|
+
for set in list(output_dict.keys()):
|
|
1257
|
+
activation[set] = np.fmin(
|
|
1258
|
+
norm_memberships[output_dict[set]],
|
|
1259
|
+
self.output_fuzzyset[list(self.output_fuzzyset.keys())[0]][set_number],
|
|
1260
|
+
)
|
|
1261
|
+
set_number = set_number + 1
|
|
1262
|
+
|
|
1263
|
+
activation_values = list(activation.values())[::-1]
|
|
1264
|
+
aggregated = np.zeros(
|
|
1265
|
+
self.output_fuzzyset[list(self.output_fuzzyset.keys())[0]][0].shape
|
|
1266
|
+
)
|
|
1267
|
+
for i in range(len(activation_values)):
|
|
1268
|
+
aggregated = np.fmax(aggregated, activation_values[i])
|
|
1269
|
+
|
|
1270
|
+
return (
|
|
1271
|
+
fuzz.defuzz(
|
|
1272
|
+
self.output_range[list(self.output_fuzzyset.keys())[0]],
|
|
1273
|
+
aggregated,
|
|
1274
|
+
defuzz,
|
|
1275
|
+
),
|
|
1276
|
+
activation_values,
|
|
1277
|
+
)
|
|
1278
|
+
|
|
1279
|
+
|
|
1280
|
+
"""
|
|
1281
|
+
env_light = np.linspace(120, 220, 200)
|
|
1282
|
+
changing_rate = np.linspace(-10, 10, 200)
|
|
1283
|
+
dimmer_control = np.linspace(0, 10, 200)
|
|
1284
|
+
|
|
1285
|
+
|
|
1286
|
+
|
|
1287
|
+
l_dark = fuzz.trapmf(env_light, [120,120,130,150])
|
|
1288
|
+
l_medium = fuzz.trapmf(env_light, [130, 150, 190,210])
|
|
1289
|
+
l_light = fuzz.trapmf(env_light, [190, 210, 220, 220])
|
|
1290
|
+
|
|
1291
|
+
r_ns = fuzz.trimf(changing_rate, [-10,-10,0])
|
|
1292
|
+
r_zero = fuzz.trimf(changing_rate, [-10,0,10])
|
|
1293
|
+
r_ps = fuzz.trimf(changing_rate, [0,10,10])
|
|
1294
|
+
|
|
1295
|
+
dm_vs = fuzz.trapmf(dimmer_control, [0,0,2,4])
|
|
1296
|
+
dm_s = fuzz.trimf(dimmer_control, [2,4,6])
|
|
1297
|
+
dm_b = fuzz.trimf(dimmer_control, [4,6,8])
|
|
1298
|
+
dm_vb = fuzz.trapmf(dimmer_control, [6,8,10,10])
|
|
1299
|
+
|
|
1300
|
+
'''rules = ['if env_light is dark and change_rate is pos_small then dimmer_ctrl is big',
|
|
1301
|
+
'if env_light is dark and change_rate is zero then dimmer_ctrl is big',
|
|
1302
|
+
'if env_light is dark and change_rate is neg_small then dimmer_ctrl is very_big',
|
|
1303
|
+
'if env_light is medium and change_rate is pos_small then dimmer_ctrl is small',
|
|
1304
|
+
'if env_light is medium and change_rate is zero then dimmer_ctrl is big',
|
|
1305
|
+
'if env_light is medium and change_rate is neg_small then dimmer_ctrl is big',
|
|
1306
|
+
'if env_light is light and change_rate is pos_small then dimmer_ctrl is very_small',
|
|
1307
|
+
'if env_light is light and change_rate is zero then dimmer_ctrl is small',
|
|
1308
|
+
'if env_light is light and change_rate is neg_small then dimmer_ctrl is big']'''
|
|
1309
|
+
|
|
1310
|
+
rules = ['if env_light is dark and change_rate is not neg_small then dimmer_ctrl is big',
|
|
1311
|
+
'if env_light is dark and change_rate is neg_small then dimmer_ctrl is very_big',
|
|
1312
|
+
'if env_light is medium and change_rate is not pos_small then dimmer_ctrl is big',
|
|
1313
|
+
'if env_light is medium and change_rate is pos_small then dimmer_ctrl is small',
|
|
1314
|
+
'if env_light is light and change_rate is pos_small then dimmer_ctrl is very_small',
|
|
1315
|
+
'if env_light is light and change_rate is zero then dimmer_ctrl is small',
|
|
1316
|
+
'if env_light is light and change_rate is neg_small then dimmer_ctrl is big']
|
|
1317
|
+
|
|
1318
|
+
qfie = QuantumFuzzyEngine(verbose=False, encoding='linear')
|
|
1319
|
+
qfie.input_variable(name='env_light', range=env_light)
|
|
1320
|
+
qfie.input_variable(name='change_rate', range=changing_rate)
|
|
1321
|
+
qfie.output_variable(name='dimmer_ctrl', range=dimmer_control)
|
|
1322
|
+
|
|
1323
|
+
qfie.add_input_fuzzysets(var_name='env_light', set_names=['dark', 'medium', 'light'], sets=[l_dark, l_medium, l_light])
|
|
1324
|
+
qfie.add_input_fuzzysets(var_name='change_rate', set_names=['neg_small', 'zero', 'pos_small'], sets=[r_ns, r_zero, r_ps])
|
|
1325
|
+
qfie.add_output_fuzzysets(var_name='dimmer_ctrl', set_names=['very_small', 'small', 'big', 'very_big'],sets=[dm_vs, dm_s, dm_b, dm_vb])
|
|
1326
|
+
qfie.set_rules(rules)
|
|
1327
|
+
qfie.build_inference_qc({'env_light':170, 'change_rate':0}, encoding='linear', draw_qc=False, distributed=True)
|
|
1328
|
+
print(qfie.qc['very_big'])
|
|
1329
|
+
print('end')
|
|
1330
|
+
"""
|