Source code for qugradlab.pulses.invertible_functions._invertible_function

  1"""
  2A class for representing invertible functions.
  3"""
  4
  5from functools import partial
  6from typing import Callable, Optional
  7
  8from qugrad.pulses import compose
  9
[docs] 10class InvertibleFunction(): 11 """A class representing an invertible function.""" 12 13 _inverse: Optional["InvertibleFunction"] = None 14 """ 15 The inverse of the function 16 17 Parameters 18 ---------- 19 input 20 The input to the inverse function 21 *args 22 Any additional positional arguments to pass to the inverse function. 23 **kwargs 24 Keyword arguments to pass to the inverse function. 25 26 Returns 27 ------- 28 Any 29 The result of the inverse function call. 30 31 Note 32 ---- 33 `_inverse` should satisfy the following assertions:: 34 35 assert self._inverse(self(x, *args, **kwargs), *args, **kwargs) == x 36 assert self(self._inverse(x, *args, **kwargs), *args, **kwargs) == x 37 38 for all inputs ``x``. 39 """ 40
[docs] 41 def __init__(self, func: Callable): 42 """ 43 Wraps the a function in an InvertibleFunction object so that an inverse 44 can be associated with it. 45 46 Parameters 47 ---------- 48 func : Callable 49 The function to be wrapped 50 """ 51 self._func = func
52 def __call__(self, input, *args, **kwargs): 53 """ 54 Call the function with the given arguments. 55 56 Parameters 57 ---------- 58 input 59 The input to the function 60 *args 61 Any additional positional arguments to pass to the function. 62 **kwargs 63 Keyword arguments to pass to the function. 64 65 Returns 66 ------- 67 Any 68 The result of the function call. 69 """ 70 return self._func(input, *args, **kwargs) 71 @property 72 def inverse(self): 73 """ 74 The inverse of the function 75 76 Parameters 77 ---------- 78 input 79 The input to the inverse function 80 *args 81 Any additional positional arguments to pass to the inverse function. 82 **kwargs 83 Keyword arguments to pass to the inverse function. 84 85 Returns 86 ------- 87 Any 88 The result of the inverse function call. 89 90 Note 91 ---- 92 `inverse` should satisfy the following assertions:: 93 94 assert self.inverse(self(x, *args, **kwargs), *args, **kwargs) == x 95 assert self(self.inverse(x, *args, **kwargs), *args, **kwargs) == x 96 97 for all inputs ``x``. 98 """ 99 if self._inverse is not None: 100 return self._inverse 101 raise NotImplementedError("This function has no inverse.")
[docs] 102 def set_inverse(self, inverse_func: Callable): 103 """ 104 Sets the inverse of the function. 105 106 Parameters 107 ---------- 108 inverse_func : Callable 109 The inverse function 110 111 Note 112 ---- 113 ``inverse_func`` should satisfy the following assertions:: 114 115 assert inverse_func(self(x, *args, **kwargs), *args, **kwargs) == x 116 assert self(inverse_func(x, *args, **kwargs), *args, **kwargs) == x 117 118 for all inputs ``x``. 119 """ 120 self._inverse = InvertibleFunction(inverse_func) 121 self.inverse._inverse = self
[docs] 122 def specify_parameters(self, **kwargs) -> "InvertibleFunction": 123 """ 124 Allows keyword parameters for the function and inverse function to be 125 pre-specified. This generates a new :class:`InvertibleFunction` without 126 the specified parameters in the call signatures. 127 128 Parameters 129 ---------- 130 **kwargs 131 Keyword arguments to pre-specify for the function and inverse 132 function. 133 134 Returns 135 ------- 136 InvertibleFunction 137 A new :class:`InvertibleFunction` with the specified parameters 138 pre-specified. 139 """ 140 func = partial(self.__call__, **kwargs) 141 inverse = partial(self.inverse.__call__, **kwargs) 142 new_invertible_function = InvertibleFunction(func) 143 new_invertible_function.set_inverse(inverse) 144 return new_invertible_function
[docs] 145 def compose(self, 146 inner_invertible_function: "InvertibleFunction", 147 *args, 148 **kwargs 149 ) -> "InvertibleFunction": 150 """ 151 Composes the :class:`InvertibleFunction` with another 152 :class:`InvertibleFunction` to create a new :class:`InvertibleFunction` 153 along with the composed inverse. That is the following assertions should 154 hold:: 155 156 assert f.compose(g, *f_args, **f_kwargs)(x, *g_args, **g_kwargs) \ 157 == f(g(x, *g_args, **g_kwargs), *f_args, **f_kwargs) 158 159 for all inputs ``x``. 160 161 Parameters 162 ---------- 163 inner_invertible_function : InvertibleFunction 164 The :class:`InvertibleFunction` be be called first. The output of 165 this :class:`InvertibleFunction` will be passed to the current 166 :class:`InvertibleFunction`. 167 *args 168 Any additional positional arguments to pass to the function. 169 170 **kwargs 171 Keyword arguments to pass to the function. 172 173 Note 174 ---- 175 Enough positional arguments and keyword arguments need to be passed that 176 the calling :class:`InvertibleFunction` has only one remaining parameter 177 (``input``). 178 179 Returns 180 ------- 181 InvertibleFunction 182 A new :class:`InvertibleFunction` that is the composition of the two 183 functions. 184 """ 185 new_invertible_function = InvertibleFunction(compose(self.__call__, 186 inner_invertible_function.__call__, 187 *args, 188 **kwargs)) 189 if (inner_invertible_function._inverse is not None 190 and self._inverse is not None): 191 inverse = compose(inner_invertible_function.inverse.__call__, 192 self.inverse.__call__, 193 *args, 194 **kwargs) 195 new_invertible_function.set_inverse(inverse) 196 return new_invertible_function