Source code for qlauncher.base.adapter_structure

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