From c42ea8b11da4057e2a43ae115d8dc1a67d66c94b Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Mon, 10 Jun 2024 11:47:10 +0200 Subject: [PATCH 01/10] add qiskit solvers for coloring problem + first version of optuna example for mis and coloring --- .../coloring/solvers/coloring_quantum.py | 141 ++++++++++++ .../generic_tools/qiskit_tools.py | 134 +++++++++--- examples/qiskit_examples/coloring_example.py | 43 ++++ .../qiskit_examples/qiskit_optuna_coloring.py | 196 +++++++++++++++++ examples/qiskit_examples/qiskit_optuna_mis.py | 203 ++++++++++++++++++ 5 files changed, 682 insertions(+), 35 deletions(-) create mode 100644 discrete_optimization/coloring/solvers/coloring_quantum.py create mode 100644 examples/qiskit_examples/coloring_example.py create mode 100644 examples/qiskit_examples/qiskit_optuna_coloring.py create mode 100644 examples/qiskit_examples/qiskit_optuna_mis.py diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py new file mode 100644 index 000000000..b1108d04a --- /dev/null +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -0,0 +1,141 @@ +from typing import Optional, Union + +import numpy as np +from qiskit_optimization import QuadraticProgram +from qiskit_optimization.algorithms import OptimizationResult +from qiskit_optimization.applications import OptimizationApplication + +from discrete_optimization.coloring.coloring_model import ColoringProblem, ColoringSolution +from discrete_optimization.coloring.solvers.coloring_solver import SolverColoring +from discrete_optimization.generic_tools.qiskit_tools import QiskitQAOASolver, QiskitVQESolver +from discrete_optimization.generic_tools.do_problem import ParamsObjectiveFunction, Solution + + +class ColoringQiskit(OptimizationApplication): + + def __init__(self, problem: ColoringProblem, nb_max_color=None) -> None: + """ + Args: + problem : the coloring problem instance + """ + super().__init__(problem) + self.problem = problem + if nb_max_color is None: + nb_max_color = self.problem.number_of_nodes + self.nb_max_color = nb_max_color + self.nb_variable = self.problem.number_of_nodes * self.nb_max_color + self.nb_max_color + + def to_quadratic_program(self) -> QuadraticProgram: + quadratic_program = QuadraticProgram() + + # TODO supprimer les X_i_j et se ramener à un problème "peut on colorer nos noeuds avec n nolors?" ?? + # TODO faire les deux ?? + # serait plus adapté pour qaoa ?? + + # X_i,j == 1 si le noeud i prend la couleur j + # C_j == 1 si la couleur j est choisit au moins une fois + + var_names = {} + for i in range(0, self.nb_max_color): + for j in range(0, self.problem.number_of_nodes): + x_new = quadratic_program.binary_var("x" + str(j) + str(i)) + var_names[(j, i)] = x_new.name + color_new = quadratic_program.binary_var("color" + str(i)) + var_names[i] = color_new.name + + # on cherche à minimiser le nombre de couleurs utilisées + + constant = 0 + linear = {} + quadratic = {} + + for i in range(0, self.nb_max_color): + quadratic[var_names[i], var_names[i]] = 1 + + """ + On va ici intégrer sous forme de pénalité les différentes contraintes afin d'avoir directement une formulation QUBO + x <= y devient P(x-xy) + x1 + ... + xi = 1 devient P(-x1 + ... + -xi + 2x1x2 + ... + 2x1xi + 2x2x3 + .... + 2x2xi + ... + 2x(i-1)xi) + x + y <= 1 devient P(xy) + où P est un scalaire qui doit idéalement être ni trop petit, ni trop grand (ici on prend le nombre de couleur max autorisé) + """ + + p = self.nb_max_color + + # si une couleur j est attribué à un noeud, la contrainte C_j doit valoir 1 + for i in range(0, self.problem.number_of_nodes): + for j in range(0, self.nb_max_color): + # quadratic[var_names[(i, j)], var_names[(i, j)]] = p + quadratic[var_names[(i, j)], var_names[j]] = -p + + # chaque noeud doit avoir une unique couleur + for i in range(0, self.problem.number_of_nodes): + for j in range(0, self.nb_max_color): + # quadratic[var_names[(i, j)], var_names[(i, j)]] = -p + for k in range(j + 1, self.nb_max_color): + quadratic[var_names[(i, j)], var_names[(i, k)]] = p + + # deux noeuds adjacents ne peuvent avoir la même couleur + for edge in self.problem.graph.graph_nx.edges(): + for j in range(0, self.nb_max_color): + quadratic[var_names[(self.problem.index_nodes_name[edge[0]], j)], var_names[ + (self.problem.index_nodes_name[edge[1]], j)]] = p + + quadratic_program.minimize(constant, linear, quadratic) + + return quadratic_program + + def interpret(self, result: Union[OptimizationResult, np.ndarray]): + + x = self._result_to_x(result) + + colors = [0] * self.problem.number_of_nodes + nb_color = 0 + + for node in range(0, self.problem.number_of_nodes): + color_find = False + color = 0 + while not color_find and color < self.nb_max_color: + if x[self.problem.number_of_nodes * color + node + color] == 1: + colors[node] = color + color_find = True + color += 1 + + # TODO think about what we want to do when a node has no color + + for color in range(0, self.nb_max_color): + if x[self.problem.number_of_nodes * color + self.problem.number_of_nodes + color] == 1: + nb_color += 1 + + sol = ColoringSolution(self.problem, colors=colors, nb_color=nb_color) + + return sol + + +class QAOAColoringSolver(SolverColoring, QiskitQAOASolver): + + def __init__(self, problem: ColoringProblem, params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_max_color=None): + super().__init__(problem, params_objective_function) + self.coloring_qiskit = ColoringQiskit(problem, nb_max_color=nb_max_color) + + def init_model(self): + self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() + + def retrieve_current_solution(self, result) -> Solution: + return self.coloring_qiskit.interpret(result) + + +class VQEColoringSolver(SolverColoring, QiskitVQESolver): + + def __init__(self, problem: ColoringProblem, params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_max_color=None): + super().__init__(problem, params_objective_function) + self.coloring_qiskit = ColoringQiskit(problem, nb_max_color=nb_max_color) + + def init_model(self): + self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() + self.nb_variable = self.coloring_qiskit.nb_variable + + def retrieve_current_solution(self, result) -> Solution: + return self.coloring_qiskit.interpret(result) diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index 20b45eaea..50043e087 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -12,6 +12,9 @@ Solution, ) from discrete_optimization.generic_tools.do_solver import SolverDO +from discrete_optimization.generic_tools.hyperparameters.hyperparameter import IntegerHyperparameter, \ + CategoricalHyperparameter, FloatHyperparameter +from discrete_optimization.generic_tools.hyperparameters.hyperparametrizable import Hyperparametrizable from discrete_optimization.generic_tools.result_storage.result_storage import ( ResultStorage, ) @@ -102,23 +105,36 @@ def cost_func(params, ansatz, hamiltonian, estimator, callback_dict): def execute_ansatz_with_Hamiltonian( - backend, ansatz, hamiltonian, **kwargs + backend, ansatz, hamiltonian, use_session: Optional[bool] = False, **kwargs ) -> np.ndarray: """ @param backend: the backend use to run the circuit (simulator or real device) @param ansatz: the quantum circuit @param hamiltonian: the hamiltonian corresponding to the problem - @param kwargs: a list of parameters who can be specified + @param use_session: boolean to set to True for use a session + @param kwargs: a list of hyperparameters who can be specified @return: the qubit's value the must often chose """ if backend is None: backend = AerSimulator() - with_session = kwargs.get("with_session", False) - optimization_level = kwargs.get("optimization_level", 3) - nb_shots = kwargs.get("nb_shot", 10000) - maxiter = kwargs.get("maxiter", None) - disp = kwargs.get("disp", False) + """ + if use_session: + print("To use a session you need to use a real device not a simulator") + use_session = False + """ + + optimization_level = kwargs["optimization_level"] + method = kwargs["method"] + nb_shots = kwargs["nb_shots"] + + if kwargs["options"]: + options = kwargs["options"] + else: + if method == "COBYLA": + options = {"maxiter": kwargs["maxiter"], "rhobeg": kwargs["rhobeg"]} + else: + options = {} # transpile and optimize the quantum circuit depending on the device who are going to use # there are four level_optimization, to 0 to 3, 3 is the better but also the longest @@ -129,9 +145,10 @@ def execute_ansatz_with_Hamiltonian( ansatz = pm.run(ansatz) hamiltonian = hamiltonian.apply_layout(ansatz.layout) - # open a session - if with_session: + # open a session if desired + if use_session: session = Session(backend=backend, max_time="2h") + # TODO what happend if we create a session for a simulator? crash or not? else: session = None @@ -153,9 +170,9 @@ def execute_ansatz_with_Hamiltonian( cost_func, validate_initial_point(point=None, circuit=ansatz), args=(ansatz, hamiltonian, estimator, callback_dict), - method="COBYLA", + method=method, bounds=validate_bounds(ansatz), - options={"maxiter": maxiter, "disp": disp}, + options=options, ) # Assign solution parameters to our ansatz @@ -170,32 +187,66 @@ def execute_ansatz_with_Hamiltonian( result = get_result_from_dict_result(results[0].data.meas.get_counts()) # Close the session since we are now done with it - if with_session: + if use_session: # with_session: session.close() return result -class QiskitQAOASolver(SolverDO): +class QiskitSolver(SolverDO): + def __init__( - self, - problem: Problem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, - backend: Optional = None, - **kwargs: Any, + self, + problem: Problem, + params_objective_function: Optional[ParamsObjectiveFunction] = None + ): + super().__init__(problem, params_objective_function) + + +class QiskitQAOASolver(QiskitSolver, Hyperparametrizable): + + kwargs = {"step": 50} + + hyperparameters = [ + IntegerHyperparameter( + name="reps", low=1, high=6, default=2 + ), + IntegerHyperparameter( + name="optimization_level", low=0, high=3, default=1 + ), + CategoricalHyperparameter( + name="method", choices=["COBYLA"], default="COBYLA" + ), + IntegerHyperparameter( + name="maxiter", low=100, high=1000, step=50, default=300 + ), + FloatHyperparameter( + name="rhobeg", low=0.5, high=1.5, default=1. + ), + # TODO 3 hyperparam Cobyla : rhobeg, tol ??, maxiter + ] + + def __init__( + self, + problem: Problem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + backend: Optional = None ): super().__init__(problem, params_objective_function) self.quadratic_programm = None self.backend = backend def solve( - self, - callbacks: Optional[List[Callback]] = None, - backend: Optional = None, - **kwargs: Any, + self, + callbacks: Optional[List[Callback]] = None, + backend: Optional = None, + use_session: Optional[bool] = False, + **kwargs: Any, ) -> ResultStorage: - reps = kwargs.get("reps", 2) + kwargs = self.complete_with_default_hyperparameters(kwargs) + + reps = kwargs["reps"] if backend is not None: self.backend = backend @@ -213,7 +264,7 @@ def solve( ansatz = QAOAAnsatz(hamiltonian, reps=reps) result = execute_ansatz_with_Hamiltonian( - self.backend, ansatz, hamiltonian, **kwargs + self.backend, ansatz, hamiltonian, use_session, **kwargs ) result = conv.interpret(result) @@ -241,13 +292,22 @@ def retrieve_current_solution(self, result) -> Solution: ... -class QiskitVQESolver(SolverDO): +class QiskitVQESolver(QiskitSolver): + + hyperparameters = [ + IntegerHyperparameter( + name="optimization_level", low=0, high=3, default=1 + ), + CategoricalHyperparameter( + name="method", choices=["COBYLA"], default="COBYLA" + ), + ] + def __init__( - self, - problem: Problem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, - backend: Optional = None, - **kwargs: Any, + self, + problem: Problem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + backend: Optional = None ): super().__init__(problem, params_objective_function) self.quadratic_programm = None @@ -255,12 +315,15 @@ def __init__( self.backend = backend def solve( - self, - callbacks: Optional[List[Callback]] = None, - backend: Optional = None, - **kwargs: Any, + self, + callbacks: Optional[List[Callback]] = None, + backend: Optional = None, + use_session: Optional[bool] = False, + **kwargs: Any, ) -> ResultStorage: + kwargs = self.complete_with_default_hyperparameters(kwargs) + if backend is not None: self.backend = backend @@ -281,7 +344,7 @@ def solve( ansatz = EfficientSU2(hamiltonian.num_qubits) result = execute_ansatz_with_Hamiltonian( - self.backend, ansatz, hamiltonian, **kwargs + self.backend, ansatz, hamiltonian, use_session, **kwargs ) result = conv.interpret(result) @@ -307,3 +370,4 @@ def retrieve_current_solution(self, result) -> Solution: """ ... + diff --git a/examples/qiskit_examples/coloring_example.py b/examples/qiskit_examples/coloring_example.py new file mode 100644 index 000000000..edb0b23ed --- /dev/null +++ b/examples/qiskit_examples/coloring_example.py @@ -0,0 +1,43 @@ +from qiskit_aer import AerSimulator + +from discrete_optimization.coloring.coloring_model import ColoringProblem +from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver +from discrete_optimization.generic_tools.graph_api import Graph + + +def quantum_coloring(): + + """ + in this example we solve a small coloring problem using a quantum hybrid algorithm : QAOA + this algorithm is an approximate algorithm and it's not deterministic + """ + + # We construct a graph with 4 nodes and three edges, two colors are sufficiant + nodes = [(1, {}), (2, {}), (3, {}), (4, {})] + edges = [(1, 2, {}), (1, 3, {}), (2, 4, {})] + + # can make the problem unsat + the number of variable depend on this parameter + nb_max_color = 2 + + # we create an instance of ColoringProblem + coloringProblem = ColoringProblem(Graph(nodes=nodes, edges=edges)) + # we create an instance of a QAOAMisSolver + coloringSolver = QAOAColoringSolver(coloringProblem, nb_max_color=nb_max_color) + # we initialize the solver, in fact this step transform the problem in a QUBO formulation + coloringSolver.init_model() + # we solve the mis problem + """ + by default we use a quantum simulator to solve the problem, a AerSimulator() but it's possible to use + any backend (the same simulator with defined parameters, an other simulator or any real quantum device we can + use as a qiskit backend) + for this you have just to define your own backend and then pass it at the creation of the solver or + when you use the solve function of the solver + """ + backend = AerSimulator() + kwargs = {"reps": 4, "optimization_level": 1, "maxiter": 300} + res = coloringSolver.solve(backend=backend, **kwargs) + + sol, fit = res.get_best_solution_fit() + print(sol) + print("Two nodes connected by an edge have never the same color : ", coloringProblem.satisfy(sol)) + diff --git a/examples/qiskit_examples/qiskit_optuna_coloring.py b/examples/qiskit_examples/qiskit_optuna_coloring.py new file mode 100644 index 000000000..778dea951 --- /dev/null +++ b/examples/qiskit_examples/qiskit_optuna_coloring.py @@ -0,0 +1,196 @@ +# Copyright (c) 2024 AIRBUS and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +"""Example using OPTUNA to choose a solving method and tune its hyperparameters for quantum solvers. + +Results can be viewed on optuna-dashboard with: + + optuna-dashboard optuna-journal.log + +""" + +import os + +os.environ["DO_SKIP_MZN_CHECK"] = "1" + +from discrete_optimization.coloring.coloring_model import ColoringProblem +from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver, VQEColoringSolver +from discrete_optimization.generic_tools.graph_api import Graph +from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver +import logging +import time +from typing import Any, Dict, List, Type, Tuple + +import optuna +from optuna import Trial +from optuna.storages import JournalFileStorage, JournalStorage +from optuna.trial import TrialState + +from discrete_optimization.generic_tools.callbacks.loggers import ObjectiveLogger +from discrete_optimization.generic_tools.callbacks.optuna import OptunaCallback +from discrete_optimization.generic_tools.do_problem import ModeOptim +from discrete_optimization.generic_tools.do_solver import SolverDO + +from discrete_optimization.generic_tools.optuna.timed_percentile_pruner import ( + TimedPercentilePruner, +) + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s") + +seed = 42 +optuna_nb_trials = 100 + +create_another_study = True # avoid relaunching the same study, keep the previous ones +max_time_per_solver = 60 # max duration (s) +min_time_per_solver = 5 # min duration before pruning (s) + +nodes = [(1, {}), (2, {}), (3, {}), (4, {})] +edges = [(1, 2, {}), (1, 3, {}), (2, 4, {})] +nb_max_color = 2 + +# we create an instance of ColoringProblem +problem = ColoringProblem(Graph(nodes=nodes, edges=edges)) + +modelfilename = "coloring_quantum_solvers" + +suffix = f"-{time.time()}" if create_another_study else "" +study_name = f"{modelfilename}{suffix}" +storage_path = "./optuna-journal.log" # NFS path for distributed optimization +elapsed_time_attr = "elapsed_time" # name of the user attribute used to store duration of trials (updated during intermediate reports) + + +solvers: Dict[str, List[Tuple[Type[QiskitSolver], Dict[str, Any]]]] = { + "qaoa": [ + ( + QAOAColoringSolver, + { + }, + ), + ], + "vqe": [ + ( + VQEColoringSolver, + { + }, + ), + ] +} + +solvers_map = {} +for key in solvers: + for solver, param in solvers[key]: + solvers_map[solver] = (key, param) + +solvers_to_test: List[Type[SolverDO]] = [ + s for s in solvers_map +] + +# we need to map the classes to a unique string, to be seen as a categorical hyperparameter by optuna +# by default, we use the class name, but if there are identical names, f"{cls.__module__}.{cls.__name__}" could be used. +solvers_by_name: Dict[str, Type[SolverDO]] = { + cls.__name__: cls for cls in solvers_to_test +} + +# sense of optimization +objective_register = problem.get_objective_register() +if objective_register.objective_sense == ModeOptim.MINIMIZATION: + direction = "minimize" +else: + direction = "maximize" + + +def objective(trial: Trial): + # hyperparameters to test + + # first parameter: solver choice + solver_name: str = trial.suggest_categorical("solver", choices=solvers_by_name) + solver_class = solvers_by_name[solver_name] + + # hyperparameters for the chosen solver + suggested_hyperparameters_kwargs = solver_class.suggest_hyperparameters_with_optuna( + trial=trial, prefix=solver_name + "." + ) + + # use existing value if corresponding to a previous complete trial + """ + states_to_consider = (TrialState.COMPLETE,) + trials_to_consider = trial.study.get_trials( + deepcopy=False, states=states_to_consider + ) + for t in reversed(trials_to_consider): + if trial.params == t.params: + logger.warning( + "Trial with same hyperparameters as a previous complete trial: returning previous fit." + ) + return t.value + """ + # prune if corresponding to a previous failed trial + states_to_consider = (TrialState.FAIL,) + trials_to_consider = trial.study.get_trials( + deepcopy=False, states=states_to_consider + ) + for t in reversed(trials_to_consider): + if trial.params == t.params: + raise optuna.TrialPruned( + "Pruning trial identical to a previous failed trial." + ) + + logger.info(f"Launching trial {trial.number} with parameters: {trial.params}") + + # construct kwargs for __init__, init_model, and solve + kwargs = {} + kwargs.update(suggested_hyperparameters_kwargs) + + # solver init + solver = solver_class(problem=problem, nb_max_color=nb_max_color) + solver.init_model() + + # init timer + starting_time = time.perf_counter() + + # solve + res = solver.solve( + callbacks=[ + OptunaCallback( + trial=trial, + starting_time=starting_time, + elapsed_time_attr=elapsed_time_attr, + report_time=True, + ), + ObjectiveLogger( + step_verbosity_level=logging.INFO, end_verbosity_level=logging.INFO + ), + ], + **kwargs, + ) + + # store elapsed time + elapsed_time = time.perf_counter() - starting_time + trial.set_user_attr(elapsed_time_attr, elapsed_time) + + if len(res.list_solution_fits) != 0: + sol, fit = res.get_best_solution_fit() + trial.set_user_attr("satisfy", problem.satisfy(sol)) + trial.set_user_attr("color by node", sol.colors) + trial.set_user_attr("nb_colors", sol.nb_color) + return fit + else: + raise optuna.TrialPruned("Pruned because failed") + + +# create study + database to store it +storage = "sqlite:///example.db" +study = optuna.create_study( + study_name=study_name, + direction=direction, + sampler=optuna.samplers.TPESampler(seed=seed), + pruner=TimedPercentilePruner( # intermediate values interpolated at same "step" + percentile=50, # median pruner + n_warmup_steps=min_time_per_solver, # no pruning during first seconds + ), + storage=storage, + load_if_exists=True, +) +study.set_metric_names(["nb_color"]) +study.optimize(objective, n_trials=optuna_nb_trials) diff --git a/examples/qiskit_examples/qiskit_optuna_mis.py b/examples/qiskit_examples/qiskit_optuna_mis.py new file mode 100644 index 000000000..3ac95ce58 --- /dev/null +++ b/examples/qiskit_examples/qiskit_optuna_mis.py @@ -0,0 +1,203 @@ +# Copyright (c) 2024 AIRBUS and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. +"""Example using OPTUNA to choose a solving method and tune its hyperparameters for quantum solvers. + +Results can be viewed on optuna-dashboard with: + + optuna-dashboard optuna-journal.log + +""" + +import os + +os.environ["DO_SKIP_MZN_CHECK"] = "1" + +import networkx as nx + +from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver +from discrete_optimization.maximum_independent_set.solvers.mis_quantum import QAOAMisSolver, VQEMisSolver +from discrete_optimization.maximum_independent_set.mis_model import MisProblem +import logging +import time +from typing import Any, Dict, List, Type, Tuple + +import optuna +from optuna import Trial +from optuna.storages import JournalFileStorage, JournalStorage +from optuna.trial import TrialState + +from discrete_optimization.generic_tools.callbacks.loggers import ObjectiveLogger +from discrete_optimization.generic_tools.callbacks.optuna import OptunaCallback +from discrete_optimization.generic_tools.do_problem import ModeOptim +from discrete_optimization.generic_tools.do_solver import SolverDO + +from discrete_optimization.generic_tools.optuna.timed_percentile_pruner import ( + TimedPercentilePruner, +) + +logger = logging.getLogger(__name__) +logging.basicConfig(level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s") + +seed = 42 +optuna_nb_trials = 100 + +create_another_study = True # avoid relaunching the same study, keep the previous ones +max_time_per_solver = 60 # max duration (s) +min_time_per_solver = 5 # min duration before pruning (s) + +graph = nx.Graph() + +graph.add_edge(1, 2) +graph.add_edge(1, 3) +graph.add_edge(2, 4) +graph.add_edge(2, 6) +graph.add_edge(3, 4) +graph.add_edge(3, 5) +graph.add_edge(4, 5) +graph.add_edge(4, 6) + +# problem definition +problem = MisProblem(graph) + +modelfilename = "MIS_quantum_solvers" + +suffix = f"-{time.time()}" if create_another_study else "" +study_name = f"{modelfilename}{suffix}" +storage_path = "./optuna-journal.log" # NFS path for distributed optimization +elapsed_time_attr = "elapsed_time" # name of the user attribute used to store duration of trials (updated during intermediate reports) + + +solvers: Dict[str, List[Tuple[Type[QiskitSolver], Dict[str, Any]]]] = { + "qaoa": [ + ( + QAOAMisSolver, + { + }, + ), + ], + "vqe": [ + ( + VQEMisSolver, + { + }, + ), + ] +} + +solvers_map = {} +for key in solvers: + for solver, param in solvers[key]: + solvers_map[solver] = (key, param) + +solvers_to_test: List[Type[SolverDO]] = [ + s for s in solvers_map +] + +# we need to map the classes to a unique string, to be seen as a categorical hyperparameter by optuna +# by default, we use the class name, but if there are identical names, f"{cls.__module__}.{cls.__name__}" could be used. +solvers_by_name: Dict[str, Type[SolverDO]] = { + cls.__name__: cls for cls in solvers_to_test +} + +# sense of optimization +objective_register = problem.get_objective_register() +if objective_register.objective_sense == ModeOptim.MINIMIZATION: + direction = "minimize" +else: + direction = "maximize" + + +def objective(trial: Trial): + # hyperparameters to test + + # first parameter: solver choice + solver_name: str = trial.suggest_categorical("solver", choices=solvers_by_name) + solver_class = solvers_by_name[solver_name] + + # hyperparameters for the chosen solver + suggested_hyperparameters_kwargs = solver_class.suggest_hyperparameters_with_optuna( + trial=trial, prefix=solver_name + "." + ) + + # use existing value if corresponding to a previous complete trial + """ + states_to_consider = (TrialState.COMPLETE,) + trials_to_consider = trial.study.get_trials( + deepcopy=False, states=states_to_consider + ) + for t in reversed(trials_to_consider): + if trial.params == t.params: + logger.warning( + "Trial with same hyperparameters as a previous complete trial: returning previous fit." + ) + return t.value + """ + # prune if corresponding to a previous failed trial + states_to_consider = (TrialState.FAIL,) + trials_to_consider = trial.study.get_trials( + deepcopy=False, states=states_to_consider + ) + for t in reversed(trials_to_consider): + if trial.params == t.params: + raise optuna.TrialPruned( + "Pruning trial identical to a previous failed trial." + ) + + logger.info(f"Launching trial {trial.number} with parameters: {trial.params}") + + # construct kwargs for __init__, init_model, and solve + kwargs = {} + kwargs.update(suggested_hyperparameters_kwargs) + + # solver init + solver = solver_class(problem=problem) + solver.init_model() + + # init timer + starting_time = time.perf_counter() + + # solve + res = solver.solve( + callbacks=[ + OptunaCallback( + trial=trial, + starting_time=starting_time, + elapsed_time_attr=elapsed_time_attr, + report_time=True, + ), + ObjectiveLogger( + step_verbosity_level=logging.INFO, end_verbosity_level=logging.INFO + ), + ], + **kwargs, + ) + + # store elapsed time + elapsed_time = time.perf_counter() - starting_time + trial.set_user_attr(elapsed_time_attr, elapsed_time) + + if len(res.list_solution_fits) != 0: + sol, fit = res.get_best_solution_fit() + trial.set_user_attr("satisfy", problem.satisfy(sol)) + trial.set_user_attr("nodes chose", list(sol.chosen)) + return fit + else: + raise optuna.TrialPruned("Pruned because failed") + + +# create study + database to store it +storage = "sqlite:///example.db" +study = optuna.create_study( + study_name=study_name, + direction=direction, + sampler=optuna.samplers.TPESampler(seed=seed), + pruner=TimedPercentilePruner( # intermediate values interpolated at same "step" + percentile=50, # median pruner + n_warmup_steps=min_time_per_solver, # no pruning during first seconds + ), + storage=storage, + load_if_exists=True, +) +study.set_metric_names(["nb_nodes"]) +study.optimize(objective, n_trials=optuna_nb_trials) From 6bb3730d48b4653adb7f84c8ce30b4438efa2044 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Tue, 11 Jun 2024 10:19:47 +0200 Subject: [PATCH 02/10] add a new problem definition for quantum solvers for coloring problem, where the objective is just to now for a given number of color, if the coloring problem is feasible. --- .../coloring/solvers/coloring_quantum.py | 131 ++++++++++++++++-- .../generic_tools/qiskit_tools.py | 7 +- examples/qiskit_examples/coloring_example.py | 4 +- .../qiskit_examples/qiskit_optuna_coloring.py | 6 +- 4 files changed, 128 insertions(+), 20 deletions(-) diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py index b1108d04a..bf0c1abc9 100644 --- a/discrete_optimization/coloring/solvers/coloring_quantum.py +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -11,14 +11,13 @@ from discrete_optimization.generic_tools.do_problem import ParamsObjectiveFunction, Solution -class ColoringQiskit(OptimizationApplication): +class ColoringQiskit_MinimizeNbColor(OptimizationApplication): def __init__(self, problem: ColoringProblem, nb_max_color=None) -> None: """ Args: problem : the coloring problem instance """ - super().__init__(problem) self.problem = problem if nb_max_color is None: nb_max_color = self.problem.number_of_nodes @@ -28,10 +27,6 @@ def __init__(self, problem: ColoringProblem, nb_max_color=None) -> None: def to_quadratic_program(self) -> QuadraticProgram: quadratic_program = QuadraticProgram() - # TODO supprimer les X_i_j et se ramener à un problème "peut on colorer nos noeuds avec n nolors?" ?? - # TODO faire les deux ?? - # serait plus adapté pour qaoa ?? - # X_i,j == 1 si le noeud i prend la couleur j # C_j == 1 si la couleur j est choisit au moins une fois @@ -50,7 +45,7 @@ def to_quadratic_program(self) -> QuadraticProgram: quadratic = {} for i in range(0, self.nb_max_color): - quadratic[var_names[i], var_names[i]] = 1 + quadratic[var_names[i], var_names[i]] = 1 # essayer de modifier le 1 ?? """ On va ici intégrer sous forme de pénalité les différentes contraintes afin d'avoir directement une formulation QUBO @@ -73,7 +68,7 @@ def to_quadratic_program(self) -> QuadraticProgram: for j in range(0, self.nb_max_color): # quadratic[var_names[(i, j)], var_names[(i, j)]] = -p for k in range(j + 1, self.nb_max_color): - quadratic[var_names[(i, j)], var_names[(i, k)]] = p + quadratic[var_names[(i, j)], var_names[(i, k)]] = 2*p # deux noeuds adjacents ne peuvent avoir la même couleur for edge in self.problem.graph.graph_nx.edges(): @@ -112,12 +107,12 @@ def interpret(self, result: Union[OptimizationResult, np.ndarray]): return sol -class QAOAColoringSolver(SolverColoring, QiskitQAOASolver): +class QAOAColoringSolver_MinimizeNbColor(SolverColoring, QiskitQAOASolver): def __init__(self, problem: ColoringProblem, params_objective_function: Optional[ParamsObjectiveFunction] = None, nb_max_color=None): super().__init__(problem, params_objective_function) - self.coloring_qiskit = ColoringQiskit(problem, nb_max_color=nb_max_color) + self.coloring_qiskit = ColoringQiskit_MinimizeNbColor(problem, nb_max_color=nb_max_color) def init_model(self): self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() @@ -126,12 +121,124 @@ def retrieve_current_solution(self, result) -> Solution: return self.coloring_qiskit.interpret(result) -class VQEColoringSolver(SolverColoring, QiskitVQESolver): +class VQEColoringSolver_MinimizeNbColor(SolverColoring, QiskitVQESolver): def __init__(self, problem: ColoringProblem, params_objective_function: Optional[ParamsObjectiveFunction] = None, nb_max_color=None): super().__init__(problem, params_objective_function) - self.coloring_qiskit = ColoringQiskit(problem, nb_max_color=nb_max_color) + self.coloring_qiskit = ColoringQiskit_MinimizeNbColor(problem, nb_max_color=nb_max_color) + + def init_model(self): + self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() + self.nb_variable = self.coloring_qiskit.nb_variable + + def retrieve_current_solution(self, result) -> Solution: + return self.coloring_qiskit.interpret(result) + + +class ColoringQiskit_FeasibleNbColor(OptimizationApplication): # TODO à tester + + def __init__(self, problem: ColoringProblem, nb_color=None) -> None: + """ + Args: + problem : the coloring problem instance + """ + self.problem = problem + if nb_color is None: + nb_color = self.problem.number_of_nodes + self.nb_color = nb_color + self.nb_variable = self.problem.number_of_nodes * self.nb_color + + def to_quadratic_program(self) -> QuadraticProgram: + quadratic_program = QuadraticProgram() + + # C_j == 1 si la couleur j est choisit au moins une fois + + var_names = {} + for i in range(0, self.nb_color): + for j in range(0, self.problem.number_of_nodes): + x_new = quadratic_program.binary_var("x" + str(j) + str(i)) + var_names[(j, i)] = x_new.name + + # on cherche à savoir si il est possible de satisfaire le problème de coloring avec ce nombre de couleur + + constant = 0 + linear = {} + quadratic = {} + + """ + On va ici intégrer sous forme de pénalité les différentes contraintes afin d'avoir directement une formulation QUBO + x1 + ... + xi = 1 devient P(-x1 + ... + -xi + 2x1x2 + ... + 2x1xi + 2x2x3 + .... + 2x2xi + ... + 2x(i-1)xi) + x + y <= 1 devient P(xy) + où P est un scalaire qui doit idéalement être ni trop petit, ni trop grand (ici on prend le nombre de couleur max autorisé) + """ + + p = self.nb_color + + # chaque noeud doit avoir une unique couleur + for i in range(0, self.problem.number_of_nodes): + for j in range(0, self.nb_color): + quadratic[var_names[(i, j)], var_names[(i, j)]] = -p + for k in range(j + 1, self.nb_color): + quadratic[var_names[(i, j)], var_names[(i, k)]] = 2*p + + # deux noeuds adjacents ne peuvent avoir la même couleur + for edge in self.problem.graph.graph_nx.edges(): + for j in range(0, self.nb_color): + quadratic[var_names[(self.problem.index_nodes_name[edge[0]], j)], var_names[ + (self.problem.index_nodes_name[edge[1]], j)]] = p + + quadratic_program.minimize(constant, linear, quadratic) + + return quadratic_program + + def interpret(self, result: Union[OptimizationResult, np.ndarray]): + + x = self._result_to_x(result) + + colors = [0] * self.problem.number_of_nodes + + color_used = set() + + for node in range(0, self.problem.number_of_nodes): + color_find = False + color = 0 + while not color_find and color < self.nb_color: + if x[self.problem.number_of_nodes * color + node] == 1: + colors[node] = color + color_find = True + color_used.add(color) + color += 1 + + # TODO think about what we want to do when a node has no color + + sol = ColoringSolution(self.problem, colors=colors, nb_color=len(color_used)) + + return sol + + +class QAOAColoringSolver_FeasibleNbColor(SolverColoring, QiskitQAOASolver): + + def __init__(self, problem: ColoringProblem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_color=None): + super().__init__(problem, params_objective_function) + self.coloring_qiskit = ColoringQiskit_FeasibleNbColor(problem, nb_color=nb_color) + + def init_model(self): + self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() + + def retrieve_current_solution(self, result) -> Solution: + return self.coloring_qiskit.interpret(result) + + +class VQEColoringSolver_FeasibleNbColor(SolverColoring, QiskitVQESolver): + + def __init__(self, problem: ColoringProblem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_color=None): + super().__init__(problem, params_objective_function) + self.coloring_qiskit = ColoringQiskit_FeasibleNbColor(problem, nb_color=nb_color) def init_model(self): self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index 50043e087..55cfa28ae 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -128,7 +128,7 @@ def execute_ansatz_with_Hamiltonian( method = kwargs["method"] nb_shots = kwargs["nb_shots"] - if kwargs["options"]: + if kwargs.get("options"): options = kwargs["options"] else: if method == "COBYLA": @@ -205,8 +205,6 @@ def __init__( class QiskitQAOASolver(QiskitSolver, Hyperparametrizable): - kwargs = {"step": 50} - hyperparameters = [ IntegerHyperparameter( name="reps", low=1, high=6, default=2 @@ -217,6 +215,9 @@ class QiskitQAOASolver(QiskitSolver, Hyperparametrizable): CategoricalHyperparameter( name="method", choices=["COBYLA"], default="COBYLA" ), + IntegerHyperparameter( + name="nb_shots", low=10000, high=100000, step=10000, default=10000 + ), IntegerHyperparameter( name="maxiter", low=100, high=1000, step=50, default=300 ), diff --git a/examples/qiskit_examples/coloring_example.py b/examples/qiskit_examples/coloring_example.py index edb0b23ed..e1ef9539d 100644 --- a/examples/qiskit_examples/coloring_example.py +++ b/examples/qiskit_examples/coloring_example.py @@ -1,7 +1,7 @@ from qiskit_aer import AerSimulator from discrete_optimization.coloring.coloring_model import ColoringProblem -from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver +from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver_MinimizeNbColor from discrete_optimization.generic_tools.graph_api import Graph @@ -22,7 +22,7 @@ def quantum_coloring(): # we create an instance of ColoringProblem coloringProblem = ColoringProblem(Graph(nodes=nodes, edges=edges)) # we create an instance of a QAOAMisSolver - coloringSolver = QAOAColoringSolver(coloringProblem, nb_max_color=nb_max_color) + coloringSolver = QAOAColoringSolver_MinimizeNbColor(coloringProblem, nb_max_color=nb_max_color) # we initialize the solver, in fact this step transform the problem in a QUBO formulation coloringSolver.init_model() # we solve the mis problem diff --git a/examples/qiskit_examples/qiskit_optuna_coloring.py b/examples/qiskit_examples/qiskit_optuna_coloring.py index 778dea951..05ff6f25b 100644 --- a/examples/qiskit_examples/qiskit_optuna_coloring.py +++ b/examples/qiskit_examples/qiskit_optuna_coloring.py @@ -14,7 +14,7 @@ os.environ["DO_SKIP_MZN_CHECK"] = "1" from discrete_optimization.coloring.coloring_model import ColoringProblem -from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver, VQEColoringSolver +from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver_MinimizeNbColor, VQEColoringSolver_MinimizeNbColor from discrete_optimization.generic_tools.graph_api import Graph from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver import logging @@ -63,14 +63,14 @@ solvers: Dict[str, List[Tuple[Type[QiskitSolver], Dict[str, Any]]]] = { "qaoa": [ ( - QAOAColoringSolver, + QAOAColoringSolver_MinimizeNbColor, { }, ), ], "vqe": [ ( - VQEColoringSolver, + VQEColoringSolver_MinimizeNbColor, { }, ), From 2fc6e7867bcc9b01daff8246dcec7bf90a5341b9 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Tue, 11 Jun 2024 15:55:35 +0200 Subject: [PATCH 03/10] add possibility to use optimizer define in qiskit_algorithm for QAOA and VQE algorithms. Give more possible optimizer of interest (like SPSA or BOBYQA). --- .../coloring/solvers/coloring_quantum.py | 8 +-- .../generic_tools/qiskit_tools.py | 57 ++++++++++++------- 2 files changed, 41 insertions(+), 24 deletions(-) diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py index bf0c1abc9..2197a16f1 100644 --- a/discrete_optimization/coloring/solvers/coloring_quantum.py +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -30,6 +30,8 @@ def to_quadratic_program(self) -> QuadraticProgram: # X_i,j == 1 si le noeud i prend la couleur j # C_j == 1 si la couleur j est choisit au moins une fois + p = self.nb_max_color + var_names = {} for i in range(0, self.nb_max_color): for j in range(0, self.problem.number_of_nodes): @@ -45,7 +47,7 @@ def to_quadratic_program(self) -> QuadraticProgram: quadratic = {} for i in range(0, self.nb_max_color): - quadratic[var_names[i], var_names[i]] = 1 # essayer de modifier le 1 ?? + quadratic[var_names[i], var_names[i]] = 1 """ On va ici intégrer sous forme de pénalité les différentes contraintes afin d'avoir directement une formulation QUBO @@ -55,8 +57,6 @@ def to_quadratic_program(self) -> QuadraticProgram: où P est un scalaire qui doit idéalement être ni trop petit, ni trop grand (ici on prend le nombre de couleur max autorisé) """ - p = self.nb_max_color - # si une couleur j est attribué à un noeud, la contrainte C_j doit valoir 1 for i in range(0, self.problem.number_of_nodes): for j in range(0, self.nb_max_color): @@ -136,7 +136,7 @@ def retrieve_current_solution(self, result) -> Solution: return self.coloring_qiskit.interpret(result) -class ColoringQiskit_FeasibleNbColor(OptimizationApplication): # TODO à tester +class ColoringQiskit_FeasibleNbColor(OptimizationApplication): def __init__(self, problem: ColoringProblem, nb_color=None) -> None: """ diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index 55cfa28ae..b4383e9cf 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -125,17 +125,8 @@ def execute_ansatz_with_Hamiltonian( """ optimization_level = kwargs["optimization_level"] - method = kwargs["method"] nb_shots = kwargs["nb_shots"] - if kwargs.get("options"): - options = kwargs["options"] - else: - if method == "COBYLA": - options = {"maxiter": kwargs["maxiter"], "rhobeg": kwargs["rhobeg"]} - else: - options = {} - # transpile and optimize the quantum circuit depending on the device who are going to use # there are four level_optimization, to 0 to 3, 3 is the better but also the longest target = backend.target @@ -148,7 +139,6 @@ def execute_ansatz_with_Hamiltonian( # open a session if desired if use_session: session = Session(backend=backend, max_time="2h") - # TODO what happend if we create a session for a simulator? crash or not? else: session = None @@ -166,14 +156,39 @@ def execute_ansatz_with_Hamiltonian( "cost_history": [], } # step of minimization - res = minimize( - cost_func, - validate_initial_point(point=None, circuit=ansatz), - args=(ansatz, hamiltonian, estimator, callback_dict), - method=method, - bounds=validate_bounds(ansatz), - options=options, - ) + if kwargs.get("optimizer"): + def fun(x): + pub = (ansatz, [hamiltonian], [x]) + result = estimator.run(pubs=[pub]).result() + cost = result[0].data.evs[0] + callback_dict["iters"] += 1 + callback_dict["prev_vector"] = x + callback_dict["cost_history"].append(cost) + print(f"Iters. done: {callback_dict['iters']} [Current cost: {cost}]") + return cost + + optimizer = kwargs["optimizer"] + res = optimizer.minimize(fun, validate_initial_point(point=None, circuit=ansatz)) + + else: + + method = kwargs["method"] + if kwargs.get("options"): + options = kwargs["options"] + else: + if method == "COBYLA": + options = {"maxiter": kwargs["maxiter"], "rhobeg": kwargs["rhobeg"]} + else: + options = {} + + res = minimize( + cost_func, + validate_initial_point(point=None, circuit=ansatz), + args=(ansatz, hamiltonian, estimator, callback_dict), + method=method, + bounds=validate_bounds(ansatz), + options=options, + ) # Assign solution parameters to our ansatz qc = ansatz.assign_parameters(res.x) @@ -184,13 +199,14 @@ def execute_ansatz_with_Hamiltonian( # run our circuit with optimal parameters find at the minimization step results = sampler.run([qc]).result() # extract a dictionnary of results, key is binary values of variable and value is number of time of these values has been found - result = get_result_from_dict_result(results[0].data.meas.get_counts()) + best_result = get_result_from_dict_result(results[0].data.meas.get_counts()) + print(best_result) # Close the session since we are now done with it if use_session: # with_session: session.close() - return result + return best_result class QiskitSolver(SolverDO): @@ -225,6 +241,7 @@ class QiskitQAOASolver(QiskitSolver, Hyperparametrizable): name="rhobeg", low=0.5, high=1.5, default=1. ), # TODO 3 hyperparam Cobyla : rhobeg, tol ??, maxiter + # TODO rajouter initial_point et initial_bound dans les hyperparams ? ] def __init__( From 687e6ea0d11a928d1fce8a82cb104e9996d00499 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Tue, 11 Jun 2024 16:13:45 +0200 Subject: [PATCH 04/10] add the tuto notebook for using QAOA algorithm with discrete_optimization --- notebooks/z_Advanced/tuto_qiskit.ipynb | 306 +++++++++++++++++++++++++ 1 file changed, 306 insertions(+) create mode 100644 notebooks/z_Advanced/tuto_qiskit.ipynb diff --git a/notebooks/z_Advanced/tuto_qiskit.ipynb b/notebooks/z_Advanced/tuto_qiskit.ipynb new file mode 100644 index 000000000..aa753270e --- /dev/null +++ b/notebooks/z_Advanced/tuto_qiskit.ipynb @@ -0,0 +1,306 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "220905dc-d6fe-464c-bbda-b661af6543dd", + "metadata": {}, + "source": [ + "Première cell pour éxécuter dans environnement avec discrete_optimization de github mais pas installer en package python" + ] + }, + { + "cell_type": "code", + "execution_count": 1, + "id": "f3b177ce-a4b8-4f43-ae92-15c5bfcaabb5", + "metadata": {}, + "outputs": [], + "source": [ + "import sys \n", + "sys.path.append(\"/Users/AGAUVZFV/Desktop/discrete-optimization\")\n", + "import os\n", + "os.environ[\"DO_SKIP_MZN_CHECK\"] = \"1\" " + ] + }, + { + "cell_type": "markdown", + "id": "4b74b0c6-2aad-43ab-9c2c-a131ffeec0d5", + "metadata": {}, + "source": [ + "other useful import" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "26efd9d5-90c9-4fb5-ae46-f85b935ff094", + "metadata": {}, + "outputs": [], + "source": [ + "from discrete_optimization.maximum_independent_set.mis_model import MisProblem\n", + "from discrete_optimization.maximum_independent_set.solvers.mis_gurobi import MisMilpSolver\n", + "from discrete_optimization.maximum_independent_set.mis_plot import plot_mis_solution, plot_mis_graph\n", + "from discrete_optimization.maximum_independent_set.solvers.mis_quantum import QAOAMisSolver\n", + "from qiskit_aer import AerSimulator\n", + "import networkx as nx" + ] + }, + { + "cell_type": "markdown", + "id": "fd7c42c6-1626-45dd-b262-6195039887f3", + "metadata": {}, + "source": [ + "The objectif of this tutorial is to present how we can use discrete_optimization to solve optimization problem using quantum simulator or quantum real device with qiskit. \n", + "\n", + "It's free to use simulator but to use quantum real divice you need an IBM account. You can have one for free BUT you will be limited to ten minutes of use by month and you can't use session (we talk about session a bit later) who is a very pratical way to execute job on real device.\n", + "\n", + "This tutorial has not to purpose to present a method to solve very large problem, nothing of revolutionnary here, just a quantum gadget to see what we can do actually with quantum technologies, the good point as the bad point." + ] + }, + { + "cell_type": "markdown", + "id": "37b171d5-8111-4626-b4fa-78680ed8251b", + "metadata": {}, + "source": [ + "The first step is, of course, to creat the problem to solve, here we are going to solve a maximum_independent_set.\n", + "In a graph we search a subset of nodes who are not connected two by two. We want to maximize the side of this subset.\n", + "An example of a graph with 6 nodes where the maximum_independent_set is the subset of nodes (1,5,6)." + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "4c36eeca-5a59-4b76-88ee-ef31192b3af9", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + " graph = nx.Graph()\n", + "\n", + " graph.add_edge(1, 2)\n", + " graph.add_edge(1, 3)\n", + " graph.add_edge(2, 4)\n", + " graph.add_edge(2, 6)\n", + " graph.add_edge(3, 4)\n", + " graph.add_edge(3, 5)\n", + " graph.add_edge(4, 5)\n", + " graph.add_edge(4, 6)\n", + "\n", + " misProblem = MisProblem(graph)\n", + " plot_mis_graph(misProblem)" + ] + }, + { + "cell_type": "markdown", + "id": "302cdc6c-5a60-4002-bfd0-8e8f0fe758cc", + "metadata": {}, + "source": [ + "We are going to see how solve this problem with a non-noisy quantum simulator or a real quantum device. We develop a tools that just need a MisProblem object and a qiskit backend object and who can solve the mis Problem using the QAOA algorithm. Quantum approximate optimization algorithm is a well-known hybrid algorithm that use the Ising representation of a QUBO problem. LIEN vers article QAOA + ising formulation Article présentant l'algorithm QAOA : https://arxiv.org/abs/1411.4028 " + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "f801bf84-1dae-4834-8fca-4c91ca68a204", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\AGAUVZFV\\AppData\\Local\\miniconda3\\envs\\d-opti\\Lib\\site-packages\\qiskit_ibm_runtime\\fake_provider\\local_service.py:243: UserWarning: Options {'default_shots': 10000} have no effect in local testing mode.\n", + " warnings.warn(f\"Options {options_copy} have no effect in local testing mode.\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iters. done: 1 [Current cost: -0.583984375]\n", + "Iters. done: 2 [Current cost: 0.0556640625]\n", + "Iters. done: 3 [Current cost: -0.207763671875]\n", + "Iters. done: 4 [Current cost: -0.89794921875]\n", + "Iters. done: 5 [Current cost: -0.594482421875]\n", + "Iters. done: 6 [Current cost: 1.247802734375]\n", + "Iters. done: 7 [Current cost: -0.0087890625]\n", + "Iters. done: 8 [Current cost: -0.426513671875]\n", + "Iters. done: 9 [Current cost: -0.564208984375]\n", + "Iters. done: 10 [Current cost: -0.8447265625]\n", + "Iters. done: 11 [Current cost: -0.4990234375]\n", + "Iters. done: 12 [Current cost: -0.824462890625]\n", + "Iters. done: 13 [Current cost: -0.923828125]\n", + "Iters. done: 14 [Current cost: -0.868896484375]\n", + "Iters. done: 15 [Current cost: -0.927978515625]\n", + "Iters. done: 16 [Current cost: -0.944580078125]\n", + "Iters. done: 17 [Current cost: -0.950439453125]\n", + "Iters. done: 18 [Current cost: -0.951171875]\n", + "Iters. done: 19 [Current cost: -0.910400390625]\n", + "Iters. done: 20 [Current cost: -0.959228515625]\n", + "Iters. done: 21 [Current cost: -0.942626953125]\n", + "Iters. done: 22 [Current cost: -0.955322265625]\n", + "Iters. done: 23 [Current cost: -0.931640625]\n", + "Iters. done: 24 [Current cost: -0.937255859375]\n", + "Iters. done: 25 [Current cost: -0.93310546875]\n", + "Iters. done: 26 [Current cost: -0.936767578125]\n", + "Iters. done: 27 [Current cost: -0.933837890625]\n", + "Iters. done: 28 [Current cost: -0.9453125]\n", + "Iters. done: 29 [Current cost: -0.945068359375]\n", + "Iters. done: 30 [Current cost: -0.929931640625]\n", + "Iters. done: 31 [Current cost: -0.93115234375]\n", + "Iters. done: 32 [Current cost: -0.95751953125]\n", + "Iters. done: 33 [Current cost: -0.931640625]\n", + "Iters. done: 34 [Current cost: -0.93798828125]\n", + "Iters. done: 35 [Current cost: -0.91943359375]\n", + "Iters. done: 36 [Current cost: -0.945068359375]\n", + "Iters. done: 37 [Current cost: -0.955810546875]\n", + "Iters. done: 38 [Current cost: -0.944091796875]\n", + "Iters. done: 39 [Current cost: -0.95263671875]\n", + "Iters. done: 40 [Current cost: -0.95068359375]\n", + "Iters. done: 41 [Current cost: -0.95703125]\n", + "Iters. done: 42 [Current cost: -0.93994140625]\n", + "Iters. done: 43 [Current cost: -0.939697265625]\n", + "Iters. done: 44 [Current cost: -0.9482421875]\n", + "Iters. done: 45 [Current cost: -0.9404296875]\n", + "Iters. done: 46 [Current cost: -0.9296875]\n", + "nb_node in mis = 3.0\n", + "nodes in mis =[1, 6, 5]\n" + ] + } + ], + "source": [ + "# we declare the MisProblem\n", + "misProblem = MisProblem(graph)\n", + "\n", + "# we declare the QAOA solver we are going to use\n", + "misSolver = QAOAMisSolver(misProblem)\n", + "\n", + "# then we need a backend, a backend is an object wo define how and where the quantum part of the algorithm is run\n", + "# it can be a simulator or a real quantum device\n", + "\n", + "# here we declare a simulator\n", + "backend = AerSimulator()\n", + "\n", + "# then we need to initialize the solver, in fact we transform the mis problem on his QUBO form\n", + "misSolver.init_model()\n", + "\n", + "# after that we can run the solver using the defined backend\n", + "res = misSolver.solve(backend=backend)\n", + "\n", + "sol, _ = res.get_best_solution_fit()\n", + "print(sol)" + ] + }, + { + "cell_type": "markdown", + "id": "946aaf2a-ea8d-481a-8df2-d9cf5e213065", + "metadata": {}, + "source": [ + "If you have an IBM account you can declare the backend as follow, where the \"token\" parameter is your personnal IBM token to use a real quantum device. If you have a premium account please set the parameter \"use-session\" of the solver to True. A session give a guaranted acces to the device, if you don't use session, you can have a lot of waiting time beetween two different job (because other jobs can be insert in the queue). In QAOA we create a new job at each iteration of the optimizer and one final job after the minimization step is over." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "c97813de-a503-48fd-be7e-c8114ce07e25", + "metadata": {}, + "outputs": [], + "source": [ + "# from qiskit_ibm_runtime import QiskitRuntimeService\n", + "\n", + "# service = QiskitRuntimeService(channel=\"ibm_quantum\", token=token)\n", + "# backend = service.least_busy(operational=True, simulator=False)\n", + "# misSolver.init_model()\n", + "# misSolver.solve(backend=backend, use_session=True)\n", + "# sol, _ = res.get_best_solution_fit()\n", + "# print(sol)" + ] + }, + { + "cell_type": "markdown", + "id": "a6fbf5d7-f7ba-43c8-a224-a5326bffd6a2", + "metadata": {}, + "source": [ + "Here we have not define any parameters except the backend, in fact there is a lot of parameters we can chose.\n", + "\n", + "They can be defined in a kwargs parameter\n", + "\n", + "Hyperparameters we can modifiy and can influence the performance of the algorithm are :\n", + "\n", + "- \"method\" the scipy optimizer we want to use for the classical optimization step\n", + "- \"reps\" : the deep of the quantum circuit\n", + "- \"optimization_level\" : the level of optimization of the circuit\n", + "- \"nb_shots\" who define the number of time we want to repeat the quantum circuit (not used on simulator)\n", + "\n", + "by default we use the \"COBYLA\" method, you can give value for three hyperparameters in the kwargs :\n", + "\n", + "- maxiter : the maximum number of iteration of the optimizer\n", + "- rhobeg : ???\n", + "- tol : ???\n", + "\n", + "for all other scipy optimizer https://docs.scipy.org/doc/scipy-1.13.1/reference/generated/scipy.optimize.minimize.html (or it's also possible for cobyla), if you want to define some parameters you can do it with a kwargs parameter \"options\", who is a dictionnary of possible options for the optimizer.\n", + "\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "a423d5cc-af42-4a7d-abca-15c4c86a7699", + "metadata": {}, + "outputs": [], + "source": [ + "# kwargs = {\"method\": \"your_method\"}\n", + "# options = {\"options1\": op1, \"options2\": op2, ...}\n", + "# kwargs[\"options\"] = options" + ] + }, + { + "cell_type": "markdown", + "id": "45fbfbc2-6262-4765-a9e9-fc35233c9906", + "metadata": {}, + "source": [ + "There is also the possibility to use an optimizer define in qiskit_algorithms (https://github.com/qiskit-community/qiskit-algorithms/tree/stable/0.3/qiskit_algorithms/optimizers). To use one of them you have just te create it and then passed it in the kwargs as \"optimizer\" parameter." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "ce79fe2a-ecf2-4d78-934e-83e212252667", + "metadata": {}, + "outputs": [], + "source": [ + "# optimizer = SPSA()\n", + "# kwargs = {\"optimizer\": optimizer} or kwargs[\"optimizer\"] = optimizer if kwargs already exist" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "d-opti", + "language": "python", + "name": "d-opti" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.12.3" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From bb2e2eb65d713f3e7171d7857ea77bec3fb94ed4 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Wed, 12 Jun 2024 15:27:03 +0200 Subject: [PATCH 05/10] finalize hyperparameters for QAOA and VQE and add labels to plot function for mis problem and solution --- .../coloring/solvers/coloring_quantum.py | 100 +++++++++++------ .../generic_tools/qiskit_tools.py | 102 +++++++++--------- .../maximum_independent_set/mis_plot.py | 15 +-- examples/qiskit_examples/coloring_example.py | 14 ++- .../qiskit_examples/qiskit_optuna_coloring.py | 27 +++-- examples/qiskit_examples/qiskit_optuna_mis.py | 27 +++-- notebooks/z_Advanced/tuto_qiskit.ipynb | 99 +++-------------- 7 files changed, 179 insertions(+), 205 deletions(-) diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py index 2197a16f1..fa3fc8b2b 100644 --- a/discrete_optimization/coloring/solvers/coloring_quantum.py +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -5,14 +5,22 @@ from qiskit_optimization.algorithms import OptimizationResult from qiskit_optimization.applications import OptimizationApplication -from discrete_optimization.coloring.coloring_model import ColoringProblem, ColoringSolution +from discrete_optimization.coloring.coloring_model import ( + ColoringProblem, + ColoringSolution, +) from discrete_optimization.coloring.solvers.coloring_solver import SolverColoring -from discrete_optimization.generic_tools.qiskit_tools import QiskitQAOASolver, QiskitVQESolver -from discrete_optimization.generic_tools.do_problem import ParamsObjectiveFunction, Solution +from discrete_optimization.generic_tools.do_problem import ( + ParamsObjectiveFunction, + Solution, +) +from discrete_optimization.generic_tools.qiskit_tools import ( + QiskitQAOASolver, + QiskitVQESolver, +) class ColoringQiskit_MinimizeNbColor(OptimizationApplication): - def __init__(self, problem: ColoringProblem, nb_max_color=None) -> None: """ Args: @@ -22,7 +30,9 @@ def __init__(self, problem: ColoringProblem, nb_max_color=None) -> None: if nb_max_color is None: nb_max_color = self.problem.number_of_nodes self.nb_max_color = nb_max_color - self.nb_variable = self.problem.number_of_nodes * self.nb_max_color + self.nb_max_color + self.nb_variable = ( + self.problem.number_of_nodes * self.nb_max_color + self.nb_max_color + ) def to_quadratic_program(self) -> QuadraticProgram: quadratic_program = QuadraticProgram() @@ -68,13 +78,15 @@ def to_quadratic_program(self) -> QuadraticProgram: for j in range(0, self.nb_max_color): # quadratic[var_names[(i, j)], var_names[(i, j)]] = -p for k in range(j + 1, self.nb_max_color): - quadratic[var_names[(i, j)], var_names[(i, k)]] = 2*p + quadratic[var_names[(i, j)], var_names[(i, k)]] = 2 * p # deux noeuds adjacents ne peuvent avoir la même couleur for edge in self.problem.graph.graph_nx.edges(): for j in range(0, self.nb_max_color): - quadratic[var_names[(self.problem.index_nodes_name[edge[0]], j)], var_names[ - (self.problem.index_nodes_name[edge[1]], j)]] = p + quadratic[ + var_names[(self.problem.index_nodes_name[edge[0]], j)], + var_names[(self.problem.index_nodes_name[edge[1]], j)], + ] = p quadratic_program.minimize(constant, linear, quadratic) @@ -99,7 +111,14 @@ def interpret(self, result: Union[OptimizationResult, np.ndarray]): # TODO think about what we want to do when a node has no color for color in range(0, self.nb_max_color): - if x[self.problem.number_of_nodes * color + self.problem.number_of_nodes + color] == 1: + if ( + x[ + self.problem.number_of_nodes * color + + self.problem.number_of_nodes + + color + ] + == 1 + ): nb_color += 1 sol = ColoringSolution(self.problem, colors=colors, nb_color=nb_color) @@ -108,11 +127,16 @@ def interpret(self, result: Union[OptimizationResult, np.ndarray]): class QAOAColoringSolver_MinimizeNbColor(SolverColoring, QiskitQAOASolver): - - def __init__(self, problem: ColoringProblem, params_objective_function: Optional[ParamsObjectiveFunction] = None, - nb_max_color=None): + def __init__( + self, + problem: ColoringProblem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_max_color=None, + ): super().__init__(problem, params_objective_function) - self.coloring_qiskit = ColoringQiskit_MinimizeNbColor(problem, nb_max_color=nb_max_color) + self.coloring_qiskit = ColoringQiskit_MinimizeNbColor( + problem, nb_max_color=nb_max_color + ) def init_model(self): self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() @@ -122,11 +146,16 @@ def retrieve_current_solution(self, result) -> Solution: class VQEColoringSolver_MinimizeNbColor(SolverColoring, QiskitVQESolver): - - def __init__(self, problem: ColoringProblem, params_objective_function: Optional[ParamsObjectiveFunction] = None, - nb_max_color=None): + def __init__( + self, + problem: ColoringProblem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_max_color=None, + ): super().__init__(problem, params_objective_function) - self.coloring_qiskit = ColoringQiskit_MinimizeNbColor(problem, nb_max_color=nb_max_color) + self.coloring_qiskit = ColoringQiskit_MinimizeNbColor( + problem, nb_max_color=nb_max_color + ) def init_model(self): self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() @@ -137,7 +166,6 @@ def retrieve_current_solution(self, result) -> Solution: class ColoringQiskit_FeasibleNbColor(OptimizationApplication): - def __init__(self, problem: ColoringProblem, nb_color=None) -> None: """ Args: @@ -180,13 +208,15 @@ def to_quadratic_program(self) -> QuadraticProgram: for j in range(0, self.nb_color): quadratic[var_names[(i, j)], var_names[(i, j)]] = -p for k in range(j + 1, self.nb_color): - quadratic[var_names[(i, j)], var_names[(i, k)]] = 2*p + quadratic[var_names[(i, j)], var_names[(i, k)]] = 2 * p # deux noeuds adjacents ne peuvent avoir la même couleur for edge in self.problem.graph.graph_nx.edges(): for j in range(0, self.nb_color): - quadratic[var_names[(self.problem.index_nodes_name[edge[0]], j)], var_names[ - (self.problem.index_nodes_name[edge[1]], j)]] = p + quadratic[ + var_names[(self.problem.index_nodes_name[edge[0]], j)], + var_names[(self.problem.index_nodes_name[edge[1]], j)], + ] = p quadratic_program.minimize(constant, linear, quadratic) @@ -218,12 +248,16 @@ def interpret(self, result: Union[OptimizationResult, np.ndarray]): class QAOAColoringSolver_FeasibleNbColor(SolverColoring, QiskitQAOASolver): - - def __init__(self, problem: ColoringProblem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, - nb_color=None): + def __init__( + self, + problem: ColoringProblem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_color=None, + ): super().__init__(problem, params_objective_function) - self.coloring_qiskit = ColoringQiskit_FeasibleNbColor(problem, nb_color=nb_color) + self.coloring_qiskit = ColoringQiskit_FeasibleNbColor( + problem, nb_color=nb_color + ) def init_model(self): self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() @@ -233,12 +267,16 @@ def retrieve_current_solution(self, result) -> Solution: class VQEColoringSolver_FeasibleNbColor(SolverColoring, QiskitVQESolver): - - def __init__(self, problem: ColoringProblem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, - nb_color=None): + def __init__( + self, + problem: ColoringProblem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + nb_color=None, + ): super().__init__(problem, params_objective_function) - self.coloring_qiskit = ColoringQiskit_FeasibleNbColor(problem, nb_color=nb_color) + self.coloring_qiskit = ColoringQiskit_FeasibleNbColor( + problem, nb_color=nb_color + ) def init_model(self): self.quadratic_programm = self.coloring_qiskit.to_quadratic_program() diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index b4383e9cf..7a81a0911 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -12,9 +12,14 @@ Solution, ) from discrete_optimization.generic_tools.do_solver import SolverDO -from discrete_optimization.generic_tools.hyperparameters.hyperparameter import IntegerHyperparameter, \ - CategoricalHyperparameter, FloatHyperparameter -from discrete_optimization.generic_tools.hyperparameters.hyperparametrizable import Hyperparametrizable +from discrete_optimization.generic_tools.hyperparameters.hyperparameter import ( + CategoricalHyperparameter, + FloatHyperparameter, + IntegerHyperparameter, +) +from discrete_optimization.generic_tools.hyperparameters.hyperparametrizable import ( + Hyperparametrizable, +) from discrete_optimization.generic_tools.result_storage.result_storage import ( ResultStorage, ) @@ -105,7 +110,7 @@ def cost_func(params, ansatz, hamiltonian, estimator, callback_dict): def execute_ansatz_with_Hamiltonian( - backend, ansatz, hamiltonian, use_session: Optional[bool] = False, **kwargs + backend, ansatz, hamiltonian, use_session: Optional[bool] = False, **kwargs ) -> np.ndarray: """ @param backend: the backend use to run the circuit (simulator or real device) @@ -157,6 +162,7 @@ def execute_ansatz_with_Hamiltonian( } # step of minimization if kwargs.get("optimizer"): + def fun(x): pub = (ansatz, [hamiltonian], [x]) result = estimator.run(pubs=[pub]).result() @@ -168,7 +174,9 @@ def fun(x): return cost optimizer = kwargs["optimizer"] - res = optimizer.minimize(fun, validate_initial_point(point=None, circuit=ansatz)) + res = optimizer.minimize( + fun, validate_initial_point(point=None, circuit=ansatz) + ) else: @@ -177,7 +185,11 @@ def fun(x): options = kwargs["options"] else: if method == "COBYLA": - options = {"maxiter": kwargs["maxiter"], "rhobeg": kwargs["rhobeg"]} + options = { + "maxiter": kwargs["maxiter"], + "rhobeg": kwargs["rhobeg"], + "tol": kwargs["tol"], + } else: options = {} @@ -210,11 +222,10 @@ def fun(x): class QiskitSolver(SolverDO): - def __init__( - self, - problem: Problem, - params_objective_function: Optional[ParamsObjectiveFunction] = None + self, + problem: Problem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, ): super().__init__(problem, params_objective_function) @@ -222,47 +233,38 @@ def __init__( class QiskitQAOASolver(QiskitSolver, Hyperparametrizable): hyperparameters = [ - IntegerHyperparameter( - name="reps", low=1, high=6, default=2 - ), - IntegerHyperparameter( - name="optimization_level", low=0, high=3, default=1 - ), - CategoricalHyperparameter( - name="method", choices=["COBYLA"], default="COBYLA" - ), + IntegerHyperparameter(name="reps", low=1, high=6, default=2), + IntegerHyperparameter(name="optimization_level", low=0, high=3, default=1), + CategoricalHyperparameter(name="method", choices=["COBYLA"], default="COBYLA"), IntegerHyperparameter( name="nb_shots", low=10000, high=100000, step=10000, default=10000 ), - IntegerHyperparameter( - name="maxiter", low=100, high=1000, step=50, default=300 - ), - FloatHyperparameter( - name="rhobeg", low=0.5, high=1.5, default=1. - ), - # TODO 3 hyperparam Cobyla : rhobeg, tol ??, maxiter + IntegerHyperparameter(name="maxiter", low=100, high=1000, step=50, default=300), + FloatHyperparameter(name="rhobeg", low=0.5, high=1.5, default=1.0), + CategoricalHyperparameter(name="tol", choices=[1e-1, 1e-2, 1e-3], default=1e-2), # TODO rajouter initial_point et initial_bound dans les hyperparams ? ] def __init__( - self, - problem: Problem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, - backend: Optional = None + self, + problem: Problem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + backend: Optional = None, ): super().__init__(problem, params_objective_function) self.quadratic_programm = None self.backend = backend def solve( - self, - callbacks: Optional[List[Callback]] = None, - backend: Optional = None, - use_session: Optional[bool] = False, - **kwargs: Any, + self, + callbacks: Optional[List[Callback]] = None, + backend: Optional = None, + use_session: Optional[bool] = False, + **kwargs: Any, ) -> ResultStorage: kwargs = self.complete_with_default_hyperparameters(kwargs) + print(kwargs) reps = kwargs["reps"] @@ -313,19 +315,22 @@ def retrieve_current_solution(self, result) -> Solution: class QiskitVQESolver(QiskitSolver): hyperparameters = [ + IntegerHyperparameter(name="optimization_level", low=0, high=3, default=1), + CategoricalHyperparameter(name="method", choices=["COBYLA"], default="COBYLA"), IntegerHyperparameter( - name="optimization_level", low=0, high=3, default=1 - ), - CategoricalHyperparameter( - name="method", choices=["COBYLA"], default="COBYLA" + name="nb_shots", low=10000, high=100000, step=10000, default=10000 ), + IntegerHyperparameter(name="maxiter", low=100, high=1000, step=50, default=300), + FloatHyperparameter(name="rhobeg", low=0.5, high=1.5, default=1.0), + CategoricalHyperparameter(name="tol", choices=[1e-1, 1e-2, 1e-3], default=1e-2), + # TODO rajouter initial_point et initial_bound dans les hyperparams ? ] def __init__( - self, - problem: Problem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, - backend: Optional = None + self, + problem: Problem, + params_objective_function: Optional[ParamsObjectiveFunction] = None, + backend: Optional = None, ): super().__init__(problem, params_objective_function) self.quadratic_programm = None @@ -333,11 +338,11 @@ def __init__( self.backend = backend def solve( - self, - callbacks: Optional[List[Callback]] = None, - backend: Optional = None, - use_session: Optional[bool] = False, - **kwargs: Any, + self, + callbacks: Optional[List[Callback]] = None, + backend: Optional = None, + use_session: Optional[bool] = False, + **kwargs: Any, ) -> ResultStorage: kwargs = self.complete_with_default_hyperparameters(kwargs) @@ -388,4 +393,3 @@ def retrieve_current_solution(self, result) -> Solution: """ ... - diff --git a/discrete_optimization/maximum_independent_set/mis_plot.py b/discrete_optimization/maximum_independent_set/mis_plot.py index 62fea533d..b36d6c5c5 100644 --- a/discrete_optimization/maximum_independent_set/mis_plot.py +++ b/discrete_optimization/maximum_independent_set/mis_plot.py @@ -15,14 +15,20 @@ def plot_mis_solution(solution: MisSolution, name_figure: str = ""): problem: MisProblem = solution.problem graph_nx = problem.graph_nx color_map = [] + labels = {} + ind = 0 for node in solution.chosen: if node == 1: color_map.append("red") + labels[problem.index_to_nodes[ind]] = str(problem.index_to_nodes[ind]) else: color_map.append("blue") + labels[problem.index_to_nodes[ind]] = str(problem.index_to_nodes[ind]) + ind += 1 pos = nx.kamada_kawai_layout(graph_nx) fig, ax = plt.subplots(1) nx.draw_networkx_nodes(graph_nx, pos=pos, ax=ax, node_color=color_map) + nx.draw_networkx_labels(graph_nx, pos=pos, ax=ax, labels=labels) nx.draw_networkx_edges(graph_nx, pos=pos, ax=ax) ax.set_title(name_figure) plt.show() @@ -31,13 +37,10 @@ def plot_mis_solution(solution: MisSolution, name_figure: str = ""): def plot_mis_graph(problem: MisProblem, name_figure: str = ""): graph_nx = problem.graph_nx pos = nx.kamada_kawai_layout(graph_nx) + labels = {n: str(n) for n in problem.graph_nx.nodes()} fig, ax = plt.subplots(1) - nx.draw_networkx_nodes( - graph_nx, - pos=pos, - nodelist=problem.graph_nx.nodes(), - ax=ax, - ) + nx.draw_networkx_nodes(graph_nx, pos=pos, nodelist=problem.graph_nx.nodes(), ax=ax) + nx.draw_networkx_labels(graph_nx, pos=pos, ax=ax, labels=labels) nx.draw_networkx_edges(graph_nx, pos=pos, ax=ax) ax.set_title(name_figure) plt.show() diff --git a/examples/qiskit_examples/coloring_example.py b/examples/qiskit_examples/coloring_example.py index e1ef9539d..ac9250c9f 100644 --- a/examples/qiskit_examples/coloring_example.py +++ b/examples/qiskit_examples/coloring_example.py @@ -1,7 +1,9 @@ from qiskit_aer import AerSimulator from discrete_optimization.coloring.coloring_model import ColoringProblem -from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver_MinimizeNbColor +from discrete_optimization.coloring.solvers.coloring_quantum import ( + QAOAColoringSolver_MinimizeNbColor, +) from discrete_optimization.generic_tools.graph_api import Graph @@ -22,7 +24,9 @@ def quantum_coloring(): # we create an instance of ColoringProblem coloringProblem = ColoringProblem(Graph(nodes=nodes, edges=edges)) # we create an instance of a QAOAMisSolver - coloringSolver = QAOAColoringSolver_MinimizeNbColor(coloringProblem, nb_max_color=nb_max_color) + coloringSolver = QAOAColoringSolver_MinimizeNbColor( + coloringProblem, nb_max_color=nb_max_color + ) # we initialize the solver, in fact this step transform the problem in a QUBO formulation coloringSolver.init_model() # we solve the mis problem @@ -39,5 +43,7 @@ def quantum_coloring(): sol, fit = res.get_best_solution_fit() print(sol) - print("Two nodes connected by an edge have never the same color : ", coloringProblem.satisfy(sol)) - + print( + "Two nodes connected by an edge have never the same color : ", + coloringProblem.satisfy(sol), + ) diff --git a/examples/qiskit_examples/qiskit_optuna_coloring.py b/examples/qiskit_examples/qiskit_optuna_coloring.py index 05ff6f25b..d0cdc469e 100644 --- a/examples/qiskit_examples/qiskit_optuna_coloring.py +++ b/examples/qiskit_examples/qiskit_optuna_coloring.py @@ -13,27 +13,29 @@ os.environ["DO_SKIP_MZN_CHECK"] = "1" -from discrete_optimization.coloring.coloring_model import ColoringProblem -from discrete_optimization.coloring.solvers.coloring_quantum import QAOAColoringSolver_MinimizeNbColor, VQEColoringSolver_MinimizeNbColor -from discrete_optimization.generic_tools.graph_api import Graph -from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver import logging import time -from typing import Any, Dict, List, Type, Tuple +from typing import Any, Dict, List, Tuple, Type import optuna from optuna import Trial from optuna.storages import JournalFileStorage, JournalStorage from optuna.trial import TrialState +from discrete_optimization.coloring.coloring_model import ColoringProblem +from discrete_optimization.coloring.solvers.coloring_quantum import ( + QAOAColoringSolver_MinimizeNbColor, + VQEColoringSolver_MinimizeNbColor, +) from discrete_optimization.generic_tools.callbacks.loggers import ObjectiveLogger from discrete_optimization.generic_tools.callbacks.optuna import OptunaCallback from discrete_optimization.generic_tools.do_problem import ModeOptim from discrete_optimization.generic_tools.do_solver import SolverDO - +from discrete_optimization.generic_tools.graph_api import Graph from discrete_optimization.generic_tools.optuna.timed_percentile_pruner import ( TimedPercentilePruner, ) +from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s") @@ -64,17 +66,15 @@ "qaoa": [ ( QAOAColoringSolver_MinimizeNbColor, - { - }, + {}, ), ], "vqe": [ ( VQEColoringSolver_MinimizeNbColor, - { - }, + {}, ), - ] + ], } solvers_map = {} @@ -82,9 +82,7 @@ for solver, param in solvers[key]: solvers_map[solver] = (key, param) -solvers_to_test: List[Type[SolverDO]] = [ - s for s in solvers_map -] +solvers_to_test: List[Type[SolverDO]] = [s for s in solvers_map] # we need to map the classes to a unique string, to be seen as a categorical hyperparameter by optuna # by default, we use the class name, but if there are identical names, f"{cls.__module__}.{cls.__name__}" could be used. @@ -141,6 +139,7 @@ def objective(trial: Trial): # construct kwargs for __init__, init_model, and solve kwargs = {} kwargs.update(suggested_hyperparameters_kwargs) + print(kwargs) # solver init solver = solver_class(problem=problem, nb_max_color=nb_max_color) diff --git a/examples/qiskit_examples/qiskit_optuna_mis.py b/examples/qiskit_examples/qiskit_optuna_mis.py index 3ac95ce58..97484e48e 100644 --- a/examples/qiskit_examples/qiskit_optuna_mis.py +++ b/examples/qiskit_examples/qiskit_optuna_mis.py @@ -13,15 +13,11 @@ os.environ["DO_SKIP_MZN_CHECK"] = "1" -import networkx as nx - -from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver -from discrete_optimization.maximum_independent_set.solvers.mis_quantum import QAOAMisSolver, VQEMisSolver -from discrete_optimization.maximum_independent_set.mis_model import MisProblem import logging import time -from typing import Any, Dict, List, Type, Tuple +from typing import Any, Dict, List, Tuple, Type +import networkx as nx import optuna from optuna import Trial from optuna.storages import JournalFileStorage, JournalStorage @@ -31,10 +27,15 @@ from discrete_optimization.generic_tools.callbacks.optuna import OptunaCallback from discrete_optimization.generic_tools.do_problem import ModeOptim from discrete_optimization.generic_tools.do_solver import SolverDO - from discrete_optimization.generic_tools.optuna.timed_percentile_pruner import ( TimedPercentilePruner, ) +from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver +from discrete_optimization.maximum_independent_set.mis_model import MisProblem +from discrete_optimization.maximum_independent_set.solvers.mis_quantum import ( + QAOAMisSolver, + VQEMisSolver, +) logger = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO, format="%(asctime)s:%(levelname)s:%(message)s") @@ -72,17 +73,15 @@ "qaoa": [ ( QAOAMisSolver, - { - }, + {}, ), ], "vqe": [ ( VQEMisSolver, - { - }, + {}, ), - ] + ], } solvers_map = {} @@ -90,9 +89,7 @@ for solver, param in solvers[key]: solvers_map[solver] = (key, param) -solvers_to_test: List[Type[SolverDO]] = [ - s for s in solvers_map -] +solvers_to_test: List[Type[SolverDO]] = [s for s in solvers_map] # we need to map the classes to a unique string, to be seen as a categorical hyperparameter by optuna # by default, we use the class name, but if there are identical names, f"{cls.__module__}.{cls.__name__}" could be used. diff --git a/notebooks/z_Advanced/tuto_qiskit.ipynb b/notebooks/z_Advanced/tuto_qiskit.ipynb index aa753270e..8b3077693 100644 --- a/notebooks/z_Advanced/tuto_qiskit.ipynb +++ b/notebooks/z_Advanced/tuto_qiskit.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "f3b177ce-a4b8-4f43-ae92-15c5bfcaabb5", "metadata": {}, "outputs": [], @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "26efd9d5-90c9-4fb5-ae46-f85b935ff094", "metadata": {}, "outputs": [], @@ -68,21 +68,10 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "4c36eeca-5a59-4b76-88ee-ef31192b3af9", "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAgMAAAGFCAYAAABg2vAPAAAAOXRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjguNCwgaHR0cHM6Ly9tYXRwbG90bGliLm9yZy8fJSN1AAAACXBIWXMAAA9hAAAPYQGoP6dpAABW40lEQVR4nO3deXiM58IG8HsmGwmCRBASW0KWmWwkIZaI2NJWilqToGqpvVW0qLZoq7RqKdU6qnUQWy2trZaJIEHShCyTzVIklgqJLYJsM98fPfK1JQQzeWa5f9d1rvY6Ge/c05b3nvfZJGq1Wg0iIiIyWlLRAYiIiEgslgEiIiIjxzJARERk5FgGiIiIjBzLABERkZFjGSAiIjJyLANERERGzrQyL1KpVLh69Spq1qwJiUSi7UxERESkAWq1GgUFBbC3t4dUWvH3/0qVgatXr8LBwUFj4YiIiKjqXLp0CY0bN67w55UqAzVr1iy/WK1atTSTjIiIiLTq7t27cHBwKL+PV6RSZeDR0ECtWrVYBoiIiPTMs4b4OYGQiIjIyLEMEBERGTmWASIiIiPHMkBERGTkWAaIiIiMHMsAERGRkWMZICIiMnIsA0REREaOZYCIiMjIsQwQEREZOZYBIiIiI8cyQEREZORYBoiIiIwcywAREZGRYxkgIiIyciwDRERERs5UdABNKiwqxcX8QhSXqmBuKkVTGytYWRjURyQiItI4vb9Tns0tQGR8DqJPX0fOzftQ/+1nEgCOdS0R1MoO4f6OcK5fU1RMIiIinSVRq9XqZ73o7t27sLa2xp07d1CrVq2qyPVMl27ex8wdSsScy4OJVIIyVcUf49HPOzrZYl4fORzqWlZhUiIiIjEqe//WyzkDmxJy0HXxERw/nw8ATy0Cf//58fP56Lr4CDYl5Gg9IxERkb7Qu2GC5dFnsfDAmRf6tWUqNcpUakzfrkTevSJMCHLWcDoiIiL9o1dPBjYl5LxwEfi3hQfOYDOfEBAREelPGbh08z4+2Zmu0Wt+vDMdl27e1+g1iYiI9I3elIGZO5QofcbcgOdVqlJj5g6lRq9JRESkb/SiDJzNLUDMubxnThR8XmUqNWLO5eHc9QKNXpeIiEif6EUZiIzPgYlUopVrm0glWB/HuQNERGS89KIMRJ++rvGnAo+UqdSIPnNdK9cmIiLSBzpfBu4VlSJHy5P8cvLvo7CoVKvvQUREpKt0vgxk5xdCO88E/p8awMX8Qi2/CxERkW7S+TJQXKoyqPchIiLSNTq/A6G5adX0lY2R63C+ZSM0b94czZo1Q+3atavkfYmIiETT+TLQ1MYKEkC7QwVqNb5f+Cm+uplX/n/VqVMHzZs3Ly8Hj/6+efPmcHR0hJmZmTYTERERVRmdLwNWFqZwrGuJbC1OImxia4ULedeRn5+PCxcu4Pz58+X/u3DhAhITE5GTk4OysjIAgFQqhYODwxOLQrNmzVCvXj1IJNpZCklERKRpOl8GACColR3WxWdrZXmhiVSCoJZ2kEgksLW1ha2tLXx9fR97XUlJCS5fvvxYUVAqlfj111+Rn59f/lorK6sKnyo0bdoU1atX1/jnICIielEStVr9zDtsZc9D1pazuQXotuSo1q6vmNwJTnY1X+oad+/efeJThUd/LS4uLn9tw4YNK3yqYG9vD6lU5+d1EhGRHqjs/Vsvngw416+Jjk62OH4+X6NPB0ykEgQ0t3npIgAAtWrVgqenJzw9PR/7mUqlwp9//vnEohAVFYU///yz/LUWFhZo2rTpY2Xh0d+LKGNERGTY9OLJAPDXqYVdFx9BkQaXAFqYSqGYHAiHupYau+aLePDgAS5evPjEsnD+/HkUFv7/Hgg2NjYVPlVwcHDgxEYiIipX2fu33pQBANiUkIPp2zV3yuCCvnIM9HXU2PW0Qa1W48aNG/8oB3//+0uXLkGl+qsgmZiYwNHRscKyYGNjw4mNRERGxCDLAAAsjz6LhQfOvPR1pnVvhfFBThpIJFZxcTEuXbpU4VOFW7dulb+2Zs2aFRaFpk2bolq1agI/CRERaZrBlgHgrycEn+xMR6lK/VxzCEykEphKJZgb6q7zTwQ05fbt2xU+Vbh48SJKSkrKX9uoUaMK5yo0aNCAExuJiPSMQZcB4K85BDN3KBFzLg8mUsnTS4FaBUik6Ohki3l95MLnCOiKsrIyXL16tcKnCrm5ueWvrVatGpo1a/bEpwrNmjVDzZovPwmTiIg0y+DLwCNncwsQGZ+D6DPXkZN//x87FUoA1DYtQU78fuxdOh3+rk1ExdRLhYWF/5jY+O+lkw8ePCh/bb169Z5YFJo3b47GjRvD1FQvFq4QERkUoykDf1dYVIqL+YUoLlXB3FSKpjZWeHjvDho0aIBFixZh4sSJoiMaDLVajevXr1f4VOHy5ct49J+WqakpmjRpUmFZqFOnDic2EhFpgVGWgYqEhobi+vXriIuLEx3FaBQVFSEnJ6fCsnDnzp3y11pbW1dYFJo0aQILCwuBn4SISH+xDPzN5s2bMWjQIJw5cwbOzs6i4xCAW7duVVgUsrOzUVpaCgCQSCRo3LjxEyc1Nm/eHPXr1+dTBSKiCrAM/M39+/fRoEEDTJkyBZ988onoOPQMpaWluHLlSoVl4caNG+WvrV69eoVPFZo1awYrKyuBn4SISCyWgX958803cfz4cZw+fZrfJPXcvXv3KlwueeHCBTx8+LD8tXZ2dk8sCs2bN0ejRo1gYmIi8JMYrifN37Gy4CRSoqrGMvAvBw8eRPfu3REfHw8/Pz/RcUhLVCoVcnNzK3yqcOXKlfLXmpmZoUmTJhWeMFm7dm1xH0QPla/sOX0dOTcfX9njWNcSQa3sEO7vCOf6XIpKVBVYBv6lrKwMjRs3xoABA7B06VLRcUiQhw8fIjs7u8LlkgUFBeWvrV27doVPFRwdHWFubi7wk+iO59nz49HPuecHUdVgGXiC9957D5GRkbhy5QrXvdNj1Go1bt68WeFThZycHJSVlQEApFIpGjduXOFThXr16hnFcNTL7gY6J9Qdg4xkN1AiEVgGnuDkyZNo06YNfvvtN/Ts2VN0HNIzpaWl/zgH4t9PFfLz88tfa2lpWeFThaZNm8LSUv+/EWvqnJCp3VtiQhBX+RBpA8vAE6jVari5uaFNmzZYt26d6DhkYO7evfvUcyCKiorKX9ugQYMKnyrY29vr/DkQxniCKJE+YhmowGeffYb58+cjNzeXy86oyqhUKvz5558VPlX4888/y19rbm6Opk2bVnjCpLW1tcBP8tccga6Lj6CoVKWxa1qYSqGYHMg5BEQaxjJQgfPnz6NFixaIjIxEWFiY6DhEAIAHDx489RyIwsLC8tfWrVu3wqcKDg4OMDMz02rWIavjcfx8/nPNEXgWE6kEAc1tsG6Ev8auSUQsA0/Vvn171K5dG3v27BEdheiZ1Go18vLyKiwKly5dgkr117d0qVQKR0fHCp8q2NravtTExrO5Bei25KimPtpjFJM7wcmOyw6JNKWy92+jnFIfHh6OSZMm4caNG6hXr57oOERPJZFIUK9ePdSrVw/+/o9/cy4pKfnHORCPykJycjK2b9+OW7dulb+2Ro0aFT5VaNq0KapVq/bULJHxOc8+MvwFmUglWB+Xg9mh7hq/NhE9nVE+GcjLy0PDhg2xePFiTJgwQXQcIq26ffv2Uyc2lpSUlL/W3t6+wqcKDRs2RNDXR5B9877WsjaxscSRqUFauz6RseEwwTP06tULeXl5OHHihOgoRMKUlZXh6tWrFQ5B5Obmlr+2Wg1r1B+/HtDi/gkSAGmze3DrYiIN4TDBM4SHh2Pw4MH4448/0KJFC9FxiIQwMTGBg4MDHBwcEBgY+NjPCwsLyyc2xp+5gvV52t1ISQ3gYn4h3O3FrpggMja6vZhZi0JDQ1GjRg1ERkaKjkKks6ysrODu7o5evXrhjf4Dq+Q9izW4ZJGIKsdoy4ClpSX69u2LyMhIVGKkhMjomZtWzR8XVfU+RPT/jPp3XXh4OM6cOYOTJ0+KjkKk85raWEHbpy1I/vc+RFS1jLoMdOnSBfXr18f69etFRyHSeVYWpnDU8g6BjjaWnDxIJIBRlwFTU1MMHjwYmzZtQmlpqeg4RDovqJUdTKTaeT5gIpUgqKWdVq5NRE9n1GUA+GuoIDc3F4cOHRIdhUjnhfs7amXDIQAoU6kR0ZaHFRGJYPRloHXr1mjZsiWHCogqwbl+Tfg71gLUGp7xryqDax1wK2IiQYy+DEgkEkRERGDHjh24f197O6sRGYLffvsNR78eC3VZKf7aFUAzJFBBMW8EwsLC/rHRERFVDaMvAwAQFhaGe/fuYefOnaKjEOmkBw8eYOLEiXjllVfg5eyAmd2dAA2uLfiinw9Wf/MlDhw4ABcXF6xatar88CUi0j6WAQAtWrRA27ZtOVRA9ARJSUlo3bo1fvjhByxbtgx79+7F293kmNq9pUauP617KwzydcTQoUORlZWF3r17Y/To0QgMDERmZqZG3oOIno5l4H8iIiKwf/9+5OXliY5CpBPKysqwYMEC+Pv7w8LCAidPnsSECRPKj0CeEOSM+X3lsDCVPvcKAxOpBBamUizoK8f4IKfy/9/W1hY//fQTDh06hNzcXHh6euLjjz/Gw4cPNfrZiOifWAb+Z8CAAVCr1diyZYvoKETC5eTkIDg4GDNmzMDkyZMRFxcHNze3x143yNcRismBCGhuAwDPLAWPfh7Q3AaKyYEY6Pvk1QNBQUFITU3FjBkzMH/+fHh4eHDFD5EWGe2phU/y6quv4tatWzh+/LjoKETCbNy4EWPHjkXNmjWxdu1aBAVV7kjhs7kFiIzPQfSZ68jJv/+P6YUS/LWhUFBLO0S0dXyuVQOZmZl4++23ERMTg2HDhmHhwoWwtbV9vg9FZKR4hPEL2LhxI8LCwvDHH3+gefPmouMQVanbt29jwoQJiIyMxKBBg7BixQrUqVPnha5VWFSKi/mFKC5VwdxUiqY2Vi+1s6BKpcKPP/6IadOmwcTEBF9//TWGDh1aPmRBRE9W2fs3hwn+JjQ0FFZWVtiwYYPoKERV6siRI/D09MSuXbuwfv16bNy48YWLAPDX1sXu9tbwdqwDd3vrl95iWCqVYuTIkcjKykKPHj3w5ptvomvXrjh79uxLXZeI/sIy8DdWVlbo06cP1q9fz5MMySgUFxdjxowZCAoKQpMmTZCamorw8HDRsSpUv359REZGYv/+/bh48SLkcjk+++wzFBcXi45GpNdYBv4lIiICp0+fxqlTp0RHIdKqrKwstGvXDgsXLsS8efMQHR2NJk2aiI5VKd27d4dSqcTkyZMxZ84ceHl5ITY2VnQsIr3FMvAvwcHBsLOzQ2RkpOgoRFqhVquxYsUK+Pj4oLCwEHFxcZg+fTpMTExER3sulpaW+OKLL3Dq1ClYW1ujY8eOGD16NG7duiU6GpHeYRn4F1NTUwwaNAgbN25EWVmZ6DhEGpWbm4vXXnsN48ePx/Dhw3Hq1Cm0bt1adKyXIpfLcezYMaxYsQKbN2+Gi4sLNm7cyKE+oufAMvAEERERuHbtGtc1k0HZtWsX5HI5EhMTsXv3bnz77bewtLQUHUsjpFIpxo4di8zMTHTq1AlhYWEICQnBhQsXREcj0gssA0/Qpk0bODs7c6iADEJhYSHGjBmD0NBQ+Pv7Q6lU4tVXXxUdSyvs7e3x888/Y9euXcjMzIS7uzsWLFiAkpIS0dGIdBrLwBNIJBKEh4dj27ZtPMmQ9FpiYiJ8fHywdu1afP/999i5cyfs7OxEx9K61157Denp6Rg7dixmzpyJNm3aID4+XnQsIp3FMlCB8PBw3Lt3D7t27RIdhei5lZWV4fPPP0e7du1Qq1YtJCUl4e233zaqTXpq1KiBr7/+GgkJCTAzM0O7du0wYcIE3LlzR3Q0Ip3DMlABJycn+Pv7c6iA9M6FCxcQGBiIjz/+GB988AGOHz+OVq1aiY4ljI+PD+Lj47F48WKsWbMGbm5u2LZtGycYEv0Ny8BThIeH47fffuNJhqQX1Go11q5dC09PT1y+fBmHDx/GZ599BjMzM9HRhDMxMcE777yDzMxMtGnTBv369cPrr7+OnJwc0dGIdALLwFMMHDgQarUaP//8s+goRE918+ZNDBo0CMOGDUPv3r2RkpKCjh07io6lcxwcHPDLL79g+/btOHnyJNzc3LB48WKUlpaKjkYkFMvAU9jZ2aF79+4cKiCdFhUVBQ8PDxw4cACbNm3C2rVrYW1tLTqWzpJIJOjTpw8yMzMxfPhwTJkyBf7+/jh58qToaETCsAw8Q3h4OI4dO8b1yqRzioqKMHXqVHTt2hWtWrWCUqnEwIEDRcfSG7Vq1cKyZctw4sQJlJaWws/PD5MnT8a9e/dERyOqciwDz9C7d2+eZEg6Jy0tDX5+fli2bBkWLlyIgwcPonHjxqJj6SV/f38kJiZi/vz5WLlyJdzc3LiKiIwOy8AzWFlZoXfv3jzJkHSCSqXC0qVL0aZNG5SWluL333/HlClTIJXyt/LLMDMzw7Rp05Ceng53d3eEhoaiX79+uHr1quhoRFWCf4JUQnh4OLKyspCUlCQ6Chmxq1evIiQkBO+++y7GjBmDxMREeHp6io5lUJo1a4a9e/di06ZNiI2NhYuLC7799lueU0IGj2WgErp164Z69epxIiEJs337dsjlcqSmpmLfvn1YsmQJqlevLjqWQZJIJBg4cCAyMzMxePBgTJgwAe3bt0dqaqroaERawzJQCTzJkEQpKCjAiBEj8MYbbyAwMBBKpRI9evQQHcso1KlTBytXrkRMTAwKCgrg4+ODDz74gFuUk0FiGaik8PBw/Pnnn4iOjhYdhYxEXFwcvL29sXnzZvzwww/Ytm0bbG1tRccyOh06dEBSUhLmzJmDpUuXQiaTYf/+/aJjEWkUy0Al+fn5wcnJiUMFpHWlpaWYPXs2OnToAFtbWyQnJ2PEiBFGda6ArjE3N8eHH34IpVKJZs2aoWfPnggLC0Nubq7oaEQawTJQSX8/yfDBgwei45CBOnfuHDp06IDPPvsMH330EWJjY+Hk5CQ6Fv2Ps7MzFAoF1q5di4MHD8LFxQWrVq2CSqUSHY3opbAMPIfw8HAUFBRwDTJpnFqtxurVq+Hl5YUbN24gJiYGn3zyCUxNTUVHo3+RSCQYMmQIMjMz0bt3b4wePRqBgYHIyMgQHY3ohbEMPAdnZ2f4+flxqIA0Ki8vD2+88QZGjhyJgQMHIjk5Ge3atRMdi57B1tYWP/30E6Kjo3H9+nV4eXnho48+wsOHD0VHI3puLAPP6dFJhvn5+aKjkAE4cOAAPDw8cOTIEWzbtg2rV69GzZo1Rcei59C5c2ekpKRgxowZWLBgATw8PHDo0CHRsYieC8vAcxo4cCBUKhVPMqSX8uDBA7zzzjvo0aMHZDIZlEol+vbtKzoWvaBq1aphzpw5SElJQYMGDRAcHIxhw4bx+HPSGywDz6l+/fro1q0bhwrohaWkpMDX1xcrV67E0qVLsW/fPtjb24uORRrg6uqKw4cP44cffsCuXbvg4uKC//73v9zKnHQey8ALCA8PR2xsLC5evCg6CukRlUqFhQsXws/PDyYmJkhMTMSkSZN4roCBkUqlGDFiBLKystCzZ0+8+eabCA4OxpkzZ0RHI6oQ/xR6Ab1794alpSVPMqRKu3TpErp27Ypp06Zh4sSJ+P333yGTyUTHIi2ys7PD+vXrsX//fmRnZ8PDwwOffvopioqKREcjegzLwAuoUaMGevfujcjISD7+o2fasmULPDw8cObMGSgUCixcuBAWFhaiY1EV6d69O5RKJSZPnoy5c+fC29sbMTExomMR/QPLwAsKDw9HRkYGUlJSREchHXXnzh0MHToUAwcORPfu3ZGamorg4GDRsUgAS0tLfPHFFzh16hRq166NTp06YdSoUbh165boaEQAWAZeWLdu3WBra4v169eLjkI6KDY2Fp6envjll1/w3//+F5s2bULdunVFxyLB5HI5YmNjsWLFCmzZsgUuLi7YuHEjnzCScCwDL8jMzIwnGdJjSkpK8OGHHyIwMBCNGzdGSkoKhg4dynMFqJxUKsXYsWORlZWFwMBAhIWFISQkBOfPnxcdjYwYy8BLCA8Px9WrV3HkyBHRUUgHnD59GgEBAfjyyy8xd+5cHD58GM2aNRMdi3RUw4YNsWXLFuzatQuZmZmQyWRYsGABSkpKREcjI8Qy8BL8/f3RokULDhUYObVajZUrV8LHxwd3797F8ePH8eGHH/JcAaqU1157Denp6Rg3bhxmzpyJ1q1bIy4uTnQsMjIsAy/h7ycZcj9y43T9+nWEhoZizJgxGDJkCE6dOgVfX1/RsUjP1KhRAwsXLkRiYiIsLCwQEBCA8ePH486dO6KjkZFgGXhJ4eHhuHv3Lnbv3i06ClWxPXv2QC6XIy4uDr/++iu+//57WFlZiY5Feszb2xtxcXFYvHgx1q5dC1dXV2zdupUTDEnrWAZeUsuWLdGmTRsOFRiR+/fvY9y4cXjttdfQpk0bKJVKhIaGio5FBsLExATvvPMOMjIy4Ovri/79+yM0NBQ5OTmio5EBYxnQgIiICOzduxc3b94UHYW07OTJk/Dx8cFPP/2Eb7/9Frt370aDBg1ExyID5ODggF9//RXbt29HUlIS3NzcsHjxYpSWloqORgaIZUADBg4ciLKyMmzdulV0FNKSsrIyfPHFF2jbti2srKxw6tQpjBs3jksGSev69OmDjIwMDB8+HFOmTIG/vz9OnjwpOhYZGJYBDWjQoAG6du3KoQIDlZ2djaCgIHz44YeYOnUqTpw4AVdXV9GxyIjUqlULy5YtQ1xcHMrKyuDn54fJkyejoKBAdDQyECwDGhIREYGYmBhkZ2eLjkIaFBkZCQ8PD2RnZyM6OhpffPEFzM3NRcciI+Xn54eEhATMnz8fK1euhLu7O3bu3Ck6FhkAlgEN6d27N6pXr46NGzeKjkIacOvWLYSFhSEiIgK9evVCSkoKAgMDRccigpmZGaZNm4aMjAzIZDK8/vrreOONN3DlyhXR0UiPsQxoSM2aNfH6669j/fr1XAak5w4fPgxPT0/s3bsXGzZswPr161G7dm3RsYj+oWnTptizZw82bdqEY8eOwdXVFd9++y23R6cXwjKgQREREUhPT0dqaqroKPQCioqK8P7776NLly5o3rw5UlNTMXjwYNGxiCokkUgwcOBAZGZmIiwsDBMmTED79u15mio9N5YBDerevTtsbW0RGRkpOgo9p4yMDLRt2xZLlizB/PnzERUVBUdHR9GxiCqlTp06+P777xEbG4uCggK0bt0aH3zwAe7fvy86GukJlgENMjMzw4ABA7BhwwaoVCrRcagS1Go1li9fjtatW6OoqAjx8fF4//33YWJiIjoa0XNr3749kpKSMHfuXCxduhTu7u7Yt2+f6FikB1gGNCwiIgJXrlzhSYZ64Nq1a3jllVcwceJEjBw5EomJifD29hYdi+ilmJubY+bMmUhLS0OLFi0QEhKCwYMH49q1a6KjkQ5jGdCwtm3bonnz5hwq0HG//vor5HI5kpKSsHfvXixbtgyWlpaiYxFpjJOTEw4ePIi1a9dCoVDA1dUV//nPf/jUkp6IZUDDJBIJwsLCsHXrVp5kqIPu3buHUaNGoXfv3ggICIBSqURISIjoWERaIZFIMGTIEGRlZaFPnz54++230alTJ2RkZIiORjqGZUALwsPDcefOHezZs0d0FPqb33//Hd7e3tiwYQP+85//4JdffkG9evVExyLSOhsbG/z444+Ijo7GjRs34OXlhY8++ohfWKgcy4AWuLi4oHXr1hwq0BGlpaX49NNPERAQgDp16iA5ORmjRo3iuQJkdDp37ozU1FTMnDkTX375JeRyOaKiokTHIh3AMqAl4eHh2LNnD27duiU6ilE7f/48OnXqhNmzZ2PmzJk4duwYnJ2dRcciEsbCwgKzZ89GSkoK7O3t0bVrVwwbNgx5eXmio5FALANaMmjQIJSWlvIkQ0HUajXWrFkDT09PXLt2DUePHsXcuXNhZmYmOhqRTnBxcUF0dDRWr16NXbt2wcXFBWvWrOEOqkaKZUBLGjZsiODgYA4VCJCfn48BAwZg+PDheOONN5CcnIz27duLjkWkc6RSKd566y1kZWWhZ8+eGD58OIKDg3HmzBnR0aiKsQxoUXh4OI4cOYKcnBzRUYzGwYMH4eHhgaioKGzZsgVr1qxBrVq1RMci0ml2dnZYv349Dhw4gOzsbMjlcsydOxdFRUWio1EVYRnQoj59+vAkwyry8OFDTJ48Gd27d4erqyuUSiX69+8vOhaRXunWrRvS0tIwZcoUfPrpp/Dy8kJMTIzoWFQFWAa0qFatWggNDeVQgZYplUr4+flhxYoVWLRoEQ4cOIBGjRqJjkWkl6pXr4558+bh1KlTqFOnDjp16oRRo0bh5s2boqORFrEMaFl4eDiUSiVPMtQClUqFxYsXo02bNlCr1UhISMDkyZMhlfI/a6KXJZfLERsbi++++w5btmyBq6srNmzYwAmGBop/ampZjx49YGNjw6cDGnblyhX06NED7733HsaPH4+EhAR4eHiIjkVkUKRSKcaMGYOsrCwEBgYiPDwcPXv2xB9//CE6GmkYy4CWmZubY8CAAdi4cSP3BNeQrVu3Qi6XIyMjAwcOHMCiRYtQrVo10bGIDFbDhg2xZcsW7N69G6dPn4ZMJsP8+fNRUlIiOhppCMtAFQgPD8elS5c4Eecl3b17F8OHD0f//v3RpUsXpKamolu3bqJjERmNV199Fenp6Rg/fjxmzZqF1q1bIy4uTnQs0gCWgSoQEBCApk2bYv369aKj6K3jx4/Dy8sLW7duxU8//YSff/4ZNjY2omMRGR0rKyssXLgQCQkJsLCwQEBAAMaNG4c7d+6IjkYvgWWgCkgkEoSHh2Pr1q1ct/ucSkpK8PHHH6Njx45o0KABkpOT8eabb/JcASLBvL29ERcXhyVLlmDdunVwdXXF1q1bOcFQT7EMVJHw8HDcvn0be/fuFR1Fb5w9exYdOnTAvHnzMHv2bBw9ehQtWrQQHYuI/sfExASTJk1CRkYG/Pz80L9/f/Tq1QvZ2dmio9FzYhmoIq6urvDx8eFQQSWo1WqsWrUKXl5euHnzJo4dO4aPPvoIpqamoqMR0RM4ODjgl19+wY4dO5CcnAx3d3csWrQIpaWloqNRJbEMVKHw8HDs3r0bt2/fFh1FZ924cQN9+vTB6NGjERYWhqSkJPj7+4uORUSV0Lt3b2RkZOCtt97C1KlT4e/vj5MnT4qORZXAMlCFBg0ahJKSEmzbtk10FJ20b98+eHh4IDY2Fjt27MCqVatQo0YN0bGI6DnUqlUL33zzDeLi4lBWVgY/Pz+8++67KCgoEB2NnoJloArZ29sjODiYQwX/8uDBA0ycOBEhISHw9PSEUqlE7969Rcciopfg5+eHxMRELFiwAKtWrYK7uzt27twpOhZVgGWgij06yfDy5cuio+iEpKQktG7dGj/88AOWLVuG3377DQ0bNhQdi4g0wNTUFFOnTkV6ejpkMhlef/11vPHGG7hy5YroaPQvLANVrG/fvrCwsDD6kwzLysrw5Zdfwt/fH+bm5khMTMSECRO4ZJDIADVt2hR79uzB5s2bcezYMbi6umL58uUoKysTHY3+h2Wgij06ydCYhwpycnLQtWtXTJ8+He+++y7i4+Ph7u4uOhYRaZFEIsGAAQOQlZWFsLAwTJw4EQEBAUhJSREdjcAyIER4eDhSU1ORlpYmOkqV27RpEzw8PHDu3DlERUXhyy+/hIWFhehYRFRFateuje+//x7Hjh1DYWEhWrdujffffx+FhYWioxk1lgEBevbsibp16xrVSYa3b99GREQEBg8ejJCQEKSmpiIoKEh0LCISJCAgAKdOncLcuXPxzTffQCaTYd++faJjGS2WAQEenWQYGRlpFCcZHj16FJ6enti1axfWr1+PDRs2oE6dOqJjEZFg5ubmmDlzJtLS0tCiRQuEhIRg0KBBuHbtmuhoRodlQJBHJxnGxsaKjqI1xcXFmDFjBjp37owmTZogJSUF4eHhnCRIRP/g5OSEgwcPYt26dYiKioKrqyv+85//GMWXJV3BMiBIQEAAmjRpYrBDBVlZWWjXrh0WLlyIefPmITo6Gk2bNhUdi4h0lEQiQUREBLKystC3b1+8/fbb6NSpE9LT00VHMwosA4JIpVKEh4djy5YtBnWSoVqtxnfffQcfHx8UFhYiLi4O06dPh4mJiehoRKQHbGxssHr1ahw+fBh5eXnw9vbGrFmz8ODBA9HRDBrLgECPTjL87bffREfRiNzcXLz22msYN24c3nzzTZw6dQqtW7cWHYuI9FBgYCBSUlIwc+ZMfPXVV/Dw8EBUVJToWAaLZUAgNzc3eHl5GcRQwa5duyCXy5GQkIBdu3ZhxYoVsLS0FB2LiPSYhYUFZs+ejZSUFDRq1Ahdu3bF0KFDcePGDdHRDA7LgGARERHYtWsX7ty5IzrKCyksLMSYMWMQGhoKPz8/KJVKvPbaa6JjEZEBcXFxQXR0NFavXo3du3fD1dUVa9asgVqtFh3NYLAMCDZo0CAUFxfr5UmGiYmJ8PHxwdq1a/Hdd99h165dqF+/vuhYRGSAJBIJ3nrrLWRlZaFnz54YPnw4unTpgtOnT4uOZhBYBgRr1KgRgoKC9GqooKysDJ9//jnatWuHmjVrIikpCWPGjOGSQSLSOjs7O6xfvx4HDhzApUuX4OHhgblz5xrURGwRWAZ0QEREBKKjo/XiJK8LFy4gMDAQH330Ed5//30cP34crVq1Eh2LiIxMt27doFQqMXXqVHz66afw8vLC0aNHRcfSWywDOqBv374wNzfX6ZMM1Wo11q1bB09PT1y+fBlHjhzB559/DnNzc9HRiMhIVa9eHZ9//jmSkpJQt25dBAYGYuTIkbh586boaHqHZUAHWFtbo1evXjo7VHDr1i0MGjQIQ4cORe/evZGSkoKOHTuKjkVEBACQyWSIiYnBd999h61bt8LFxQWRkZGcYPgcWAZ0REREBJKTk3Vut61Dhw7Bw8MDBw4cwKZNm7B27VpYW1uLjkVE9A9SqRRjxoxBZmYmgoKCEBERgZ49e+KPP/4QHU0vsAzoiJCQENSpU0dnng4UFRVh6tSpCA4OhrOzM1JTUzFw4EDRsYiInqphw4bYvHkz9uzZg9OnT0Mmk2H+/PkoKSkRHU2nsQzoCHNzc/Tv3x8bNmwQfjhHeno6/Pz88M033+Crr76CQqGAg4OD0ExERM/jlVdeQXp6OiZMmIBZs2bBx8cHJ06ceOnrFhaVIv3qHSTl3EL61TsoLCrVQFrxJOpKDKrcvXsX1tbWuHPnDmrVqlUVuYxSTEwMOnXqhKNHjwoZk1epVFi+fDnef/99tGjRApGRkfDy8qryHEREmpScnIxRo0bh5MmTGDNmDObNm4fatWtX+tefzS1AZHwOok9fR87N+/j7TVMCwLGuJYJa2SHc3xHO9WtqOv5Lqez9m2VAh6hUKjRr1gwhISH4/vvvq/S9r169irfeegv79+/HpEmTMH/+fFSvXr1KMxARaUtZWRm+/fZbfPjhh6hRowa++eYb9OvX76n7o1y6eR8zdygRcy4PJlIJylQV3y4f/byjky3m9ZHDoa5ubMde2fs3hwl0iFQqRVhYGLZs2YLi4uIqe98dO3bAw8MDKSkp2LdvH5YuXcoiQEQGxcTEBJMmTUJmZibatm2LAQMGoFevXsjOzn7i6zcl5KDr4iM4fj4fAJ5aBP7+8+Pn89F18RFsSsjR7AfQMpYBHRMREYFbt25VyUmG9+7dw4gRI9C3b1907NgRSqUSPXr00Pr7EhGJ0rhxY+zYsQM7duxAcnIy3NzcsGjRIpSW/v/Y//Los5i+XYmiUtUzS8C/lanUKCpVYfp2JZZHn9V0fK1hGdAx7u7u8PT01Pqqgri4OHh5eWHz5s344YcfsH37dtja2mr1PYmIdEXv3r2RkZGBkSNHYurUqfDz80NiYiI2JeRg4YEzGnmPhQfOYLOePCFgGdBB4eHhWjvJsLS0FHPmzEGHDh1ga2uL5ORkjBgxgucKEJHRqVWrFpYuXYr4+Hio1WoEdA/FzG3JGn2Pj3em49LN+xq9pjawDOigwYMHo6ioCNu3bweguaUs586dQ4cOHTB37lzMmjULMTExcHJy0mR0IiK94+vri4SEBPhPWIwyDW9aWKpSY+YOpWYvqgWmogPQ4xo3boyAkL5YEnMFa/KiX3opi1qtxk8//YRJkyahfv36iI2NRbt27bT6GYiI9MmF/Ae4VFIDEg1/RS5TqRFzLg/nrhfAyU63lh3+HZ8M6JhLN+9jyOp4XPYYjtv1PJD9ryIAAGoA2TfvY118NrotOYohq+MrfAyVn5+Pfv36YcSIERgwYACSk5NZBIiI/iUyPgcmUu0Ml5pIJVgfp9tzB1gGdMi/l7JIpCZPff2zlrIcOHAAcrkchw8fxtatW/Hjjz+iZk3dbaZERKJEn77+3CsHKqtMpUb0metaubamsAzoCE0uZXnw4AHeeecd9OjRAzKZDEqlEm+88YaWkhMR6bd7RaXI0fIkv5z8+zq9dTHLgA7Q9FIWr77jsHLlSixZsgT79u2Dvb29Rq5NRGSIsvMLHxuO1TQ1gIv5hVp+lxfHCYSCXbp5H5/s1NyxxWq1GkXy17Hr40no1s5bY9clIjJUxaVVczhcVb3Pi+CTAcFm7lCiVIPjVBKJBCZm5libUXXbGRMR6TNz06q5FVbV+7wI3U1mBM7mFiDmXJ7GJ638fSkLERE9XVMbK2h72zXJ/95HV7EMCGTsS1mIiHSBlYUpHLV8yqCjjSWsLHR3ZJ5lQCBjX8pCRKQrglrZafXLWVBLO61cW1NYBgThUhYiIt0R7u+o1S9nEW0dtXJtTWEZEIRLWYiIdIdz/ZpwqyOBWlWm0euaSCXo6GSr01sRAywDwnApCxGRbnj48CHee+89HJz3FqQa/ppmKpVgXh+5Rq+pDSwDgnApCxGReGlpafDz88O3336Lr2ZPx7y+Xhq9/txQdzhoeXKiJuju1EYD92gpizaHCnR9KQsRkSgqlQrLli3DBx98AGdnZyQkJMDDwwMAkH+/WCO7wk7r3goDfXV7rsAj/NooCJeyEBGJ8eeffyIkJATvvvsuxowZ848iAAATgpwxv68cFqbS515hYCKVwMJUigV95Rgf5KTp6FrDMiCQsS9lISKqar/88gvkcjlSU1Oxf/9+LFmyBNWqVXvsdYN8HaGYHIiA5jYA8Mw/qx/9PKC5DRSTA/XmicAj/NooULi/I9acuKiVa+vDUhYioqpy7949TJ48GT/88AN69+6NVatWwdbW9qm/xqGuJdaN8MfZ3AJExucg+sx15OTf/8fwrgR/PYUNammHiLaOOr9qoCIsAwI516+Jjk62OH4+X6PrW9WqMjQ2LYRjbQuNXZOISF/9/vvvCA8Px9WrV7Fq1SqMGDECEknln8o616+J2aHumA13FBaV4mJ+IYpLVTA3laKpjZVBDMdymECweX3kMNXwUIGpFDi5ciq8vLwQExOj0WsTEemL0tJSfPrppwgICECdOnWQnJyMkSNHPlcR+DcrC1O421vD27EO3O2tDaIIACwDwjnUtcScUHeNXnNeXy8kRO+DtbU1OnXqhFGjRuHmzZsafQ8iIl124cIFdO7cGbNnz8aMGTNw7NgxODs7i46ls1gGdMAgX0dM7d5SI9d6tJRFLpfj2LFj+O677/Dzzz/DxcUFkZGRUKu1ve8hEZE4arUaa9euhaenJ65cuYIjR47g008/hZmZmehoOo1lQEdoYymLVCrFmDFjkJmZiS5duiAiIgLdu3fHuXPnNB2fiEi4W7duYdCgQRg2bBj69OmD5ORkdOjQQXQsvcAyoEP+vZRF8owtiSq7lKVhw4bYtGkT9u7di3PnzkEmk+Hzzz9HcXGxZj8AEZEg0dHR8PDwwIEDB7B582b897//hbW1tehYeoNlQMc8Wspy8N1OsLp6EhbFd/Dv5wQSAE1sLDHEvwkUkzth3Qj/Sm13GRISgvT0dLzzzjv45JNP4O3tjdjYWK18DiKiqlBcXIwPPvgAwcHBcHJyQmpqKgYMGCA6lt6RqCsxiHz37l1YW1vjzp07qFWrVlXkMnr37t1DnTp1sGTJErw58m2NL2VJTU3F6NGjER8fj1GjRmHBggWoU6eOhtITEWlfZmYmwsPDkZaWhs8//xxTpkyBVMrvuH9X2fs3/6npqJiYGJSWlqJr165aWcri4eGBY8eOYcWKFdi8eTNcXFywceNGTjAkIp2nVquxYsUK+Pj44OHDh4iPj8e0adNYBF4C/8npKIVCgcaNG6NlS82sMngSExMTjB07FpmZmQgMDERYWBh69uyJP/74Q2vvSUT0MnJzc9GrVy+MHz8eI0aMQGJiIry9vUXH0nssAzpKoVAgODj4pTbHqCx7e3ts2bIFu3fvxunTpyGTyfDFF19wgiER6ZTdu3dDLpcjISEBu3fvxvLly2FpqfvHA+sDlgEddP36daSmpqJr165V+r6vvvoq0tPTMWHCBHz00Udo3bo1jh8/XqUZiIj+7f79+xg3bhx69eoFPz8/KJVKvPrqq6JjGRSWAR106NAhAECXLl2q/L2trKzw1VdfITExEZaWlmjfvj3GjBmDW7duVXkWIqJTp06hdevWWLNmDVasWIFdu3bBzo4nsmoay4AOioqKgpubG+zt7YVl8PLywvHjx7F8+XJs2LABrq6u2LRpEycYElGVKCsrw4IFC9C2bVtYWlri1KlTGDt2bJUMnRojlgEdo1arcfDgwSofIngSExMTjB8/HpmZmejQoQMGDx6MV155BRcuXBAdjYgMWE5ODoKDgzFjxgy89957OHHiBFxcXETHMmgsAzrm/PnzyM7ORnBwsOgo5Ro1aoStW7di586dyMjIgLu7OxYsWICSkhLR0YjIwGzatAkeHh44f/48Dh06hPnz58Pc3Fx0LIPHMqBjoqKiYGJigsDAQNFRHtOrVy+kp6dj3LhxmDlzJnx8fHDixAnRsYjIANy5cwdDhgzB4MGDERISgpSUFHTu3Fl0LKPBMqBjFAoFfH19dXZP7Ro1amDhwoVITExE9erV0b59e4wdOxa3b98WHY2I9FRsbCw8PT2xc+dOrFu3Dhs2bOCOqFWMZUCHqFQqHDp0SCfmCzyLt7c3Tpw4gaVLlyIyMhKurq7YsmULJxgSUaWVlJRg1qxZCAwMhIODA1JSUhAREcFJggKwDOiQlJQU5Ofn60UZAP6aYDhx4kRkZGQgICAAAwcOxKuvvsoJhkT0TGfOnEH79u2xYMECzJ07F4cPH0bTpk1FxzJaLAM6RKFQoHr16mjbtq3oKM+lcePG2LZtG3799VekpaXB3d0dX375JScYEtFj1Go1Vq1aBW9vb9y+fRvHjx/Hhx9+CBMTE9HRjBrLgA6JiopCp06dYGFhITrKCwkNDUVGRgbGjBmDGTNmoE2bNoiLixMdi4h0RF5eHvr06YPRo0cjPDwcp06dgq+vr+hYBJYBnVFUVISjR4/qzRBBRWrUqIFFixYhISEBZmZmCAgIwPjx43Hnzh3R0YhIoP3790MulyM2NhY7duzAf/7zH9SoUUN0LPoflgEdERcXhwcPHujU/gIvw8fHB/Hx8ViyZAnWrl0LV1dXbN26lRMMiYzMgwcP8M4776Bnz57w9PSEUqlE7969Rceif2EZ0BEKhQI2Njbw9PQUHUVjTExMMGnSJGRkZMDf3x/9+/dHr169cPHiRdHRiKgKpKamwtfXFytXrsTSpUuxd+9eNGzYUHQsegKWAR2hUCjQpUsXSKWG96/EwcEBO3bswI4dO5CSkgJ3d3csXLgQpaWloqMRkRaoVCosWrQIvr6+MDExQWJiIiZNmmSQf74ZCv6b0QF37txBQkKC3s8XeJbevXsjIyMDo0aNwgcffIA2bdrg999/Fx2LiDToypUr6NGjB6ZMmYIJEyYgPj4eMplMdCx6BpYBHXDkyBGUlZUZfBkAgJo1a2LJkiWIj4+HiYkJ2rZti4kTJ+Lu3buioxHRS9q2bRs8PDyQkZGBgwcP4uuvv0a1atVEx6JKYBnQAVFRUWjatCmaN28uOkqVadOmDeLj47Fo0SL89NNPcHV1xbZt2zjBkEgPFRQU4K233kK/fv0QFBSE1NRUo/hyY0hYBnSAQqEwyt84pqamePfdd5GRkYHWrVujX79+CA0NRXZ2tuhoRFRJcXFx8Pb2xs8//4wff/wRP//8M2xsbETHoufEMiDY1atXkZGRYTBLCl+Eo6Mjfv31V2zfvh1JSUlwc3PD119/zQmGRDqstLQUc+bMQYcOHVCvXj0kJydj+PDhPFdAT7EMCHbo0CEAQJcuXQQnEUsikaBPnz7IyMjAyJEjMW3aNPj6+iIhIUF0NCL6l/Pnz6NTp06YO3cuZs2ahZiYGLRo0UJ0LHoJLAOCKRQKeHp6ws7OTnQUnVCrVi0sXboU8fHxkEgk8Pf3x6RJkzjBkEgHqNVqrFmzBp6ensjNzUVsbCxmz54NU1NT0dHoJbEMCKRWqxEVFWXUQwQV8fX1xe+//46FCxdi9erVcHNzw44dOzjBkEiQmzdvYsCAARg+fDj69euH5ORktGvXTnQs0hCWAYHOnDmDy5cvG+XkwcowNTXFe++9h4yMDHh7e6Nv377o3bs3cnJyREcjMiqHDh2Ch4cHoqKi8PPPP+Onn35CzZo1RcciDWIZEEihUMDU1BQdO3YUHUWnNWnSBDt37sS2bduQmJgINzc3LF68mBMMibSsqKgIU6dORXBwMFq1aoXU1FT069dPdCzSApYBgaKiotCuXTue3FUJEokEffv2RWZmJoYPH44pU6bA398fJ0+eFB2NyCClp6fD398fy5Ytw8KFC3Hw4EE0btxYdCzSEpYBQcrKyhAdHc0hgudUq1YtLFu2DHFxcSgrK4Ofnx/effddFBQUiI5GZBDUajWWLVuGNm3aoKSkBL///jumTJnCcwUMHP/tCnLq1Cncvn2bkwdfkJ+fHxITE/Hll19i1apVcHNzw6+//io6FpFeu3btGl555RVMmjQJo0aNQmJiokGdpEoVYxkQRKFQoEaNGvDz8xMdRW+ZmppiypQpSE9Ph4eHB3r37o0+ffrg0qVLoqMR6Z2dO3dCLpcjKSkJe/fuxTfffIPq1auLjkVVhGVAEIVCgcDAQJiZmYmOoveaNm2K3bt34+eff0Z8fDzc3NywdOlSlJWViY5GpPMKCwvx9ttv4/XXX0dAQACUSiVCQkJEx6IqxjIgwIMHD3Ds2DHOF9AgiUSCfv36ITMzE0OHDsXkyZM5wZDoGRITE+Hj44N169bh+++/xy+//IJ69eqJjkUCsAwIcOzYMRQVFbEMaIG1tTW+/fZbHD9+HCUlJfDz88PkyZNx79490dGIdEZZWRnmzZuHdu3aoWbNmkhKSsLbb7/NcwWMGMuAAFFRUahfvz7c3d1FRzFYbdu2RWJiIubPn4+VK1fCzc0NO3fuFB2LSLjs7GwEBQVh1qxZmDZtGo4fP45WrVqJjkWCsQwIoFAoEBwczBauZWZmZpg2bRoyMjIgk8nw+uuvo2/fvrh8+bLoaERCREZGwsPDA9nZ2Th8+DDmzZsHc3Nz0bFIB7AMVLGbN2/i5MmTXFJYhZo2bYo9e/Zgy5YtOHHiBFxdXfHNN99wgiEZjdu3byMsLAwRERF47bXXkJKSgk6dOomORTqEZaCKHT58GGq1mvMFqphEIkH//v2RmZmJIUOG4N1330Xbtm2RlJQkOhqRVh09ehSenp7Ys2cPIiMjERkZidq1a4uORTqGZaCKKRQKODs7w9HRUXQUo1S7dm2sWLGifBJnmzZtMGXKFE4wJINTXFyMGTNmoHPnzmjSpAlSU1MRFhYmOhbpKJaBKsYji3VDu3btcPLkScybNw/fffcd3N3dsXv3btGxiDTi9OnTCAgIwMKFC/H5558jOjoaTZo0ER2LdBjLQBXKycnBmTNnOESgI8zMzPDBBx8gLS0Nrq6u6NWrF/r164crV66Ijkb0QtRqNb7//nt4e3ujoKAAJ06cwIwZM2BiYiI6Guk4loEqFBUVBYlEgqCgINFR6G+aN2+O3377DZs2bUJsbCxcXV2xfPlyTjAkvXLjxg28/vrrGDt2LIYNG4ZTp06hTZs2omORnmAZqEJRUVHw8fFB3bp1RUehf5FIJBg4cCCysrIQFhaGiRMnIiAgAMnJyaKjET3Tb7/9Brlcjri4OOzcuRPfffcdrKysRMciPcIyUEXUajWioqI4RKDjateuje+//x7Hjh1DYWEh2rRpg2nTpqGwsFB0NKLHPHjwABMnTsQrr7wCHx8fpKamolevXqJjkR5iGagiGRkZuHbtGicP6omAgACcOnUKn332GZYvXw53d3fs2bNHdCyicsnJyWjTpg1++OEHLF++HHv27EGDBg1ExyI9xTJQRRQKBSwsLNChQwfRUaiSzM3NMX36dKSlpaFVq1Z47bXX0L9/f1y9elV0NDJiKpUKCxcuhJ+fH8zMzHDy5EmMHz+eO5rSS2EZqCIKhQLt27fn+eB6qEWLFti3bx82bNiAo0ePwtXVFStWrOAEQ6pyly9fRrdu3fD+++/jnXfeKT+ym+hlsQxUgZKSEhw5coRDBHpMIpFg8ODByMrKwqBBgzB+/Hi0b98eKSkpoqORkfj555/h4eGB06dPQ6FQ4KuvvoKFhYXoWGQgWAaqQEJCAgoKCjh50ADUqVMHK1euRGxsLAoKCtC6dWu8//77nGBIWnP37l28+eabGDBgALp27YrU1FR06dJFdCwyMCwDVSAqKgrW1tZo3bq16CikIe3bt0dSUhLmzp2LZcuWQSaT4bfffhMdiwzM8ePH4eXlhW3btmHNmjXYvHkzlyaTVrAMVAGFQoGgoCDuAmZgzM3NMXPmTCiVSjg7O+OVV17BwIED8eeff4qORnqupKQEH3/8MTp27IgGDRogJSUFw4YN4yRB0hqWAS0rLCzEiRMnOERgwJycnLB//35ERkYiOjoaLi4u+O6776BSqURHIz107tw5dOzYEfPmzcMnn3yCo0ePonnz5qJjkYFjGdCymJgYlJSUcPKggZNIJAgLC0NWVhYGDBiAcePGoX379lAqlaKjkZ5Qq9VYvXo1vLy8kJeXh9jYWHz88ccwNTUVHY2MAMuAlikUCjRq1AitWrUSHYWqQN26dbFq1SocPXoUd+7cgY+PD6ZPn4779++LjkY6LD8/H/369cPIkSMxcOBAJCUloW3btqJjkRFhGdCyR0cWc6zPuHTs2BHJycn45JNPsGTJEshkMuzbt090LNJBBw8ehIeHBw4fPoxt27Zh9erVqFmzpuhYZGRYBrToxo0bSE5O5nwBI2Vubo5Zs2ZBqVSiefPmCAkJweDBg3Ht2jXR0UgHPHz4EO+99x66d+8ONzc3pKamom/fvqJjkZFiGdCiQ4cOAQDnCxg5Z2dnHDx4EOvWrYNCoYCLiwtWrlzJCYZGLC0tDX5+fvj222+xaNEi7N+/H40aNRIdi4wYy4AWRUVFwdXVFfb29qKjkGASiQQRERHIysrCG2+8gTFjxqBDhw5IS0sTHY2qkEqlwtKlS9GmTRuo1WokJCRg8uTJkEr5RzGJxf8CtUihUHCIgP7BxsYGq1evxuHDh3Hr1i14e3tjxowZnGBoBP7880+EhITg3XffxZgxY5CQkAAPDw/RsYgAsAxozfnz53HhwgUOEdATBQYGIjk5GR9//DEWLVoEuVyO/fv3i45FWvLLL79ALpcjNTUV+/btw5IlS1CtWjXRsYjKsQxoSVRUFKRSKTp37iw6CukoCwsLfPTRR1AqlWjSpAl69uyJsLAw5Obmio5GGnLv3j2MGjUKffr0QceOHaFUKtGjRw/RsYgewzKgJQqFAn5+frC2thYdhXRcy5YtERUVhf/+9784cOAAXFxcsGrVKk4w1HO///47vL29sWHDBqxatQrbt2+Hra2t6FhET8QyoAUqlQqHDh3iEAFVmkQiwdChQ5GVlYU+ffpg9OjR6NSpE9LT00VHo+dUVlaGzz77DAEBAahTpw6Sk5MxcuRI7jVCOo1lQAtSU1ORl5fHyYP03GxtbfHjjz8iOjoaeXl58PLywocffogHDx6IjkaVcOHCBQQGBuKTTz7B9OnTcezYMTg7O4uORfRMLANaEBUVherVq6Ndu3aio5Ce6ty5M1JSUjBr1iwsXLgQcrkcBw8eFB2LKqBWq7Fu3Tp4enriypUrOHLkCD777DOYmZmJjkZUKSwDWqBQKNCxY0dYWFiIjkJ6zMLCAp988glSU1Ph4OCA7t27IyIiAtevXxcdjf7m1q1bGDx4MIYOHYrevXsjOTkZHTp0EB2L6LmwDGhYcXExjh49yiEC0phWrVrh0KFDWLNmDfbt2wcXFxf88MMPnGCoAw4fPgxPT0/s378fmzZtwtq1azlpmPQSy4CGxcXF4f79+5w8SBolkUgwbNgwZGVlITQ0FKNGjULnzp2RkZEhOppRKi4uxgcffIAuXbqgRYsWSE1NxcCBA0XHInphLAMaplAoULduXXh5eYmOQgbI1tYWa9aswaFDh3Dt2jV4eXnho48+4gTDKpSZmYm2bdti8eLFmD9/PhQKBRwcHETHInopLAMa9ujIYu41TtoUFBSE1NRUzJgxAwsWLICHhwcUCoXoWAZNrVZjxYoV8PHxwYMHDxAXF4f3338fJiYmoqMRvTTesTTo7t27iI+P5xABVYlq1aphzpw5SE1Nhb29Pbp164YhQ4bgxo0boqMZnNzcXPTq1Qvjx4/HW2+9hZMnT8LHx0d0LCKNYRnQoCNHjqCsrIyTB6lKubi44PDhw/jxxx+xd+9euLi44Mcff4RarRYdzSDs3r0bcrkcCQkJ2L17N7799ltYWlqKjkWkUSwDGhQVFYUmTZqgefPmoqOQkZFIJBg+fDiysrLw6quvYsSIEejcuTMyMzNFR9Nb9+/fx7hx49CrVy/4+fkhNTUVr776quhYRFrBMqBBj44s5rajJEq9evWwdu1aKBQKXL16FZ6envj444/x8OFD0dH0yqlTp9C6dWusWbMGK1aswK5du1C/fn3RsYi0hmVAQ65du4b09HQOEZBOCA4OhlKpxPTp0zF//nx4eHjg0KFDomPpvLKyMixYsABt27ZF9erVcfLkSYwdO5YFnwwey4CGREVFAQC6dOkiOAnRX6pVq4a5c+ciJSUFDRo0QHBwMIYNG8YJhhXIyclBcHAwZsyYgffeew9xcXFwdXUVHYuoSrAMaIhCoYCHhwfs7OxERyH6B1dXVxw+fBg//PADdu3aBVdXV6xZs4YTDP9m06ZN8PDwwB9//IFDhw5h/vz5MDc3Fx2LqMqwDGiAWq0u31+ASBdJpVKMGDECWVlZCAkJwfDhwxEUFITTp0+LjibUnTt3MGTIEAwePBg9e/ZEamoqOnfuLDoWUZVjGdCAs2fP4tKlS5wvQDrPzs4O69atw8GDB3H58mV4eHhg9uzZKCoqEh2tysXGxsLT0xO//vor1q5di40bN6JOnTqiYxEJwTKgAVFRUTA1NUWnTp1ERyGqlK5du0KpVGLatGmYN28ePDw8cPjwYdGxqkRJSQlmzZqFwMBANG7cGCkpKRgyZAgnCZJRYxnQAIVCgbZt26JGjRqioxBVWvXq1fHZZ58hKSkJ9erVQ1BQEIYPH468vDzR0bTm7NmzaN++PebPn485c+bg8OHDaNasmehYRMKxDLyksrIyHDp0iEMEpLfc3d1x9OhRrFq1Cr/88gtcXFzw3//+16AmGKrVaqxatQpeXl64desWjh8/jlmzZsHU1FR0NCKdwDLwkpKSknD79m1OHiS9JpVKMXLkSGRlZaFHjx5488030aVLF4OYYJiXl4e+ffti9OjRCAsLQ1JSEvz8/ETHItIpLAMvSaFQoEaNGvD39xcdheil1a9fH5GRkdi/fz9ycnLg4eGBuXPn6u0Ew/3790MulyMmJgbbt2/HqlWrOJxH9AQsAy8pKioKgYGBMDMzEx2FSGO6d++OtLQ0TJkyBZ9++ik8PT1x5MgR0bEq7eHDh3jnnXfQs2dPeHh4IDU1FX369BEdi0hnsQy8hAcPHiAmJoZDBGSQqlevjnnz5iEpKQk2Njbo3Lkz3nrrLeTn54uO9lSpqanw9fXFypUrsWTJEvz222+wt7cXHYtIp7EMvITjx4+jqKiIkwfJoMlkMsTExGDlypXYvn07XFxcsG7dOp2bYKhSqbBo0SL4+vpCIpEgISEB77zzDqRS/jFH9Cz8XfISoqKiYGdnB5lMJjoKkVZJpVKMHj0aWVlZ6NatG4YOHYquXbvi7NmzL33twqJSpF+9g6ScW0i/egeFRaXPfY0rV66gR48emDJlCsaPH4/ff/8dcrn8pbMRGQuuq3kJCoUCwcHB3KyEjEaDBg2wYcMGDBs2DGPHjoVcLseHH36I999/HxYWFpW+ztncAkTG5yD69HXk3LyPvz9jkABwrGuJoFZ2CPd3hHP9mk+91rZt2zB69GhUq1YNBw8e5JM6ohfAJwMv6NatWzh58iT/4CGj1KNHD6SlpWHy5MmYO3cuvL29ERMT88xfd+nmfQxZHY9uS45iXXw2sv9VBABADSD75n2si89GtyVHMWR1PC7dvP/YtQoKCvDWW2+hX79+6Ny5M1JTU/n7kegFsQy8oMOHD0OlUnHyIBktS0tLfPHFFzh16hRq166NTp06YeTIkbh58+YTX78pIQddFx/B8fN/TUAsUz19zsGjnx8/n4+ui49gU0JO+c/i4uLg7e2NLVu2YPXq1di6dStsbGw09MmIjA/LwAtSKBRwcnJCkyZNREchEkoulyM2Nhbfffcdtm7dChcXF0RGRv5jguHy6LOYvl2JolLVM0vAv5Wp1CgqVWH6diW+UZzGnDlz0KFDB9ja2iI5ORlvvfUWh+qIXhLLwAvikcVE/08qlWLMmDHIzMxEly5dEBERge7du+PcuXPYlJCDhQfOaOR9FkWdw9c7TuDDDz9ETEwMnJycNHJdImPHMvACLl++jNOnT3N8kuhfGjZsiE2bNmHv3r04d+4cPAO64MPtKZp7A7Ua9V+diJHvfMCNvog0iGXgBURFRUEikSAoKEh0FCKdFBISgvT0dMje+hylKpXmLiyRQAUJZu5Qau6aRMQy8CIUCgW8vb05YYnoKa4UlCFXUhcSqWZXMJep1Ig5l4dz1ws0el0iY8Yy8JzUajUUCgWHCIieITI+ByZS7UzsM5FKsD4u59kvJKJKYRl4TpmZmbh27RonDxI9Q/Tp68+9cqCyylRqRJ+5rpVrExkjloHnpFAoYG5ujg4dOoiOQqSz7hWVIucJGwVpUk7+/RfaupiIHscy8JyioqLQvn17WFpaio5CpLOy8wsf21lQ09QALuYXavldiIwDy8BzKC0tRXR0NIcIiJ6huFSDKwh04H2IDB0PKnoOCQkJKCgo4ORBoie4f/8+MjIyoFQqEZt+ETDz0/p7mpvy+wyRJrAMPIeoqChYW1ujdevWoqMQCVNSUoKzZ88iLS0NSqWy/K/nz5+HWq2GRCJBM2cXoI8voMVtgiUAmtpYae36RMaEZeA5KBQKdO7cGaam/MdGhk+tViMnJ+cfN/y0tDRkZWWhuLgYwF87DspkMrz++uuQyWSQy+VwdXWFlZUVAr+KRrYWJxE62ljCyoK/F4k0gb+TKqmwsBAnTpzA119/LToKkcbduHHjsW/66enpKCj4a2OfWrVqQS6Xo127dhg9ejRkMhlkMtlTN94KamWHdfHZWlleaCKVIKilncavS2SsWAYqKTY2FsXFxZw8SHrt3r17SE9P/8eNPy0tDbm5uQAACwsLuLq6QiaToU+fPuXf9hs3bvzcJwOG+ztizYmLWvgUf+0zENHWUSvXJjJGLAOVpFAoYG9vDxcXF9FRiJ6puLgYZ86c+ccNX6lU4sKFCwAAiUQCJycnyOVyvP3225DL5ZDJZHByctLYMJhz/Zro6GSL4+fzNfp0wEQqQUBzGzjZ1dTYNYmMHctAJUVFRaFr1648N510ikqlwsWLF/9xw09LS8Pp06dRUlICAGjUqBFkMhneeOONf4zrV69eXev55vWRo+viIxotA6ZSCeb1kWvsekTEMlApeXl5SEpKwrvvvis6Chmx3NzcJ47rFxb+tfFO7dq1IZfL0bFjR4wbNw4ymQzu7u6oW7eusMwOdS0xJ9Qd07dr7pTBuaHucKjLTb+INIlloBKio6MBgPMFqErcvXv3ieP6N27cAABUq1YNbm5ukMvl6N+/f/m3fXt7e518cjXI1xF594qw8MCZl77WtO6tMNCXcwWINI1loBIUCgVcXFzQqFEj0VHIgBQVFeH06dOPLd3Lzs4GAEilUrRs2RIymQzjx48vH9dv0aIFTExMBKd/PhOCnGFbwwKf7ExHqUr9XMMGJlIJTKUSzA11ZxEg0hKWgUpQKBR45ZVXRMcgPaVSqXD+/PnHvumfPn0aZWVlAAAHBwfIZDIMHDiw/Ju+i4sLqlWrJji95gzydUT7FraYuUOJmHN5MJFKnloKHv08oLkN5vWRc2iASItYBp7hwoULOH/+PLcgpmdSq9W4du3aYzP4MzIycP/+X5vv1K1bF3K5HEFBQZg0aVL5en1ra2vB6auGQ11LrBvhj7O5BYiMz0H0mevIyb//j0ONJPhrQ6GglnaIaOvIVQNEVYBl4BmioqIglUoRGBgoOgrpkNu3bz82rq9UKnHz5k0AQPXq1eHu7g65XI7BgweXP+Jv0KCBTo7rVzXn+jUxO9Qds+GOwqJSXMwvRHGpCuamUjS1seLOgkRVjL/jniEqKgq+vr6oXbu26CgkwMOHD5GVlfXYuP6lS5cAACYmJmjVqhVkMhmCg4PLb/rNmjXTu3F9UawsTOFubxxPRoh0FcvAU6hUKkRFRWHUqFGio5CWlZWV4Y8//njsm/7Zs2ehUv11TG6TJk0gk8kQHh5ePq7fqlUrWFhYCE5PRPRyWAaeQqlU4saNG5wvYEDUajWuXr362Df9jIwMPHz4EABga2sLuVyO7t27Y8qUKeXr9WvVqiU4PRGRdrAMPEVUVBSqV6+Odu3aiY5CL+DWrVuPfdNPS0vD7du3AQBWVlZwd3eHt7c3hgwZUv6I387OjuP6RGRUWAaeQqFQoEOHDga1vMsQPXjwABkZGY8t3bty5QoAwNTUFC4uLpDJZAgJCSmfwd+0aVNIpVLB6YmIxGMZqEBxcTGOHj2Kjz/+WHQU+p/S0lKcO3fusaV7f/zxR/m4frNmzSCXyzFs2LDycf2WLVvC3NxccHoiIt3FMlCB+Ph4FBYWcgtiAdRqNS5duvTY4TuZmZkoKioCANSvXx8ymQyvvPJK+eN9d3d31KhRQ3B6IiL9wzJQAYVCgbp168LLy0t0FIOWn5//xHH9u3fvAgBq1KgBmUwGX19fDB8+vPzGX69ePcHJiYgMB8tABaKiotClSxeuFdeQwsLCx8b1lUolrl27BgAwMzODq6srZDIZevXqVf6I39HRkZP5iIi0jGXgCQoKChAfH49ly5aJjqJ3SkpKcPbs2ce+6Z8/fx5qtRoSiQTNmzeHXC7HyJEjy7/pOzs7w8zMTHR8IiKjxDLwBEePHkVpaSn3F3gKtVqN7Ozsx77pZ2VloaSkBADQsGFDyGQyhIaGlt/03dzcYGVlJTg9ERH9HcvAEygUCjg6OqJFixaio+iEGzduPDaDPz09HQUFBQCAWrVqQSaTISAgAKNHj4ZcLoe7uztsbW0FJyciospgGQAeOyjlYPRRdO3a1ejGqu/du/fEw3euX78OALCwsCgf1+/Tp0/5uH7jxo2N7p8VEZEhkajV6ooPFP+fu3fvwtraGnfu3DGYLVnLj1A9fR05N/95hKparYaNhQqhrVsg3N8RzvUN6wjV4uJinD59+rGlexcuXAAASCQSODk5lT/af/RXJycnmJqyPxIR6YvK3r+NrgxcunkfM3coEXMuDyZSCcpUFX/8Rz/v6GSLeX3kcKhrWYVJX55KpcLFixcf+6Z/+vRplJaWAgAaNWr0jxu+TCaDq6srLC3167MSEdHjWAaeYFNCDj7ZmY5SlfqpJeDfTKQSmEolmBPqjkG+jlpM+GLUajWuX7/+2Az+9PR0FBYWAgBq1679jxv+o3H9unXrCk5PRETaUtn7t9E8810efRYLD5x5oV9b9r/yMH27Enn3ijAhyFnD6Srv7t27SE9Pf+zGn5eXBwCoVq0a3NzcIJPJ0L9///Ibv729Pcf1iYjoiYyiDGxKyHnhIvBvCw+cQb0aFhio5ScERUVFyMrKeuzwnezsbACAVCqFs7Mz5HI5JkyYUP6tv0WLFtwoiYiInovBl4FLN+/jk53pGr3mxzvTEdDCViNzCMrKynDhwoXHlu6dOXMGZWVlAAAHBwfIZDIMHDiw/Ju+i4sLT1MkIiKNMPgyMHOHEqXPMT+gMkpVaszcocS6Ef6V/jVqtRp//vnnYzP409PT8eDBAwBA3bp1IZfL0aVLF0yaNKl8XL927doazU9ERPR3Bl0GzuYWIOZcnsavW6ZSI+ZcHs5dL4CT3ePLDm/fvl1+0//7jf/mzZsAgOrVq8Pd3R0ymQyDBw8u/7bfoEEDjusTEVGVM+gyEBmf88zlgy/KRCrBmmPn8UZT1WNL9y5fvvzXa0xM0LJlS8jlcnTt2rV8XL9Zs2Yc1yciIp1h0GUg+vR1rRQB4K+nAz/t+x2f/2c0AKBJkyaQyWSIiIgo/6bfqlUrWFhYaOX9iYiINMVgy8C9olLk3Lyv1fcwq9sQh44eQ2tPmV7vv0BERMZNKjqAtmTnF0I7zwT+TgK7Fu4sAkREpNcMtgwUl6oM6n2IiIi0xWDLgLlp1Xy0qnofIiIibTHYO1lTGytoe5Ge5H/vQ0REpM8MtgxYWZjCUcunDDraWMLKwmDnYBIRkZEw2DIAAEGt7GAi1c7zAROpBEEt7bRybSIioqpk0GUg3N9Rq/sMRLTVveOMiYiInpdBlwHn+jXR0clW408HTKQSdHSyfeJWxERERPrGoMsAAMzrI4ephsuAqVSCeX3kGr0mERGRKAZfBhzqWmJOqLtGrzk31F0jxxcTERHpAoMvAwAwyNcRU7u31Mi1pnVvhYG+nCtARESGw2jWxU0IcoZtDQt8sjMdpSr1c00sNJFKYCqVYG6oO4sAEREZHKN4MvDIIF9HKCYHIqC5DQA8c2Lho58HNLeBYnIgiwARERkko3ky8IhDXUusG+GPs7kFiIzPQfSZ68jJv/+PQ40k+GtDoaCWdoho68hVA0REZNAkarX6mc/L7969C2tra9y5c8cgT+grLCrFxfxCFJeqYG4qRVMbK+4sSEREeq+y92/e8fDX1sXu9taiYxAREQlhVHMGiIiI6HEsA0REREaOZYCIiMjIsQwQEREZOZYBIiIiI8cyQEREZORYBoiIiIwcywAREZGRYxkgIiIyciwDRERERo5lgIiIyMixDBARERk5lgEiIiIjxzJARERk5FgGiIiIjBzLABERkZEzrcyL1Go1AODu3btaDUNERESa8+i+/eg+XpFKlYGCggIAgIODw0vGIiIioqpWUFAAa2vrCn8uUT+rLgBQqVS4evUqatasCYlEotGAREREpB1qtRoFBQWwt7eHVFrxzIBKlQEiIiIyXJxASEREZORYBoiIiIwcywAREZGRYxkgIiIyciwDRERERo5lgIiIyMixDBARERm5/wPoiemSbdzqNgAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], + "outputs": [], "source": [ " graph = nx.Graph()\n", "\n", @@ -104,78 +93,15 @@ "id": "302cdc6c-5a60-4002-bfd0-8e8f0fe758cc", "metadata": {}, "source": [ - "We are going to see how solve this problem with a non-noisy quantum simulator or a real quantum device. We develop a tools that just need a MisProblem object and a qiskit backend object and who can solve the mis Problem using the QAOA algorithm. Quantum approximate optimization algorithm is a well-known hybrid algorithm that use the Ising representation of a QUBO problem. LIEN vers article QAOA + ising formulation Article présentant l'algorithm QAOA : https://arxiv.org/abs/1411.4028 " + "We are going to see how solve this problem with a non-noisy quantum simulator or a real quantum device. We develop a tools that just need a MisProblem object and a qiskit backend object and who can solve the mis Problem using the QAOA algorithm. Quantum approximate optimization algorithm is a well-known hybrid algorithm that use the Ising representation of a QUBO problem. Article présentant l'algorithm QAOA : https://arxiv.org/abs/1411.4028 " ] }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "f801bf84-1dae-4834-8fca-4c91ca68a204", "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\AGAUVZFV\\AppData\\Local\\miniconda3\\envs\\d-opti\\Lib\\site-packages\\qiskit_ibm_runtime\\fake_provider\\local_service.py:243: UserWarning: Options {'default_shots': 10000} have no effect in local testing mode.\n", - " warnings.warn(f\"Options {options_copy} have no effect in local testing mode.\")\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Iters. done: 1 [Current cost: -0.583984375]\n", - "Iters. done: 2 [Current cost: 0.0556640625]\n", - "Iters. done: 3 [Current cost: -0.207763671875]\n", - "Iters. done: 4 [Current cost: -0.89794921875]\n", - "Iters. done: 5 [Current cost: -0.594482421875]\n", - "Iters. done: 6 [Current cost: 1.247802734375]\n", - "Iters. done: 7 [Current cost: -0.0087890625]\n", - "Iters. done: 8 [Current cost: -0.426513671875]\n", - "Iters. done: 9 [Current cost: -0.564208984375]\n", - "Iters. done: 10 [Current cost: -0.8447265625]\n", - "Iters. done: 11 [Current cost: -0.4990234375]\n", - "Iters. done: 12 [Current cost: -0.824462890625]\n", - "Iters. done: 13 [Current cost: -0.923828125]\n", - "Iters. done: 14 [Current cost: -0.868896484375]\n", - "Iters. done: 15 [Current cost: -0.927978515625]\n", - "Iters. done: 16 [Current cost: -0.944580078125]\n", - "Iters. done: 17 [Current cost: -0.950439453125]\n", - "Iters. done: 18 [Current cost: -0.951171875]\n", - "Iters. done: 19 [Current cost: -0.910400390625]\n", - "Iters. done: 20 [Current cost: -0.959228515625]\n", - "Iters. done: 21 [Current cost: -0.942626953125]\n", - "Iters. done: 22 [Current cost: -0.955322265625]\n", - "Iters. done: 23 [Current cost: -0.931640625]\n", - "Iters. done: 24 [Current cost: -0.937255859375]\n", - "Iters. done: 25 [Current cost: -0.93310546875]\n", - "Iters. done: 26 [Current cost: -0.936767578125]\n", - "Iters. done: 27 [Current cost: -0.933837890625]\n", - "Iters. done: 28 [Current cost: -0.9453125]\n", - "Iters. done: 29 [Current cost: -0.945068359375]\n", - "Iters. done: 30 [Current cost: -0.929931640625]\n", - "Iters. done: 31 [Current cost: -0.93115234375]\n", - "Iters. done: 32 [Current cost: -0.95751953125]\n", - "Iters. done: 33 [Current cost: -0.931640625]\n", - "Iters. done: 34 [Current cost: -0.93798828125]\n", - "Iters. done: 35 [Current cost: -0.91943359375]\n", - "Iters. done: 36 [Current cost: -0.945068359375]\n", - "Iters. done: 37 [Current cost: -0.955810546875]\n", - "Iters. done: 38 [Current cost: -0.944091796875]\n", - "Iters. done: 39 [Current cost: -0.95263671875]\n", - "Iters. done: 40 [Current cost: -0.95068359375]\n", - "Iters. done: 41 [Current cost: -0.95703125]\n", - "Iters. done: 42 [Current cost: -0.93994140625]\n", - "Iters. done: 43 [Current cost: -0.939697265625]\n", - "Iters. done: 44 [Current cost: -0.9482421875]\n", - "Iters. done: 45 [Current cost: -0.9404296875]\n", - "Iters. done: 46 [Current cost: -0.9296875]\n", - "nb_node in mis = 3.0\n", - "nodes in mis =[1, 6, 5]\n" - ] - } - ], + "outputs": [], "source": [ "# we declare the MisProblem\n", "misProblem = MisProblem(graph)\n", @@ -196,7 +122,8 @@ "res = misSolver.solve(backend=backend)\n", "\n", "sol, _ = res.get_best_solution_fit()\n", - "print(sol)" + "print(sol)\n", + "plot_mis_solution(sol)" ] }, { @@ -209,7 +136,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "c97813de-a503-48fd-be7e-c8114ce07e25", "metadata": {}, "outputs": [], @@ -243,8 +170,8 @@ "by default we use the \"COBYLA\" method, you can give value for three hyperparameters in the kwargs :\n", "\n", "- maxiter : the maximum number of iteration of the optimizer\n", - "- rhobeg : ???\n", - "- tol : ???\n", + "- rhobeg : reasonable initial changes to the variables\n", + "- tol : tolerence to consider the convergence of the minimization\n", "\n", "for all other scipy optimizer https://docs.scipy.org/doc/scipy-1.13.1/reference/generated/scipy.optimize.minimize.html (or it's also possible for cobyla), if you want to define some parameters you can do it with a kwargs parameter \"options\", who is a dictionnary of possible options for the optimizer.\n", "\n" @@ -272,7 +199,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "ce79fe2a-ecf2-4d78-934e-83e212252667", "metadata": {}, "outputs": [], From 169424a8aa676dfcf094caec86ac708e2ec1397c Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Thu, 13 Jun 2024 15:10:23 +0200 Subject: [PATCH 06/10] always initialized bounds of hyperparameters of the quantum circuit in QAOASolver --- .../generic_tools/qiskit_tools.py | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index 7a81a0911..eee88fd7d 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -138,8 +138,8 @@ def execute_ansatz_with_Hamiltonian( pm = generate_preset_pass_manager( target=target, optimization_level=optimization_level ) - ansatz = pm.run(ansatz) - hamiltonian = hamiltonian.apply_layout(ansatz.layout) + new_ansatz = pm.run(ansatz) + hamiltonian = hamiltonian.apply_layout(new_ansatz.layout) # open a session if desired if use_session: @@ -164,7 +164,7 @@ def execute_ansatz_with_Hamiltonian( if kwargs.get("optimizer"): def fun(x): - pub = (ansatz, [hamiltonian], [x]) + pub = (new_ansatz, [hamiltonian], [x]) result = estimator.run(pubs=[pub]).result() cost = result[0].data.evs[0] callback_dict["iters"] += 1 @@ -175,7 +175,7 @@ def fun(x): optimizer = kwargs["optimizer"] res = optimizer.minimize( - fun, validate_initial_point(point=None, circuit=ansatz) + fun, validate_initial_point(point=None, circuit=ansatz), bounds=validate_bounds(ansatz) ) else: @@ -196,14 +196,14 @@ def fun(x): res = minimize( cost_func, validate_initial_point(point=None, circuit=ansatz), - args=(ansatz, hamiltonian, estimator, callback_dict), + args=(new_ansatz, hamiltonian, estimator, callback_dict), method=method, bounds=validate_bounds(ansatz), options=options, ) # Assign solution parameters to our ansatz - qc = ansatz.assign_parameters(res.x) + qc = new_ansatz.assign_parameters(res.x) # Add measurements to our circuit qc.measure_all() # transpile our circuit @@ -212,7 +212,6 @@ def fun(x): results = sampler.run([qc]).result() # extract a dictionnary of results, key is binary values of variable and value is number of time of these values has been found best_result = get_result_from_dict_result(results[0].data.meas.get_counts()) - print(best_result) # Close the session since we are now done with it if use_session: # with_session: @@ -264,7 +263,6 @@ def solve( ) -> ResultStorage: kwargs = self.complete_with_default_hyperparameters(kwargs) - print(kwargs) reps = kwargs["reps"] @@ -282,6 +280,16 @@ def solve( qubo = conv.convert(self.quadratic_programm) hamiltonian, offset = qubo.to_ising() ansatz = QAOAAnsatz(hamiltonian, reps=reps) + """ + by default only hyperparameters of the mixer operator are initialized + but for some optimizer we need to initialize also hyperparameters of the cost operator + """ + bounds = [] + for hp in ansatz.parameter_bounds: + if hp == (None, None): + hp = (0, np.pi) + bounds.append(hp) + ansatz.parameter_bounds = bounds result = execute_ansatz_with_Hamiltonian( self.backend, ansatz, hamiltonian, use_session, **kwargs From 999f31ac6fdc236a6a802580582745dc237dc125 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Fri, 14 Jun 2024 09:25:33 +0200 Subject: [PATCH 07/10] modif notebook tuto qiskit --- notebooks/z_Advanced/tuto_qiskit.ipynb | 257 ++++++++++++++++++++++++- 1 file changed, 250 insertions(+), 7 deletions(-) diff --git a/notebooks/z_Advanced/tuto_qiskit.ipynb b/notebooks/z_Advanced/tuto_qiskit.ipynb index 8b3077693..318675cc0 100644 --- a/notebooks/z_Advanced/tuto_qiskit.ipynb +++ b/notebooks/z_Advanced/tuto_qiskit.ipynb @@ -10,7 +10,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "f3b177ce-a4b8-4f43-ae92-15c5bfcaabb5", "metadata": {}, "outputs": [], @@ -31,7 +31,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "26efd9d5-90c9-4fb5-ae46-f85b935ff094", "metadata": {}, "outputs": [], @@ -40,6 +40,7 @@ "from discrete_optimization.maximum_independent_set.solvers.mis_gurobi import MisMilpSolver\n", "from discrete_optimization.maximum_independent_set.mis_plot import plot_mis_solution, plot_mis_graph\n", "from discrete_optimization.maximum_independent_set.solvers.mis_quantum import QAOAMisSolver\n", + "from qiskit_algorithms.optimizers import SPSA, BOBYQA\n", "from qiskit_aer import AerSimulator\n", "import networkx as nx" ] @@ -68,10 +69,21 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "4c36eeca-5a59-4b76-88ee-ef31192b3af9", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ " graph = nx.Graph()\n", "\n", @@ -98,10 +110,237 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "f801bf84-1dae-4834-8fca-4c91ca68a204", "metadata": {}, - "outputs": [], + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "C:\\Users\\AGAUVZFV\\AppData\\Local\\miniconda3\\envs\\d-opti\\Lib\\site-packages\\qiskit_ibm_runtime\\fake_provider\\local_service.py:243: UserWarning: Options {'default_shots': 10000} have no effect in local testing mode.\n", + " warnings.warn(f\"Options {options_copy} have no effect in local testing mode.\")\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Iters. done: 1 [Current cost: 0.305419921875]\n", + "Iters. done: 2 [Current cost: -0.683837890625]\n", + "Iters. done: 3 [Current cost: -0.524658203125]\n", + "Iters. done: 4 [Current cost: 0.359619140625]\n", + "Iters. done: 5 [Current cost: 0.450927734375]\n", + "Iters. done: 6 [Current cost: 0.23486328125]\n", + "Iters. done: 7 [Current cost: 0.1533203125]\n", + "Iters. done: 8 [Current cost: 0.139404296875]\n", + "Iters. done: 9 [Current cost: 0.225341796875]\n", + "Iters. done: 10 [Current cost: -0.04248046875]\n", + "Iters. done: 11 [Current cost: -0.396240234375]\n", + "Iters. done: 12 [Current cost: 0.06689453125]\n", + "Iters. done: 13 [Current cost: -0.73876953125]\n", + "Iters. done: 14 [Current cost: -0.451171875]\n", + "Iters. done: 15 [Current cost: -0.735107421875]\n", + "Iters. done: 16 [Current cost: -0.785400390625]\n", + "Iters. done: 17 [Current cost: 0.263671875]\n", + "Iters. done: 18 [Current cost: -0.704345703125]\n", + "Iters. done: 19 [Current cost: 0.03271484375]\n", + "Iters. done: 20 [Current cost: -0.285400390625]\n", + "Iters. done: 21 [Current cost: -0.2578125]\n", + "Iters. done: 22 [Current cost: -0.3720703125]\n", + "Iters. done: 23 [Current cost: -0.17626953125]\n", + "Iters. done: 24 [Current cost: -0.305419921875]\n", + "Iters. done: 25 [Current cost: -0.227294921875]\n", + "Iters. done: 26 [Current cost: -0.735107421875]\n", + "Iters. done: 27 [Current cost: -0.746337890625]\n", + "Iters. done: 28 [Current cost: -0.803466796875]\n", + "Iters. done: 29 [Current cost: -0.7734375]\n", + "Iters. done: 30 [Current cost: -0.75]\n", + "Iters. done: 31 [Current cost: -0.772705078125]\n", + "Iters. done: 32 [Current cost: -0.705810546875]\n", + "Iters. done: 33 [Current cost: -0.6806640625]\n", + "Iters. done: 34 [Current cost: -0.791015625]\n", + "Iters. done: 35 [Current cost: -0.77197265625]\n", + "Iters. done: 36 [Current cost: -0.800048828125]\n", + "Iters. done: 37 [Current cost: -0.767333984375]\n", + "Iters. done: 38 [Current cost: -0.744384765625]\n", + "Iters. done: 39 [Current cost: -0.7939453125]\n", + "Iters. done: 40 [Current cost: -0.78271484375]\n", + "Iters. done: 41 [Current cost: -0.82373046875]\n", + "Iters. done: 42 [Current cost: -0.791015625]\n", + "Iters. done: 43 [Current cost: -0.77880859375]\n", + "Iters. done: 44 [Current cost: -0.75048828125]\n", + "Iters. done: 45 [Current cost: -0.787353515625]\n", + "Iters. done: 46 [Current cost: -0.7607421875]\n", + "Iters. done: 47 [Current cost: -0.758544921875]\n", + "Iters. done: 48 [Current cost: -0.804931640625]\n", + "Iters. done: 49 [Current cost: -0.7861328125]\n", + "Iters. done: 50 [Current cost: -0.790283203125]\n", + "Iters. done: 51 [Current cost: -0.796142578125]\n", + "Iters. done: 52 [Current cost: -0.77880859375]\n", + "Iters. done: 53 [Current cost: -0.795166015625]\n", + "Iters. done: 54 [Current cost: -0.79052734375]\n", + "Iters. done: 55 [Current cost: -0.79638671875]\n", + "Iters. done: 56 [Current cost: -0.780029296875]\n", + "Iters. done: 57 [Current cost: -0.78369140625]\n", + "Iters. done: 58 [Current cost: -0.791748046875]\n", + "Iters. done: 59 [Current cost: -0.7861328125]\n", + "Iters. done: 60 [Current cost: -0.784912109375]\n", + "Iters. done: 61 [Current cost: -0.797119140625]\n", + "Iters. done: 62 [Current cost: -0.816650390625]\n", + "Iters. done: 63 [Current cost: -0.7978515625]\n", + "Iters. done: 64 [Current cost: -0.79833984375]\n", + "Iters. done: 65 [Current cost: -0.7880859375]\n", + "Iters. done: 66 [Current cost: -0.80078125]\n", + "Iters. done: 67 [Current cost: -0.793701171875]\n", + "Iters. done: 68 [Current cost: -0.777099609375]\n", + "Iters. done: 69 [Current cost: -0.805908203125]\n", + "Iters. done: 70 [Current cost: -0.81103515625]\n", + "Iters. done: 71 [Current cost: -0.78125]\n", + "Iters. done: 72 [Current cost: -0.8193359375]\n", + "Iters. done: 73 [Current cost: -0.794921875]\n", + "Iters. done: 74 [Current cost: -0.788818359375]\n", + "Iters. done: 75 [Current cost: -0.81494140625]\n", + "Iters. done: 76 [Current cost: -0.800048828125]\n", + "Iters. done: 77 [Current cost: -0.794189453125]\n", + "Iters. done: 78 [Current cost: -0.799072265625]\n", + "Iters. done: 79 [Current cost: -0.79150390625]\n", + "Iters. done: 80 [Current cost: -0.783203125]\n", + "Iters. done: 81 [Current cost: -0.814697265625]\n", + "Iters. done: 82 [Current cost: -0.799072265625]\n", + "Iters. done: 83 [Current cost: -0.79541015625]\n", + "Iters. done: 84 [Current cost: -0.787841796875]\n", + "Iters. done: 85 [Current cost: -0.81005859375]\n", + "Iters. done: 86 [Current cost: -0.23876953125]\n", + "Iters. done: 87 [Current cost: 0.3876953125]\n", + "Iters. done: 88 [Current cost: -0.632080078125]\n", + "Iters. done: 89 [Current cost: -0.290771484375]\n", + "Iters. done: 90 [Current cost: -0.65185546875]\n", + "Iters. done: 91 [Current cost: -0.193115234375]\n", + "Iters. done: 92 [Current cost: -0.474853515625]\n", + "Iters. done: 93 [Current cost: -0.228271484375]\n", + "Iters. done: 94 [Current cost: 0.16748046875]\n", + "Iters. done: 95 [Current cost: -0.463134765625]\n", + "Iters. done: 96 [Current cost: -0.594482421875]\n", + "Iters. done: 97 [Current cost: -0.549560546875]\n", + "Iters. done: 98 [Current cost: -0.684814453125]\n", + "Iters. done: 99 [Current cost: -0.765625]\n", + "Iters. done: 100 [Current cost: -0.774169921875]\n", + "Iters. done: 101 [Current cost: -0.486083984375]\n", + "Iters. done: 102 [Current cost: -0.655029296875]\n", + "Iters. done: 103 [Current cost: -0.796875]\n", + "Iters. done: 104 [Current cost: -0.78076171875]\n", + "Iters. done: 105 [Current cost: -0.778564453125]\n", + "Iters. done: 106 [Current cost: -0.721923828125]\n", + "Iters. done: 107 [Current cost: -0.738525390625]\n", + "Iters. done: 108 [Current cost: -0.650634765625]\n", + "Iters. done: 109 [Current cost: -0.77197265625]\n", + "Iters. done: 110 [Current cost: -0.775634765625]\n", + "Iters. done: 111 [Current cost: -0.750732421875]\n", + "Iters. done: 112 [Current cost: -0.749755859375]\n", + "Iters. done: 113 [Current cost: -0.788330078125]\n", + "Iters. done: 114 [Current cost: -0.75537109375]\n", + "Iters. done: 115 [Current cost: -0.779296875]\n", + "Iters. done: 116 [Current cost: -0.77880859375]\n", + "Iters. done: 117 [Current cost: -0.783447265625]\n", + "Iters. done: 118 [Current cost: -0.75634765625]\n", + "Iters. done: 119 [Current cost: -0.750244140625]\n", + "Iters. done: 120 [Current cost: -0.782958984375]\n", + "Iters. done: 121 [Current cost: -0.764404296875]\n", + "Iters. done: 122 [Current cost: -0.79736328125]\n", + "Iters. done: 123 [Current cost: -0.7939453125]\n", + "Iters. done: 124 [Current cost: -0.79345703125]\n", + "Iters. done: 125 [Current cost: -0.793701171875]\n", + "Iters. done: 126 [Current cost: -0.7802734375]\n", + "Iters. done: 127 [Current cost: -0.757568359375]\n", + "Iters. done: 128 [Current cost: -0.782470703125]\n", + "Iters. done: 129 [Current cost: -0.782470703125]\n", + "Iters. done: 130 [Current cost: -0.8154296875]\n", + "Iters. done: 131 [Current cost: -0.77783203125]\n", + "Iters. done: 132 [Current cost: 0.315673828125]\n", + "Iters. done: 133 [Current cost: 0.15625]\n", + "Iters. done: 134 [Current cost: -0.055419921875]\n", + "Iters. done: 135 [Current cost: -0.1064453125]\n", + "Iters. done: 136 [Current cost: 0.212890625]\n", + "Iters. done: 137 [Current cost: 0.174560546875]\n", + "Iters. done: 138 [Current cost: -0.187744140625]\n", + "Iters. done: 139 [Current cost: 0.041748046875]\n", + "Iters. done: 140 [Current cost: 0.422119140625]\n", + "Iters. done: 141 [Current cost: -0.095947265625]\n", + "Iters. done: 142 [Current cost: 0.508056640625]\n", + "Iters. done: 143 [Current cost: 0.1064453125]\n", + "Iters. done: 144 [Current cost: -0.305419921875]\n", + "Iters. done: 145 [Current cost: 0.32080078125]\n", + "Iters. done: 146 [Current cost: -0.098876953125]\n", + "Iters. done: 147 [Current cost: -0.34033203125]\n", + "Iters. done: 148 [Current cost: -0.35107421875]\n", + "Iters. done: 149 [Current cost: -0.39111328125]\n", + "Iters. done: 150 [Current cost: 0.059814453125]\n", + "Iters. done: 151 [Current cost: -0.380126953125]\n", + "Iters. done: 152 [Current cost: -0.2783203125]\n", + "Iters. done: 153 [Current cost: -0.451904296875]\n", + "Iters. done: 154 [Current cost: -0.13720703125]\n", + "Iters. done: 155 [Current cost: -0.387939453125]\n", + "Iters. done: 156 [Current cost: -0.429931640625]\n", + "Iters. done: 157 [Current cost: -0.41796875]\n", + "Iters. done: 158 [Current cost: -0.460693359375]\n", + "Iters. done: 159 [Current cost: -0.470703125]\n", + "Iters. done: 160 [Current cost: -0.390869140625]\n", + "Iters. done: 161 [Current cost: -0.45458984375]\n", + "Iters. done: 162 [Current cost: -0.299072265625]\n", + "Iters. done: 163 [Current cost: -0.459716796875]\n", + "Iters. done: 164 [Current cost: -0.458251953125]\n", + "Iters. done: 165 [Current cost: -0.452392578125]\n", + "Iters. done: 166 [Current cost: -0.477783203125]\n", + "Iters. done: 167 [Current cost: -0.47119140625]\n", + "Iters. done: 168 [Current cost: -0.488525390625]\n", + "Iters. done: 169 [Current cost: -0.536865234375]\n", + "Iters. done: 170 [Current cost: -0.577392578125]\n", + "Iters. done: 171 [Current cost: -0.666259765625]\n", + "Iters. done: 172 [Current cost: -0.5869140625]\n", + "Iters. done: 173 [Current cost: -0.55224609375]\n", + "Iters. done: 174 [Current cost: -0.680419921875]\n", + "Iters. done: 175 [Current cost: -0.74169921875]\n", + "Iters. done: 176 [Current cost: -0.641357421875]\n", + "Iters. done: 177 [Current cost: -0.750244140625]\n", + "Iters. done: 178 [Current cost: -0.651123046875]\n", + "Iters. done: 179 [Current cost: -0.687255859375]\n", + "Iters. done: 180 [Current cost: -0.34326171875]\n", + "Iters. done: 181 [Current cost: -0.7255859375]\n", + "Iters. done: 182 [Current cost: -0.788330078125]\n", + "Iters. done: 183 [Current cost: -0.74072265625]\n", + "Iters. done: 184 [Current cost: -0.75537109375]\n", + "Iters. done: 185 [Current cost: -0.718017578125]\n", + "Iters. done: 186 [Current cost: -0.78515625]\n", + "Iters. done: 187 [Current cost: -0.763427734375]\n", + "Iters. done: 188 [Current cost: -0.733154296875]\n", + "Iters. done: 189 [Current cost: -0.75341796875]\n", + "Iters. done: 190 [Current cost: -0.756591796875]\n", + "Iters. done: 191 [Current cost: -0.748291015625]\n", + "Iters. done: 192 [Current cost: -0.732421875]\n", + "Iters. done: 193 [Current cost: -0.77880859375]\n", + "Iters. done: 194 [Current cost: -0.783935546875]\n", + "Iters. done: 195 [Current cost: -0.74072265625]\n", + "Iters. done: 196 [Current cost: -0.758544921875]\n", + "Iters. done: 197 [Current cost: -0.780029296875]\n", + "Iters. done: 198 [Current cost: -0.760986328125]\n", + "Iters. done: 199 [Current cost: -0.75634765625]\n", + "Iters. done: 200 [Current cost: -0.76123046875]\n", + "nb_node in mis = 3.0\n", + "nodes in mis =[1, 6, 5]\n" + ] + }, + { + "data": { + "image/png": "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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "# we declare the MisProblem\n", "misProblem = MisProblem(graph)\n", @@ -118,8 +357,11 @@ "# then we need to initialize the solver, in fact we transform the mis problem on his QUBO form\n", "misSolver.init_model()\n", "\n", + "optimizer = BOBYQA(maxiter=200)\n", + "kwargs = {\"optimizer\": optimizer}\n", + "\n", "# after that we can run the solver using the defined backend\n", - "res = misSolver.solve(backend=backend)\n", + "res = misSolver.solve(backend=backend, **kwargs)\n", "\n", "sol, _ = res.get_best_solution_fit()\n", "print(sol)\n", @@ -204,6 +446,7 @@ "metadata": {}, "outputs": [], "source": [ + "# from qiskit_algorithms.optimizers import SPSA\n", "# optimizer = SPSA()\n", "# kwargs = {\"optimizer\": optimizer} or kwargs[\"optimizer\"] = optimizer if kwargs already exist" ] From 5f424760cd5698dee13e94983a681876c0f4c46c Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Mon, 17 Jun 2024 10:55:19 +0200 Subject: [PATCH 08/10] modify coloring_quantum.py to take in consideration that qiskit is an optionnal dependency. --- .../coloring/solvers/coloring_quantum.py | 24 ++++++++++++++++++- .../generic_tools/qiskit_tools.py | 4 +++- examples/qiskit_examples/coloring_example.py | 8 +++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py index fa3fc8b2b..81abfd5b3 100644 --- a/discrete_optimization/coloring/solvers/coloring_quantum.py +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -1,9 +1,13 @@ +# Copyright (c) 2024 AIRBUS and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import logging from typing import Optional, Union import numpy as np from qiskit_optimization import QuadraticProgram from qiskit_optimization.algorithms import OptimizationResult -from qiskit_optimization.applications import OptimizationApplication from discrete_optimization.coloring.coloring_model import ( ColoringProblem, @@ -17,8 +21,26 @@ from discrete_optimization.generic_tools.qiskit_tools import ( QiskitQAOASolver, QiskitVQESolver, + qiskit_available, ) +logger = logging.getLogger(__name__) + +if qiskit_available: + from qiskit_optimization import QuadraticProgram + from qiskit_optimization.algorithms import OptimizationResult + from qiskit_optimization.applications import OptimizationApplication +else: + msg = ( + "ColoringQiskit_MinimizeNbColor, QAOAColoringSolver_MinimizeNbColor, VQEColoringSolver_MinimizeNbColor, " + "ColoringQiskit_FeasibleNbColor, QAOAColoringSolver_FeasibleNbColor and VQEColoringSolver_FeasibleNbColor, " + "need qiskit, qiskit_aer, qiskit_algorithms, qiskit_ibm_runtime, " + "and qiskit_optimization to be installed." + "You can use the command `pip install discrete-optimization[quantum]` to install them." + ) + logger.warning(msg) + OptimizationApplication = object + class ColoringQiskit_MinimizeNbColor(OptimizationApplication): def __init__(self, problem: ColoringProblem, nb_max_color=None) -> None: diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index eee88fd7d..bfd96846f 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -175,7 +175,9 @@ def fun(x): optimizer = kwargs["optimizer"] res = optimizer.minimize( - fun, validate_initial_point(point=None, circuit=ansatz), bounds=validate_bounds(ansatz) + fun, + validate_initial_point(point=None, circuit=ansatz), + bounds=validate_bounds(ansatz), ) else: diff --git a/examples/qiskit_examples/coloring_example.py b/examples/qiskit_examples/coloring_example.py index ac9250c9f..e06c1f96b 100644 --- a/examples/qiskit_examples/coloring_example.py +++ b/examples/qiskit_examples/coloring_example.py @@ -2,7 +2,7 @@ from discrete_optimization.coloring.coloring_model import ColoringProblem from discrete_optimization.coloring.solvers.coloring_quantum import ( - QAOAColoringSolver_MinimizeNbColor, + QAOAColoringSolver_FeasibleNbColor, ) from discrete_optimization.generic_tools.graph_api import Graph @@ -19,13 +19,13 @@ def quantum_coloring(): edges = [(1, 2, {}), (1, 3, {}), (2, 4, {})] # can make the problem unsat + the number of variable depend on this parameter - nb_max_color = 2 + nb_color = 2 # we create an instance of ColoringProblem coloringProblem = ColoringProblem(Graph(nodes=nodes, edges=edges)) # we create an instance of a QAOAMisSolver - coloringSolver = QAOAColoringSolver_MinimizeNbColor( - coloringProblem, nb_max_color=nb_max_color + coloringSolver = QAOAColoringSolver_FeasibleNbColor( + coloringProblem, nb_color=nb_color ) # we initialize the solver, in fact this step transform the problem in a QUBO formulation coloringSolver.init_model() From 946cb802c732f15083fe99006a9cc0936fff66d4 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Mon, 17 Jun 2024 11:00:36 +0200 Subject: [PATCH 09/10] delete temporarily the notebook tuto_qiskit.ipynb --- .../coloring/solvers/coloring_quantum.py | 4 +- .../solvers/mis_quantum.py | 2 + notebooks/z_Advanced/tuto_qiskit.ipynb | 476 ------------------ 3 files changed, 4 insertions(+), 478 deletions(-) delete mode 100644 notebooks/z_Advanced/tuto_qiskit.ipynb diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py index 81abfd5b3..262e7b100 100644 --- a/discrete_optimization/coloring/solvers/coloring_quantum.py +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -6,8 +6,6 @@ from typing import Optional, Union import numpy as np -from qiskit_optimization import QuadraticProgram -from qiskit_optimization.algorithms import OptimizationResult from discrete_optimization.coloring.coloring_model import ( ColoringProblem, @@ -40,6 +38,8 @@ ) logger.warning(msg) OptimizationApplication = object + OptimizationResult = object + QuadraticProgram = object class ColoringQiskit_MinimizeNbColor(OptimizationApplication): diff --git a/discrete_optimization/maximum_independent_set/solvers/mis_quantum.py b/discrete_optimization/maximum_independent_set/solvers/mis_quantum.py index b12316eb7..571479ae9 100644 --- a/discrete_optimization/maximum_independent_set/solvers/mis_quantum.py +++ b/discrete_optimization/maximum_independent_set/solvers/mis_quantum.py @@ -40,6 +40,8 @@ ) logger.warning(msg) OptimizationApplication = object + OptimizationResult = object + QuadraticProgram = object class MisQiskit(OptimizationApplication): diff --git a/notebooks/z_Advanced/tuto_qiskit.ipynb b/notebooks/z_Advanced/tuto_qiskit.ipynb deleted file mode 100644 index 318675cc0..000000000 --- a/notebooks/z_Advanced/tuto_qiskit.ipynb +++ /dev/null @@ -1,476 +0,0 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "220905dc-d6fe-464c-bbda-b661af6543dd", - "metadata": {}, - "source": [ - "Première cell pour éxécuter dans environnement avec discrete_optimization de github mais pas installer en package python" - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "f3b177ce-a4b8-4f43-ae92-15c5bfcaabb5", - "metadata": {}, - "outputs": [], - "source": [ - "import sys \n", - "sys.path.append(\"/Users/AGAUVZFV/Desktop/discrete-optimization\")\n", - "import os\n", - "os.environ[\"DO_SKIP_MZN_CHECK\"] = \"1\" " - ] - }, - { - "cell_type": "markdown", - "id": "4b74b0c6-2aad-43ab-9c2c-a131ffeec0d5", - "metadata": {}, - "source": [ - "other useful import" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "26efd9d5-90c9-4fb5-ae46-f85b935ff094", - "metadata": {}, - "outputs": [], - "source": [ - "from discrete_optimization.maximum_independent_set.mis_model import MisProblem\n", - "from discrete_optimization.maximum_independent_set.solvers.mis_gurobi import MisMilpSolver\n", - "from discrete_optimization.maximum_independent_set.mis_plot import plot_mis_solution, plot_mis_graph\n", - "from discrete_optimization.maximum_independent_set.solvers.mis_quantum import QAOAMisSolver\n", - "from qiskit_algorithms.optimizers import SPSA, BOBYQA\n", - "from qiskit_aer import AerSimulator\n", - "import networkx as nx" - ] - }, - { - "cell_type": "markdown", - "id": "fd7c42c6-1626-45dd-b262-6195039887f3", - "metadata": {}, - "source": [ - "The objectif of this tutorial is to present how we can use discrete_optimization to solve optimization problem using quantum simulator or quantum real device with qiskit. \n", - "\n", - "It's free to use simulator but to use quantum real divice you need an IBM account. You can have one for free BUT you will be limited to ten minutes of use by month and you can't use session (we talk about session a bit later) who is a very pratical way to execute job on real device.\n", - "\n", - "This tutorial has not to purpose to present a method to solve very large problem, nothing of revolutionnary here, just a quantum gadget to see what we can do actually with quantum technologies, the good point as the bad point." - ] - }, - { - "cell_type": "markdown", - "id": "37b171d5-8111-4626-b4fa-78680ed8251b", - "metadata": {}, - "source": [ - "The first step is, of course, to creat the problem to solve, here we are going to solve a maximum_independent_set.\n", - "In a graph we search a subset of nodes who are not connected two by two. We want to maximize the side of this subset.\n", - "An example of a graph with 6 nodes where the maximum_independent_set is the subset of nodes (1,5,6)." - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "4c36eeca-5a59-4b76-88ee-ef31192b3af9", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - " graph = nx.Graph()\n", - "\n", - " graph.add_edge(1, 2)\n", - " graph.add_edge(1, 3)\n", - " graph.add_edge(2, 4)\n", - " graph.add_edge(2, 6)\n", - " graph.add_edge(3, 4)\n", - " graph.add_edge(3, 5)\n", - " graph.add_edge(4, 5)\n", - " graph.add_edge(4, 6)\n", - "\n", - " misProblem = MisProblem(graph)\n", - " plot_mis_graph(misProblem)" - ] - }, - { - "cell_type": "markdown", - "id": "302cdc6c-5a60-4002-bfd0-8e8f0fe758cc", - "metadata": {}, - "source": [ - "We are going to see how solve this problem with a non-noisy quantum simulator or a real quantum device. We develop a tools that just need a MisProblem object and a qiskit backend object and who can solve the mis Problem using the QAOA algorithm. Quantum approximate optimization algorithm is a well-known hybrid algorithm that use the Ising representation of a QUBO problem. Article présentant l'algorithm QAOA : https://arxiv.org/abs/1411.4028 " - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "f801bf84-1dae-4834-8fca-4c91ca68a204", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "C:\\Users\\AGAUVZFV\\AppData\\Local\\miniconda3\\envs\\d-opti\\Lib\\site-packages\\qiskit_ibm_runtime\\fake_provider\\local_service.py:243: UserWarning: Options {'default_shots': 10000} have no effect in local testing mode.\n", - " warnings.warn(f\"Options {options_copy} have no effect in local testing mode.\")\n" - ] - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Iters. done: 1 [Current cost: 0.305419921875]\n", - "Iters. done: 2 [Current cost: -0.683837890625]\n", - "Iters. done: 3 [Current cost: -0.524658203125]\n", - "Iters. done: 4 [Current cost: 0.359619140625]\n", - "Iters. done: 5 [Current cost: 0.450927734375]\n", - "Iters. done: 6 [Current cost: 0.23486328125]\n", - "Iters. done: 7 [Current cost: 0.1533203125]\n", - "Iters. done: 8 [Current cost: 0.139404296875]\n", - "Iters. done: 9 [Current cost: 0.225341796875]\n", - "Iters. done: 10 [Current cost: -0.04248046875]\n", - "Iters. done: 11 [Current cost: -0.396240234375]\n", - "Iters. done: 12 [Current cost: 0.06689453125]\n", - "Iters. done: 13 [Current cost: -0.73876953125]\n", - "Iters. done: 14 [Current cost: -0.451171875]\n", - "Iters. done: 15 [Current cost: -0.735107421875]\n", - "Iters. done: 16 [Current cost: -0.785400390625]\n", - "Iters. done: 17 [Current cost: 0.263671875]\n", - "Iters. done: 18 [Current cost: -0.704345703125]\n", - "Iters. done: 19 [Current cost: 0.03271484375]\n", - "Iters. done: 20 [Current cost: -0.285400390625]\n", - "Iters. done: 21 [Current cost: -0.2578125]\n", - "Iters. done: 22 [Current cost: -0.3720703125]\n", - "Iters. done: 23 [Current cost: -0.17626953125]\n", - "Iters. done: 24 [Current cost: -0.305419921875]\n", - "Iters. done: 25 [Current cost: -0.227294921875]\n", - "Iters. done: 26 [Current cost: -0.735107421875]\n", - "Iters. done: 27 [Current cost: -0.746337890625]\n", - "Iters. done: 28 [Current cost: -0.803466796875]\n", - "Iters. done: 29 [Current cost: -0.7734375]\n", - "Iters. done: 30 [Current cost: -0.75]\n", - "Iters. done: 31 [Current cost: -0.772705078125]\n", - "Iters. done: 32 [Current cost: -0.705810546875]\n", - "Iters. done: 33 [Current cost: -0.6806640625]\n", - "Iters. done: 34 [Current cost: -0.791015625]\n", - "Iters. done: 35 [Current cost: -0.77197265625]\n", - "Iters. done: 36 [Current cost: -0.800048828125]\n", - "Iters. done: 37 [Current cost: -0.767333984375]\n", - "Iters. done: 38 [Current cost: -0.744384765625]\n", - "Iters. done: 39 [Current cost: -0.7939453125]\n", - "Iters. done: 40 [Current cost: -0.78271484375]\n", - "Iters. done: 41 [Current cost: -0.82373046875]\n", - "Iters. done: 42 [Current cost: -0.791015625]\n", - "Iters. done: 43 [Current cost: -0.77880859375]\n", - "Iters. done: 44 [Current cost: -0.75048828125]\n", - "Iters. done: 45 [Current cost: -0.787353515625]\n", - "Iters. done: 46 [Current cost: -0.7607421875]\n", - "Iters. done: 47 [Current cost: -0.758544921875]\n", - "Iters. done: 48 [Current cost: -0.804931640625]\n", - "Iters. done: 49 [Current cost: -0.7861328125]\n", - "Iters. done: 50 [Current cost: -0.790283203125]\n", - "Iters. done: 51 [Current cost: -0.796142578125]\n", - "Iters. done: 52 [Current cost: -0.77880859375]\n", - "Iters. done: 53 [Current cost: -0.795166015625]\n", - "Iters. done: 54 [Current cost: -0.79052734375]\n", - "Iters. done: 55 [Current cost: -0.79638671875]\n", - "Iters. done: 56 [Current cost: -0.780029296875]\n", - "Iters. done: 57 [Current cost: -0.78369140625]\n", - "Iters. done: 58 [Current cost: -0.791748046875]\n", - "Iters. done: 59 [Current cost: -0.7861328125]\n", - "Iters. done: 60 [Current cost: -0.784912109375]\n", - "Iters. done: 61 [Current cost: -0.797119140625]\n", - "Iters. done: 62 [Current cost: -0.816650390625]\n", - "Iters. done: 63 [Current cost: -0.7978515625]\n", - "Iters. done: 64 [Current cost: -0.79833984375]\n", - "Iters. done: 65 [Current cost: -0.7880859375]\n", - "Iters. done: 66 [Current cost: -0.80078125]\n", - "Iters. done: 67 [Current cost: -0.793701171875]\n", - "Iters. done: 68 [Current cost: -0.777099609375]\n", - "Iters. done: 69 [Current cost: -0.805908203125]\n", - "Iters. done: 70 [Current cost: -0.81103515625]\n", - "Iters. done: 71 [Current cost: -0.78125]\n", - "Iters. done: 72 [Current cost: -0.8193359375]\n", - "Iters. done: 73 [Current cost: -0.794921875]\n", - "Iters. done: 74 [Current cost: -0.788818359375]\n", - "Iters. done: 75 [Current cost: -0.81494140625]\n", - "Iters. done: 76 [Current cost: -0.800048828125]\n", - "Iters. done: 77 [Current cost: -0.794189453125]\n", - "Iters. done: 78 [Current cost: -0.799072265625]\n", - "Iters. done: 79 [Current cost: -0.79150390625]\n", - "Iters. done: 80 [Current cost: -0.783203125]\n", - "Iters. done: 81 [Current cost: -0.814697265625]\n", - "Iters. done: 82 [Current cost: -0.799072265625]\n", - "Iters. done: 83 [Current cost: -0.79541015625]\n", - "Iters. done: 84 [Current cost: -0.787841796875]\n", - "Iters. done: 85 [Current cost: -0.81005859375]\n", - "Iters. done: 86 [Current cost: -0.23876953125]\n", - "Iters. done: 87 [Current cost: 0.3876953125]\n", - "Iters. done: 88 [Current cost: -0.632080078125]\n", - "Iters. done: 89 [Current cost: -0.290771484375]\n", - "Iters. done: 90 [Current cost: -0.65185546875]\n", - "Iters. done: 91 [Current cost: -0.193115234375]\n", - "Iters. done: 92 [Current cost: -0.474853515625]\n", - "Iters. done: 93 [Current cost: -0.228271484375]\n", - "Iters. done: 94 [Current cost: 0.16748046875]\n", - "Iters. done: 95 [Current cost: -0.463134765625]\n", - "Iters. done: 96 [Current cost: -0.594482421875]\n", - "Iters. done: 97 [Current cost: -0.549560546875]\n", - "Iters. done: 98 [Current cost: -0.684814453125]\n", - "Iters. done: 99 [Current cost: -0.765625]\n", - "Iters. done: 100 [Current cost: -0.774169921875]\n", - "Iters. done: 101 [Current cost: -0.486083984375]\n", - "Iters. done: 102 [Current cost: -0.655029296875]\n", - "Iters. done: 103 [Current cost: -0.796875]\n", - "Iters. done: 104 [Current cost: -0.78076171875]\n", - "Iters. done: 105 [Current cost: -0.778564453125]\n", - "Iters. done: 106 [Current cost: -0.721923828125]\n", - "Iters. done: 107 [Current cost: -0.738525390625]\n", - "Iters. done: 108 [Current cost: -0.650634765625]\n", - "Iters. done: 109 [Current cost: -0.77197265625]\n", - "Iters. done: 110 [Current cost: -0.775634765625]\n", - "Iters. done: 111 [Current cost: -0.750732421875]\n", - "Iters. done: 112 [Current cost: -0.749755859375]\n", - "Iters. done: 113 [Current cost: -0.788330078125]\n", - "Iters. done: 114 [Current cost: -0.75537109375]\n", - "Iters. done: 115 [Current cost: -0.779296875]\n", - "Iters. done: 116 [Current cost: -0.77880859375]\n", - "Iters. done: 117 [Current cost: -0.783447265625]\n", - "Iters. done: 118 [Current cost: -0.75634765625]\n", - "Iters. done: 119 [Current cost: -0.750244140625]\n", - "Iters. done: 120 [Current cost: -0.782958984375]\n", - "Iters. done: 121 [Current cost: -0.764404296875]\n", - "Iters. done: 122 [Current cost: -0.79736328125]\n", - "Iters. done: 123 [Current cost: -0.7939453125]\n", - "Iters. done: 124 [Current cost: -0.79345703125]\n", - "Iters. done: 125 [Current cost: -0.793701171875]\n", - "Iters. done: 126 [Current cost: -0.7802734375]\n", - "Iters. done: 127 [Current cost: -0.757568359375]\n", - "Iters. done: 128 [Current cost: -0.782470703125]\n", - "Iters. done: 129 [Current cost: -0.782470703125]\n", - "Iters. done: 130 [Current cost: -0.8154296875]\n", - "Iters. done: 131 [Current cost: -0.77783203125]\n", - "Iters. done: 132 [Current cost: 0.315673828125]\n", - "Iters. done: 133 [Current cost: 0.15625]\n", - "Iters. done: 134 [Current cost: -0.055419921875]\n", - "Iters. done: 135 [Current cost: -0.1064453125]\n", - "Iters. done: 136 [Current cost: 0.212890625]\n", - "Iters. done: 137 [Current cost: 0.174560546875]\n", - "Iters. done: 138 [Current cost: -0.187744140625]\n", - "Iters. done: 139 [Current cost: 0.041748046875]\n", - "Iters. done: 140 [Current cost: 0.422119140625]\n", - "Iters. done: 141 [Current cost: -0.095947265625]\n", - "Iters. done: 142 [Current cost: 0.508056640625]\n", - "Iters. done: 143 [Current cost: 0.1064453125]\n", - "Iters. done: 144 [Current cost: -0.305419921875]\n", - "Iters. done: 145 [Current cost: 0.32080078125]\n", - "Iters. done: 146 [Current cost: -0.098876953125]\n", - "Iters. done: 147 [Current cost: -0.34033203125]\n", - "Iters. done: 148 [Current cost: -0.35107421875]\n", - "Iters. done: 149 [Current cost: -0.39111328125]\n", - "Iters. done: 150 [Current cost: 0.059814453125]\n", - "Iters. done: 151 [Current cost: -0.380126953125]\n", - "Iters. done: 152 [Current cost: -0.2783203125]\n", - "Iters. done: 153 [Current cost: -0.451904296875]\n", - "Iters. done: 154 [Current cost: -0.13720703125]\n", - "Iters. done: 155 [Current cost: -0.387939453125]\n", - "Iters. done: 156 [Current cost: -0.429931640625]\n", - "Iters. done: 157 [Current cost: -0.41796875]\n", - "Iters. done: 158 [Current cost: -0.460693359375]\n", - "Iters. done: 159 [Current cost: -0.470703125]\n", - "Iters. done: 160 [Current cost: -0.390869140625]\n", - "Iters. done: 161 [Current cost: -0.45458984375]\n", - "Iters. done: 162 [Current cost: -0.299072265625]\n", - "Iters. done: 163 [Current cost: -0.459716796875]\n", - "Iters. done: 164 [Current cost: -0.458251953125]\n", - "Iters. done: 165 [Current cost: -0.452392578125]\n", - "Iters. done: 166 [Current cost: -0.477783203125]\n", - "Iters. done: 167 [Current cost: -0.47119140625]\n", - "Iters. done: 168 [Current cost: -0.488525390625]\n", - "Iters. done: 169 [Current cost: -0.536865234375]\n", - "Iters. done: 170 [Current cost: -0.577392578125]\n", - "Iters. done: 171 [Current cost: -0.666259765625]\n", - "Iters. done: 172 [Current cost: -0.5869140625]\n", - "Iters. done: 173 [Current cost: -0.55224609375]\n", - "Iters. done: 174 [Current cost: -0.680419921875]\n", - "Iters. done: 175 [Current cost: -0.74169921875]\n", - "Iters. done: 176 [Current cost: -0.641357421875]\n", - "Iters. done: 177 [Current cost: -0.750244140625]\n", - "Iters. done: 178 [Current cost: -0.651123046875]\n", - "Iters. done: 179 [Current cost: -0.687255859375]\n", - "Iters. done: 180 [Current cost: -0.34326171875]\n", - "Iters. done: 181 [Current cost: -0.7255859375]\n", - "Iters. done: 182 [Current cost: -0.788330078125]\n", - "Iters. done: 183 [Current cost: -0.74072265625]\n", - "Iters. done: 184 [Current cost: -0.75537109375]\n", - "Iters. done: 185 [Current cost: -0.718017578125]\n", - "Iters. done: 186 [Current cost: -0.78515625]\n", - "Iters. done: 187 [Current cost: -0.763427734375]\n", - "Iters. done: 188 [Current cost: -0.733154296875]\n", - "Iters. done: 189 [Current cost: -0.75341796875]\n", - "Iters. done: 190 [Current cost: -0.756591796875]\n", - "Iters. done: 191 [Current cost: -0.748291015625]\n", - "Iters. done: 192 [Current cost: -0.732421875]\n", - "Iters. done: 193 [Current cost: -0.77880859375]\n", - "Iters. done: 194 [Current cost: -0.783935546875]\n", - "Iters. done: 195 [Current cost: -0.74072265625]\n", - "Iters. done: 196 [Current cost: -0.758544921875]\n", - "Iters. done: 197 [Current cost: -0.780029296875]\n", - "Iters. done: 198 [Current cost: -0.760986328125]\n", - "Iters. done: 199 [Current cost: -0.75634765625]\n", - "Iters. done: 200 [Current cost: -0.76123046875]\n", - "nb_node in mis = 3.0\n", - "nodes in mis =[1, 6, 5]\n" - ] - }, - { - "data": { - "image/png": "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", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "# we declare the MisProblem\n", - "misProblem = MisProblem(graph)\n", - "\n", - "# we declare the QAOA solver we are going to use\n", - "misSolver = QAOAMisSolver(misProblem)\n", - "\n", - "# then we need a backend, a backend is an object wo define how and where the quantum part of the algorithm is run\n", - "# it can be a simulator or a real quantum device\n", - "\n", - "# here we declare a simulator\n", - "backend = AerSimulator()\n", - "\n", - "# then we need to initialize the solver, in fact we transform the mis problem on his QUBO form\n", - "misSolver.init_model()\n", - "\n", - "optimizer = BOBYQA(maxiter=200)\n", - "kwargs = {\"optimizer\": optimizer}\n", - "\n", - "# after that we can run the solver using the defined backend\n", - "res = misSolver.solve(backend=backend, **kwargs)\n", - "\n", - "sol, _ = res.get_best_solution_fit()\n", - "print(sol)\n", - "plot_mis_solution(sol)" - ] - }, - { - "cell_type": "markdown", - "id": "946aaf2a-ea8d-481a-8df2-d9cf5e213065", - "metadata": {}, - "source": [ - "If you have an IBM account you can declare the backend as follow, where the \"token\" parameter is your personnal IBM token to use a real quantum device. If you have a premium account please set the parameter \"use-session\" of the solver to True. A session give a guaranted acces to the device, if you don't use session, you can have a lot of waiting time beetween two different job (because other jobs can be insert in the queue). In QAOA we create a new job at each iteration of the optimizer and one final job after the minimization step is over." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "c97813de-a503-48fd-be7e-c8114ce07e25", - "metadata": {}, - "outputs": [], - "source": [ - "# from qiskit_ibm_runtime import QiskitRuntimeService\n", - "\n", - "# service = QiskitRuntimeService(channel=\"ibm_quantum\", token=token)\n", - "# backend = service.least_busy(operational=True, simulator=False)\n", - "# misSolver.init_model()\n", - "# misSolver.solve(backend=backend, use_session=True)\n", - "# sol, _ = res.get_best_solution_fit()\n", - "# print(sol)" - ] - }, - { - "cell_type": "markdown", - "id": "a6fbf5d7-f7ba-43c8-a224-a5326bffd6a2", - "metadata": {}, - "source": [ - "Here we have not define any parameters except the backend, in fact there is a lot of parameters we can chose.\n", - "\n", - "They can be defined in a kwargs parameter\n", - "\n", - "Hyperparameters we can modifiy and can influence the performance of the algorithm are :\n", - "\n", - "- \"method\" the scipy optimizer we want to use for the classical optimization step\n", - "- \"reps\" : the deep of the quantum circuit\n", - "- \"optimization_level\" : the level of optimization of the circuit\n", - "- \"nb_shots\" who define the number of time we want to repeat the quantum circuit (not used on simulator)\n", - "\n", - "by default we use the \"COBYLA\" method, you can give value for three hyperparameters in the kwargs :\n", - "\n", - "- maxiter : the maximum number of iteration of the optimizer\n", - "- rhobeg : reasonable initial changes to the variables\n", - "- tol : tolerence to consider the convergence of the minimization\n", - "\n", - "for all other scipy optimizer https://docs.scipy.org/doc/scipy-1.13.1/reference/generated/scipy.optimize.minimize.html (or it's also possible for cobyla), if you want to define some parameters you can do it with a kwargs parameter \"options\", who is a dictionnary of possible options for the optimizer.\n", - "\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "a423d5cc-af42-4a7d-abca-15c4c86a7699", - "metadata": {}, - "outputs": [], - "source": [ - "# kwargs = {\"method\": \"your_method\"}\n", - "# options = {\"options1\": op1, \"options2\": op2, ...}\n", - "# kwargs[\"options\"] = options" - ] - }, - { - "cell_type": "markdown", - "id": "45fbfbc2-6262-4765-a9e9-fc35233c9906", - "metadata": {}, - "source": [ - "There is also the possibility to use an optimizer define in qiskit_algorithms (https://github.com/qiskit-community/qiskit-algorithms/tree/stable/0.3/qiskit_algorithms/optimizers). To use one of them you have just te create it and then passed it in the kwargs as \"optimizer\" parameter." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce79fe2a-ecf2-4d78-934e-83e212252667", - "metadata": {}, - "outputs": [], - "source": [ - "# from qiskit_algorithms.optimizers import SPSA\n", - "# optimizer = SPSA()\n", - "# kwargs = {\"optimizer\": optimizer} or kwargs[\"optimizer\"] = optimizer if kwargs already exist" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "d-opti", - "language": "python", - "name": "d-opti" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.12.3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} From a10640e1711cf80e64a847ab9f5a0bb849925f84 Mon Sep 17 00:00:00 2001 From: armand-gautier Date: Thu, 20 Jun 2024 10:20:56 +0200 Subject: [PATCH 10/10] add test with pytest for quantum solvers, we just verify that there is no bug at the execution when qiskit is available. --- .../coloring/solvers/coloring_quantum.py | 13 +- .../generic_tools/qiskit_tools.py | 3 +- .../generic_tools/quantum_solvers.py | 118 ++++++++++++++++++ tests/quantum/quantum.py | 70 +++++++++++ 4 files changed, 197 insertions(+), 7 deletions(-) create mode 100644 discrete_optimization/generic_tools/quantum_solvers.py create mode 100644 tests/quantum/quantum.py diff --git a/discrete_optimization/coloring/solvers/coloring_quantum.py b/discrete_optimization/coloring/solvers/coloring_quantum.py index 262e7b100..4b181ed1f 100644 --- a/discrete_optimization/coloring/solvers/coloring_quantum.py +++ b/discrete_optimization/coloring/solvers/coloring_quantum.py @@ -92,15 +92,16 @@ def to_quadratic_program(self) -> QuadraticProgram: # si une couleur j est attribué à un noeud, la contrainte C_j doit valoir 1 for i in range(0, self.problem.number_of_nodes): for j in range(0, self.nb_max_color): - # quadratic[var_names[(i, j)], var_names[(i, j)]] = p + quadratic[var_names[(i, j)], var_names[(i, j)]] = p quadratic[var_names[(i, j)], var_names[j]] = -p # chaque noeud doit avoir une unique couleur for i in range(0, self.problem.number_of_nodes): for j in range(0, self.nb_max_color): - # quadratic[var_names[(i, j)], var_names[(i, j)]] = -p + quadratic[var_names[(i, j)], var_names[(i, j)]] += -p for k in range(j + 1, self.nb_max_color): quadratic[var_names[(i, j)], var_names[(i, k)]] = 2 * p + constant += p # deux noeuds adjacents ne peuvent avoir la même couleur for edge in self.problem.graph.graph_nx.edges(): @@ -152,8 +153,8 @@ class QAOAColoringSolver_MinimizeNbColor(SolverColoring, QiskitQAOASolver): def __init__( self, problem: ColoringProblem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, nb_max_color=None, + params_objective_function: Optional[ParamsObjectiveFunction] = None, ): super().__init__(problem, params_objective_function) self.coloring_qiskit = ColoringQiskit_MinimizeNbColor( @@ -171,8 +172,8 @@ class VQEColoringSolver_MinimizeNbColor(SolverColoring, QiskitVQESolver): def __init__( self, problem: ColoringProblem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, nb_max_color=None, + params_objective_function: Optional[ParamsObjectiveFunction] = None, ): super().__init__(problem, params_objective_function) self.coloring_qiskit = ColoringQiskit_MinimizeNbColor( @@ -273,8 +274,8 @@ class QAOAColoringSolver_FeasibleNbColor(SolverColoring, QiskitQAOASolver): def __init__( self, problem: ColoringProblem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, nb_color=None, + params_objective_function: Optional[ParamsObjectiveFunction] = None, ): super().__init__(problem, params_objective_function) self.coloring_qiskit = ColoringQiskit_FeasibleNbColor( @@ -292,8 +293,8 @@ class VQEColoringSolver_FeasibleNbColor(SolverColoring, QiskitVQESolver): def __init__( self, problem: ColoringProblem, - params_objective_function: Optional[ParamsObjectiveFunction] = None, nb_color=None, + params_objective_function: Optional[ParamsObjectiveFunction] = None, ): super().__init__(problem, params_objective_function) self.coloring_qiskit = ColoringQiskit_FeasibleNbColor( diff --git a/discrete_optimization/generic_tools/qiskit_tools.py b/discrete_optimization/generic_tools/qiskit_tools.py index bfd96846f..fcfcffbc7 100644 --- a/discrete_optimization/generic_tools/qiskit_tools.py +++ b/discrete_optimization/generic_tools/qiskit_tools.py @@ -244,6 +244,7 @@ class QiskitQAOASolver(QiskitSolver, Hyperparametrizable): FloatHyperparameter(name="rhobeg", low=0.5, high=1.5, default=1.0), CategoricalHyperparameter(name="tol", choices=[1e-1, 1e-2, 1e-3], default=1e-2), # TODO rajouter initial_point et initial_bound dans les hyperparams ? + # TODO add mixer_operator comme hyperparam ] def __init__( @@ -330,7 +331,7 @@ class QiskitVQESolver(QiskitSolver): IntegerHyperparameter( name="nb_shots", low=10000, high=100000, step=10000, default=10000 ), - IntegerHyperparameter(name="maxiter", low=100, high=1000, step=50, default=300), + IntegerHyperparameter(name="maxiter", low=100, high=2000, step=50, default=300), FloatHyperparameter(name="rhobeg", low=0.5, high=1.5, default=1.0), CategoricalHyperparameter(name="tol", choices=[1e-1, 1e-2, 1e-3], default=1e-2), # TODO rajouter initial_point et initial_bound dans les hyperparams ? diff --git a/discrete_optimization/generic_tools/quantum_solvers.py b/discrete_optimization/generic_tools/quantum_solvers.py new file mode 100644 index 000000000..bd6bed15b --- /dev/null +++ b/discrete_optimization/generic_tools/quantum_solvers.py @@ -0,0 +1,118 @@ +"""Utility module to launch different solvers on the coloring problem.""" + +# Copyright (c) 2022 AIRBUS and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +from typing import Any, Dict, List, Tuple, Type, Union + +from discrete_optimization.coloring.solvers.coloring_quantum import ( + QAOAColoringSolver_FeasibleNbColor, + QAOAColoringSolver_MinimizeNbColor, + VQEColoringSolver_FeasibleNbColor, + VQEColoringSolver_MinimizeNbColor, +) +from discrete_optimization.coloring.solvers.greedy_coloring import ColoringProblem +from discrete_optimization.generic_tools.qiskit_tools import QiskitSolver +from discrete_optimization.generic_tools.result_storage.result_storage import ( + ResultStorage, +) +from discrete_optimization.maximum_independent_set.mis_model import MisProblem +from discrete_optimization.maximum_independent_set.solvers.mis_quantum import ( + QAOAMisSolver, + VQEMisSolver, +) + +solvers_coloring: Dict[str, List[Tuple[Type[QiskitSolver], Dict[str, Any]]]] = { + "qaoa": [ + ( + QAOAColoringSolver_MinimizeNbColor, + {}, + ), + ( + QAOAColoringSolver_FeasibleNbColor, + {}, + ), + ], + "vqe": [ + ( + VQEColoringSolver_MinimizeNbColor, + {}, + ), + ( + VQEColoringSolver_FeasibleNbColor, + {}, + ), + ], +} + +solvers_map_coloring = {} +for key in solvers_coloring: + for solver, param in solvers_coloring[key]: + solvers_map_coloring[solver] = (key, param) + + +solvers_mis: Dict[str, List[Tuple[Type[QiskitSolver], Dict[str, Any]]]] = { + "qaoa": [ + ( + QAOAMisSolver, + {}, + ), + ], + "vqe": [ + ( + VQEMisSolver, + {}, + ), + ], +} + +solvers_map_mis = {} +for key in solvers_mis: + for solver, param in solvers_mis[key]: + solvers_map_mis[solver] = (key, param) + + +def solve( + method: Type[QiskitSolver], problem: MisProblem, **kwargs: Any +) -> ResultStorage: + """Solve a problem instance with a given class of solver. + + Args: + method: class of the solver to use + problem: problem instance + **args: specific options of the solver + + Returns: a ResultsStorage objecting obtained by the solver. + + """ + solver_ = method(problem, **kwargs) + try: + + solver_.init_model() + except AttributeError: + pass + return solver_.solve(**kwargs) + + +def solve_coloring( + method: Type[QiskitSolver], problem: ColoringProblem, nb_color, **kwargs: Any +) -> ResultStorage: + """Solve a problem instance with a given class of solver. + + Args: + method: class of the solver to use + problem: problem instance + nb_color: the number of colors or the max number of colors + **args: specific options of the solver + + Returns: a ResultsStorage objecting obtained by the solver. + + """ + solver_ = method(problem, nb_color) + try: + + solver_.init_model() + except AttributeError: + pass + return solver_.solve(**kwargs) diff --git a/tests/quantum/quantum.py b/tests/quantum/quantum.py new file mode 100644 index 000000000..db0bc5bad --- /dev/null +++ b/tests/quantum/quantum.py @@ -0,0 +1,70 @@ +# Copyright (c) 2024 AIRBUS and its affiliates. +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +import os + +os.environ["DO_SKIP_MZN_CHECK"] = "1" + +import logging + +import networkx as nx +import pytest + +from discrete_optimization.coloring.coloring_model import ColoringProblem +from discrete_optimization.generic_tools.graph_api import Graph +from discrete_optimization.generic_tools.qiskit_tools import qiskit_available +from discrete_optimization.generic_tools.quantum_solvers import ( + solve, + solve_coloring, + solvers_map_coloring, + solvers_map_mis, +) +from discrete_optimization.maximum_independent_set.mis_model import MisProblem + +logger = logging.getLogger(__name__) + + +@pytest.mark.skipif( + not qiskit_available, reason="You need Qiskit modules to this test." +) +@pytest.mark.parametrize("solver_class", solvers_map_coloring) +def test_solvers_coloring(solver_class): + nodes = [(1, {}), (2, {}), (3, {}), (4, {})] + edges = [(1, 2, {}), (1, 3, {}), (2, 4, {})] + nb_colors = 2 + coloring_model: ColoringProblem = ColoringProblem(Graph(nodes=nodes, edges=edges)) + results = solve_coloring( + method=solver_class, + problem=coloring_model, + nb_color=nb_colors, + **solvers_map_coloring[solver_class][1] + ) + sol, fit = results.get_best_solution_fit() + + +@pytest.mark.skipif( + not qiskit_available, reason="You need Qiskit modules to this test." +) +@pytest.mark.parametrize("solver_class", solvers_map_mis) +def test_solvers_mis(solver_class): + graph = nx.Graph() + + graph.add_edge(1, 2) + graph.add_edge(1, 3) + graph.add_edge(2, 4) + graph.add_edge(2, 6) + graph.add_edge(3, 4) + graph.add_edge(3, 5) + graph.add_edge(4, 5) + graph.add_edge(4, 6) + mis_model: MisProblem = MisProblem(graph) + results = solve( + method=solver_class, problem=mis_model, **solvers_map_mis[solver_class][1] + ) + sol, fit = results.get_best_solution_fit() + + +if __name__ == "__main__": + test_solvers_coloring() + test_solvers_mis()