docs: def multigrad_dict(fun):
      "Takes gradients wrt all arguments simultaneously,"
      "returns a dict mapping 'argname' to 'gradval'"
  
      import funcsigs
      sig = funcsigs.signature(fun)
  
      def select(preds, lst):
          idx = lambda item: next(
              (i for i, pred in enumerate(preds) if pred(item)), len(preds))
          results = [[] for _ in preds] + [[]]
          for item in lst:
              results[idx(item)].append(item)
          return results
  
      is_var_pos = lambda name: sig.parameters[name].kind == sig.parameters[name].VAR_POSITIONAL
      is_var_kwd = lambda name: sig.parameters[name].kind == sig.parameters[name].VAR_KEYWORD
      var_pos, var_kwd, argnames = select([is_var_pos, is_var_kwd], sig.parameters)
  
      todict = lambda dct: {key:dct[key] for key in dct}
  
      def apply_defaults(arguments):
          defaults = {name: param.default for name, param in sig.parameters.items()
                      if param.default is not param.empty}
          return OrderedDict((name, arguments[name] if name in arguments else defaults[name])
                             for name in sig.parameters)
  
      def gradfun(*args, **kwargs):
          bindings = sig.bind(*args, **kwargs)
  
          args = lambda dct: tuple(dct[var_pos[0]]) if var_pos else ()
          kwargs = lambda dct: todict(dct[var_kwd[0]]) if var_kwd else {}
          others = lambda dct: tuple(dct[argname] for argname in argnames
                                     if argname not in var_kwd + var_pos)
  
          newfun = lambda dct: fun(*(others(dct) + args(dct)), **kwargs(dct))
  
          argdict = apply_defaults(bindings.arguments)
          grad_dict = grad(newfun)(dict(argdict))
          return OrderedDict((argname, grad_dict[argname]) for argname in argdict)
  
      return gradfun