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 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
+ """