""" This module implements the base class for setting up the quantum fuzzy inference engine proposed in doi: 10.1109/TFUZZ.2022.3202348. """
import numpy as np
import skfuzzy as fuzz
import math
from qiskit import (
ClassicalRegister,
execute,
BasicAer,
)
from qiskit.visualization import plot_histogram
from . import fuzzy_partitions as fp
from . import QFS as QFS
[docs]class QuantumFuzzyEngine:
"""
Class implementing the Quantum Fuzzy Inference Engine proposed in:
G. Acampora, R. Schiattarella and A. Vitiello, "On the Implementation of Fuzzy Inference Engines on Quantum Computers,"
in IEEE Transactions on Fuzzy Systems, 2022, doi: 10.1109/TFUZZ.2022.3202348.
"""
def __init__(self, verbose = True):
self.input_ranges = {}
self.output_range = {}
self.input_fuzzysets = {}
self.output_fuzzyset = {}
self.input_partitions = {}
self.output_partition = {}
self.variables = {}
self.rules = []
self.qc = ""
self.verbose = verbose
[docs] def output_variable(self, name, range):
"""Define the output variable "name" of the system.
Args:
name (str): Name of the variable as string.
range (np array): Universe of the discourse for the output variable.
Returns:
None
"""
self.output_range[name] = range
self.output_fuzzyset[name] = []
self.output_partition[name] = ""
[docs] def add_output_fuzzysets(self, var_name, set_names, sets):
"""
Set the partition for the output fuzzy variable 'var_name'.
Args:
var_name (str): name of the fuzzy variable defined with output_variable method previously.
set_names (list): list of fuzzy sets' name as str.
sets (list): list of scikit-fuzzy membership function objects.
Returns:
None
"""
for set in sets:
self.output_fuzzyset[var_name].append(set)
self.output_partition[var_name] = fp.fuzzy_partition(var_name, set_names)
[docs] def set_rules(self, rules):
"""Set the rule-base of the system. \n
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'
Args:
rules (list): list of rules as strings.
Returns:
None
"""
self.rules = rules
[docs] def truncate(self, n, decimals=0):
multiplier = 10**decimals
return math.floor(n * multiplier + 0.5) / multiplier
[docs] def counts_evaluator(self, n_qubits, counts):
"""Function returning the alpha values for alpha-cutting the output fuzzy sets according to the
probability of measuring the related basis states on the output quantum register.
Args:
n_qubits (int): number of qubits in the output quantum register.
counts (dict): counting dictionary of the output quantum register measurement.
Returns:
alpha values for alpha-cutting the output fuzzy sets as 'dict'.
"""
output = {}
n_shots = sum(list(counts.values()))
counts = {k: v / n_shots for k, v in counts.items()}
for i in range(n_qubits):
state = [0 * k for k in range(n_qubits)]
n = i + 1
state[-n] = 1
stringb = ""
for b in state:
stringb = str(b) + stringb
output[stringb] = 0
counts_keys = list(counts.keys())
for key in counts_keys:
if key in list(output.keys()):
output[key] = counts[key] + output[key]
else:
sum_1s = 0
for bit in key:
if bit == "1":
sum_1s = sum_1s + 1
for num_bit in range(n_qubits):
if key[num_bit] == "1":
for selected_state in list(output.keys()):
if selected_state[num_bit] == "1":
output[selected_state] = output[selected_state] + (
counts[key] / sum_1s
)
return output
[docs] def build_inference_qc(self, input_values, draw_qc=False):
""" This function builds the quantum circuit implementing the QFIE, initializing the input quantum registers
according to the 'input_value' argument.
Args:
input_values (dict): dictionary containing the crisp input values of the system.
E.g. {'var_name_1' (str): x_1 (float), ..., 'var_name_n' (str): x_n (float)}
draw_qc (Boolean): True for drawing the quantum circuit built. False otherwise.
Returns:
None
"""
self.qc = QFS.generate_circuit(list(self.input_partitions.values()))
self.qc = QFS.output_register(self.qc, list(self.output_partition.values())[0])
if self.verbose:
print(input_values)
fuzzyfied_values = {}
norm_values = {}
for var_name in list(input_values.keys()):
fuzzyfied_values[var_name] = [
fuzz.interp_membership(
self.input_ranges[var_name], i, input_values[var_name]
)
for i in self.input_fuzzysets[var_name]
]
# norm_values[var_name] = [self.truncate(float(i)/sum(fuzzyfied_values[var_name]), 3) for i in fuzzyfied_values[var_name]]
if self.verbose:
print("Input values ", fuzzyfied_values)
initial_state = {}
for var_name in list(input_values.keys()):
initial_state[var_name] = [
math.sqrt(fuzzyfied_values[var_name][i])
for i in range(len(fuzzyfied_values[var_name]))
]
required_len = QFS.select_qreg_by_name(self.qc, var_name).size
while len(initial_state[var_name]) != 2**required_len:
initial_state[var_name].append(0)
initial_state[var_name][-1] = math.sqrt(1 - sum(fuzzyfied_values[var_name]))
# print(initial_state)
self.qc.initialize(
initial_state[var_name], QFS.select_qreg_by_name(self.qc, var_name)
)
for rule in self.rules:
QFS.convert_rule(
qc=self.qc,
fuzzy_rule=rule,
partitions=list(self.input_partitions.values()),
output_partition=list(self.output_partition.values())[0],
)
self.qc.barrier()
self.out_register_name = list(self.output_fuzzyset.keys())[0]
out = ClassicalRegister(len(self.output_fuzzyset[self.out_register_name]))
self.qc.add_register(out)
self.qc.measure(QFS.select_qreg_by_name(self.qc, self.out_register_name), out)
if draw_qc:
self.qc.draw("mpl").show()
[docs] def execute(self, backend_name, n_shots, provider=None, plot_histo=False, GPU = False):
""" Run the inference engine.
Args:
backend_name (str): IBMQ backend to use for computing.\n
- Use "qasm_simulator" to simulate the run.\n
- For real devices an IBMQ provider is required.
n_shots (int): Number of shots.
provider (str): IBMQ Provider.\n
- Default 'None' to use with 'qasm_simulator' backend
plot_histo (Boolean): True for plotting the counts histogram. False Otherwise.
GPU (Boolean): True for using GPU for simulation. Use False if backend is a real device.
Return:
Crisp output of the system.
"""
if backend_name == "qasm_simulator":
backend = BasicAer.get_backend(backend_name)
else:
backend = provider.get_backend(backend_name)
if GPU:
backend.set_options(device='GPU')
job = execute(self.qc, backend, shots=n_shots)
result = job.result()
if plot_histo:
plot_histogram(
job.result().get_counts(), color="midnightblue", figsize=(7, 10)
).show()
self.counts_ = job.result().get_counts()
self.n_q = len(self.output_fuzzyset[self.out_register_name])
counts = self.counts_evaluator(n_qubits=self.n_q, counts=self.counts_)
# normalized_counts = {k: v / total for total in (sum(counts.values()),) for k, v in counts.items()}
normalized_counts = counts
output_dict = {
i: [] for i in self.output_partition[self.out_register_name].sets
}
counter = 0
for set in list(output_dict.keys()):
counter = counter + 1
for i in range(self.n_q):
if i == self.n_q - counter:
output_dict[set].append("1")
else:
output_dict[set].append("0")
output_dict[set] = "".join(output_dict[set])
memberships = {}
for state in list(output_dict.values()):
if state in list(normalized_counts.keys()):
memberships[state] = normalized_counts[state]
else:
memberships[state] = 0
norm_memberships = memberships
if self.verbose:
print("Output Counts", memberships)
activation = {}
set_number = 0
for set in list(output_dict.keys()):
activation[set] = np.fmin(
norm_memberships[output_dict[set]],
self.output_fuzzyset[self.out_register_name][set_number],
)
set_number = set_number + 1
activation_values = list(activation.values())[::-1]
aggregated = np.zeros(self.output_fuzzyset[self.out_register_name][0].shape)
for i in range(len(activation_values)):
aggregated = np.fmax(aggregated, activation_values[i])
return (
fuzz.defuzz(
self.output_range[self.out_register_name], aggregated, "centroid"
),
activation_values,
)