Source code for quantum_launcher.base.adapter_structure

  1from collections import defaultdict
  2from typing import Dict, Callable, Any
  3from inspect import signature
  4import warnings
  5import networkx as nx
  6from .base import Problem
  7
  8__QL_ADAPTERS: Dict[str, Dict[str, Callable]] = defaultdict(lambda: {})  # adapters[to][from]
  9__QL_FORMATTERS: Dict[type[Problem] | None, Dict[str, Callable]] = defaultdict(lambda: {})  # formatters[problem][format]
 10
 11
 12def _get_callable_name(c: Callable) -> str:
 13    """
 14    Gets name of input callable, made it because callable classes don't have a __name__
 15    """
 16    try:
 17        return c.__name__
 18    except AttributeError:
 19        return str(c.__class__)
 20
 21
 22def _merge_dicts(dicts: list[dict]) -> dict:
 23    out = {}
 24    for d in dicts:
 25        out = out | d
 26    return out
 27
 28
[docs] 29class ProblemFormatter: 30 """ 31 Converts input problem to a given format (input and output types determined by formatter and adapters in __init__) 32 33 Probably shouldn't be constructed directly, call :py:func:`get_formatter()` 34 """ 35 36 def __init__(self, formatter: Callable, adapters: list[Callable] | None = None): 37 self.formatter = formatter 38 self.adapters = [a['func'] for a in adapters] if adapters is not None else [] 39 self.adapter_requirements = _merge_dicts([a['formatter_requirements'] for a in adapters] if adapters is not None else []) 40 41 self.formatter_sig = signature(self.formatter) 42 43 self.run_params = {} 44 45 def _formatter_call(self, run_params, *args, **kwargs): 46 params_used = {k: v for k, v in run_params.items() if k in self.formatter_sig.parameters} 47 48 unused_count = len(run_params) - len(params_used) 49 if unused_count > 0: 50 warnings.warn( 51 f"{unused_count} unused parameters. {_get_callable_name(self.formatter)} does not accept {[k for k in run_params if not k in params_used]}", Warning) 52 return self.formatter(*args, **kwargs, **params_used) 53 54 def __call__(self, *args, **kwargs): 55 # Reset bound params 56 curr_run_params = dict(self.run_params) 57 self.run_params = {} 58 59 common_params = set(curr_run_params.keys()).intersection(set(self.adapter_requirements.keys())) 60 if len(common_params) > 0: 61 warnings.warn( 62 f"Attempting to reassign parameter values required by one of the adapters: {common_params}, those params will not be set.") 63 64 curr_run_params = curr_run_params | self.adapter_requirements 65 66 out = self._formatter_call(curr_run_params, *args, **kwargs) 67 for a in self.adapters: 68 out = a(out) 69 70 return out 71
[docs] 72 def get_pipeline(self) -> str: 73 """ 74 Returns: 75 String representing the conversion process: problem -> formatter -> adapters (if applicable) 76 """ 77 return " -> ".join( 78 [str(list(self.formatter_sig.parameters.keys())[0])] + 79 [_get_callable_name(self.formatter)] + 80 [_get_callable_name(fn) for fn in self.adapters] 81 )
82
[docs] 83 def set_run_param(self, param: str, value: Any) -> None: 84 """ 85 Sets a parameter to be used during next conversion. 86 87 Args: 88 param (str): parameter key 89 value (str): parameter value 90 """ 91 self.run_params[param] = value
92
[docs] 93 def set_run_params(self, params: dict[str, Any]) -> None: 94 """ 95 Sets multiple parameters to be used during next conversion. 96 97 Args: 98 params (dict[str, Any]): parameters to be set 99 """ 100 for k, v in params.items(): 101 self.set_run_param(k, v)
102 103
[docs] 104def adapter(translates_from: str, translates_to: str, **kwargs) -> Callable: 105 """ 106 Register a function as an adapter from one problem format to another. 107 108 Args: 109 translates_from (str): Input format 110 translates_to (str): Output format 111 112 Returns: 113 Same function 114 """ 115 def decorator(func): 116 if isinstance(func, type): 117 func = func() 118 __QL_ADAPTERS[translates_to][translates_from] = {'func': func, 'formatter_requirements': kwargs} 119 return func 120 return decorator
121 122
[docs] 123def formatter(problem: type[Problem] | None, alg_format: str): 124 """ 125 Register a function as a formatter for a given problem type to a given format. 126 127 Args: 128 problem (type[Problem]): Input problem type 129 alg_format (str): Output format 130 131 Returns: 132 Same function 133 """ 134 def decorator(func): 135 if isinstance(func, type): 136 func = func() 137 __QL_FORMATTERS[problem][alg_format] = func 138 return func 139 return decorator
140 141 142def _find_shortest_adapter_path(problem: type[Problem], alg_format: str) -> list[str] | None: 143 """ 144 Creates directed graph of possible conversions between formats and finds shortest path of formats between problem and alg_format. 145 146 Returns: 147 List of formats or None if no path was found. 148 """ 149 G = nx.DiGraph() 150 for problem_node in __QL_FORMATTERS[problem]: 151 G.add_edge("__problem__", problem_node) 152 153 for out_form in __QL_ADAPTERS: 154 for in_form in __QL_ADAPTERS[out_form]: 155 G.add_edge(in_form, out_form) 156 157 if not G.has_node(alg_format): 158 return None 159 160 path = nx.shortest_path(G, "__problem__", alg_format) 161 assert isinstance(path, list) or path is None, "Something went wrong in `nx.shortest_path`" 162 return path 163 164
[docs] 165def get_formatter(problem: type[Problem], alg_format: str) -> ProblemFormatter: 166 """ 167 Creates a ProblemFormatter that converts a given Problem subclass into the requested format. 168 169 Args: 170 problem (type[Problem]): Input problem type 171 alg_format (str): Desired output format 172 173 Returns: 174 ProblemFormatter meeting the desired criteria. 175 176 Raises: 177 ValueError: If no combination of adapters can achieve conversion from problem to desired format. 178 """ 179 formatter, adapters = None, None 180 available_problem_formats = set(__QL_FORMATTERS[problem].keys()) 181 if alg_format in available_problem_formats: 182 formatter = __QL_FORMATTERS[problem][alg_format] 183 else: 184 path = _find_shortest_adapter_path(problem, alg_format) 185 186 if path is None: 187 formatter = default_formatter 188 else: 189 formatter = __QL_FORMATTERS[problem][path[1]] 190 adapters = [] 191 for i in range(1, len(path)-1): 192 adapters.append(__QL_ADAPTERS[path[i+1]][path[i]]) 193 194 return ProblemFormatter(formatter, adapters)
195 196
[docs] 197@formatter(None, 'none') 198def default_formatter(problem: Problem): 199 return problem.instance