docs: def _step(vertices, d_limit, temperature, placements, l2v, v2n,
            vertices_resources, fixed_vertices, machine, has_wrap_around_links,
            random):
      """Attempt a single swap operation: the kernel of the Simulated Annealing
      algorithm.
  
      Parameters
      ----------
      vertices : [vertex, ...]
          The set of *movable* vertices.
      d_limit : int
          The maximum distance over-which swaps are allowed.
      temperature : float > 0.0 or None
          The temperature (i.e. likelihood of accepting a non-advantageous swap).
          Higher temperatures mean higher chances of accepting a swap.
      placements : {vertex: (x, y), ...}
          The positions of all vertices, will be updated if a swap is made.
      l2v : {(x, y): [vertex, ...], ...}
          Lookup from chip to vertices, will be updated if a swap is made.
      v2n : {vertex: [:py:class:`rig.netlist.Net`, ...], ...}
          Lookup from vertex to all nets that vertex is in.
      vertices_resources : {vertex: {resource: value, ...}, ...}
      fixed_vertices : {vertex, ...}
          The set of vertices which must not be moved.
      machine : :py:class:`rig.place_and_route.Machine`
          Describes the state of the machine including the resources actually
          available on each chip given the current placements. Updated if a swap
          is made.
      has_wrap_around_links : bool
          Should the placements attempt to make use of wrap-around links?
      random : :py:class:`random.Random`
          The random number generator to use.
  
      Returns
      -------
      (swapped, delta)
          swapped is a boolean indicating if a swap was made.
  
          delta is a float indicating the change in cost resulting from the swap
          (or 0.0 when no swap is made).
      """
      # Special case: If the machine is a singleton, no swaps can be made so just
      # terminate.
      if machine.width == 1 and machine.height == 1:
          return (False, 0.0)
  
      # Select a vertex to swap at random
      src_vertex = random.choice(vertices)
  
      # Select a random (nearby) location to swap the vertex with. Note: this is
      # guaranteed to be different from the selected vertex, otherwise the swap
      # cannot change the cost of the placements.
      # XXX: Does not consider hexagonal properties of the system!
      src_location = placements[src_vertex]
      dst_location = src_location
      while dst_location == src_location:
          if has_wrap_around_links:
              dst_location = tuple(random.randint(v - d_limit,
                                                  v + d_limit) % limit
                                   for v, limit
                                   in [(src_location[0], machine.width),
                                       (src_location[1], machine.height)])
          else:
              dst_location = tuple(random.randint(max(v - d_limit, 0),
                                                  min(v + d_limit, limit-1))
                                   for v, limit
                                   in [(src_location[0], machine.width),
                                       (src_location[1], machine.height)])
  
      # If we've inadvertently selected a dead chip to swap to, abort the swap.
      if dst_location not in machine:
          return (False, 0.0)
  
      # Find out which vertices (if any) must be swapped out of the destination
      # to make room for the vertex we're moving.
      src_resources = vertices_resources[src_vertex]
      dst_vertices = _get_candidate_swap(src_resources, dst_location,
                                         l2v, vertices_resources,
                                         fixed_vertices, machine)
  
      # The destination simply isn't big enough (no matter how many vertices at
      # the destination are moved), abort the swap.
      if dst_vertices is None:
          return (False, 0.0)
  
      # Make sure that any vertices moved out of the destination will fit in the
      # space left in the source location. If there isn't enough space, abort the
      # swap.
      resources = machine[src_location]
      resources = add_resources(resources, src_resources)
      for dst_vertex in dst_vertices:
          resources = subtract_resources(resources,
                                         vertices_resources[dst_vertex])
      if overallocated(resources):
          return (False, 0.0)
  
      # Work out the cost of the nets involved *before* swapping
      cost_before = _vertex_net_cost(src_vertex, v2n, placements,
                                     has_wrap_around_links, machine)
      for dst_vertex in dst_vertices:
          cost_before += _vertex_net_cost(dst_vertex, v2n, placements,
                                          has_wrap_around_links, machine)
  
      # Swap the vertices
      _swap([src_vertex], src_location,
            dst_vertices, dst_location,
            l2v, vertices_resources, placements, machine)
  
      # Work out the new cost
      cost_after = _vertex_net_cost(src_vertex, v2n, placements,
                                    has_wrap_around_links, machine)
      for dst_vertex in dst_vertices:
          cost_after += _vertex_net_cost(dst_vertex, v2n, placements,
                                         has_wrap_around_links, machine)
  
      # If the swap was beneficial, keep it, otherwise keep it with a probability
      # related to just how bad the cost change is is and the temperature.
      delta = cost_after - cost_before
      if delta <= 0.0 or random.random() < math.exp(-delta/temperature):
          # Keep the swap!
          return (True, delta)
      else:
          # Revert the swap
          _swap([src_vertex], dst_location,
                dst_vertices, src_location,
                l2v, vertices_resources, placements, machine)
          return (False, 0.0)