docs: def embed_ising(source_linear, source_quadratic, embedding, target_adjacency, chain_strength=1.0):
      """Embeds a logical Ising model onto another graph via an embedding.
  
      Args:
          source_linear (dict): The linear biases to be embedded. Should be a dict of
              the form {v: bias, ...} where v is a variable in the source model
              and bias is the linear bias associated with v.
          source_quadratic (dict): The quadratic biases to be embedded. Should be a dict
              of the form {(u, v): bias, ...} where u, v are variables in the
              source model and bias is the quadratic bias associated with (u, v).
          embedding (dict): The mapping from the source graph to the target graph.
              Should be of the form {v: {s, ...}, ...} where v is a variable in the
              source model and s is a variable in the target model.
          target_adjacency (dict/:class:`networkx.Graph`): The adjacency dict of the target
              graph. Should be a dict of the form {s: Ns, ...} where s is a variable
              in the target graph and Ns is the set of neighbours of s.
          chain_strength (float, optional): The quadratic bias that should be used
              to create chains.
  
      Returns:
          (dict, dict, dict): A 3-tuple containing:
  
              dict: The linear biases of the target problem. In the form {s: bias, ...}
              where s is a node in the target graph and bias is the associated linear bias.
  
              dict: The quadratic biases of the target problem. A dict of the form
              {(s, t): bias, ...} where (s, t) is an edge in the target graph and bias is
              the associated quadratic bias.
  
              dict: The quadratic biases that induce the variables in the target problem to
              act as one. A dict of the form {(s, t): -chain_strength, ...} which
              is the quadratic biases associated with the chains.
  
      Examples:
          >>> source_linear = {'a': 1, 'b': 1}
          >>> source_quadratic = {('a', 'b'): -1}
          >>> embedding = {'a': [0, 1], 'b': [2]}
          >>> target_adjacency = {0: {1, 2}, 1: {0, 2}, 2: {0, 1}}
          >>> target_linear, target_quadratic, chain_quadratic = embed_ising(
          ...     source_linear, source_quadratic, embedding, target_adjacency)
          >>> target_linear
          {0: 0.5, 1: 0.5, 2: 1.0}
          >>> target_quadratic
          {(0, 2): -0.5, (1, 2): -0.5}
          >>> chain_quadratic
          {(0, 1): -1.0}
  
      """
  
      # store variables in the target graph that the embedding hasn't used
      unused = {v for v in target_adjacency} - set().union(*embedding.values())
  
      # ok, let's begin with the linear biases.
      # we spread the value of h evenly over the chain
      target_linear = {v: 0. for v in target_adjacency}
      for v, bias in iteritems(source_linear):
          try:
              chain_variables = embedding[v]
          except KeyError:
              # if our embedding doesn't deal with this variable, assume it's an isolated vertex and embed it to one of
              # the unused variables. if this turns out to not be an isolated vertex, it will be caught below when
              # handling quadratic biases
              try:
                  embedding[v] = {unused.pop()}
              except KeyError:
                  raise ValueError('no embedding provided for source variable {}'.format(v))
              chain_variables = embedding[v]
  
          b = bias / len(chain_variables)
  
          for s in chain_variables:
              try:
                  target_linear[s] += b
              except KeyError:
                  raise ValueError('chain variable {} not in target_adjacency'.format(s))
  
      # next up the quadratic biases.
      # We spread the quadratic biases evenly over the edges
      target_quadratic = {}
      for (u, v), bias in iteritems(source_quadratic):
          edges = set()
  
          if u not in embedding:
              raise ValueError('no embedding provided for source variable {}'.format(u))
          if v not in embedding:
              raise ValueError('no embedding provided for source variable {}'.format(v))
  
          for s in embedding[u]:
              for t in embedding[v]:
                  try:
                      if s in target_adjacency[t] and (t, s) not in edges:
                          edges.add((s, t))
                  except KeyError:
                      raise ValueError('chain variable {} not in target_adjacency'.format(s))
  
          if not edges:
              raise ValueError("no edges in target graph between source variables {}, {}".format(u, v))
  
          b = bias / len(edges)
  
          # in some cases the logical J can have (u, v) and (v, u) as inputs, so make
          # sure we are not doubling them up with our choice of ordering
          for s, t in edges:
              if (s, t) in target_quadratic:
                  target_quadratic[(s, t)] += b
              elif (t, s) in target_quadratic:
                  target_quadratic[(t, s)] += b
              else:
                  target_quadratic[(s, t)] = b
  
      # finally we need to connect the nodes in the chains
      chain_quadratic = {}
      for chain in itervalues(embedding):
          chain_quadratic.update(chain_to_quadratic(chain, target_adjacency, chain_strength))
  
      return target_linear, target_quadratic, chain_quadratic