docs: def avoid_dead_links(root, machine, wrap_around=False):
      """Modify a RoutingTree to route-around dead links in a Machine.
  
      Uses A* to reconnect disconnected branches of the tree (due to dead links
      in the machine).
  
      Parameters
      ----------
      root : :py:class:`~rig.place_and_route.routing_tree.RoutingTree`
          The root of the RoutingTree which contains nothing but RoutingTrees
          (i.e. no vertices and links).
      machine : :py:class:`~rig.place_and_route.Machine`
          The machine in which the routes exist.
      wrap_around : bool
          Consider wrap-around links in pathfinding heuristics.
  
      Returns
      -------
      (:py:class:`~.rig.place_and_route.routing_tree.RoutingTree`,
       {(x,y): :py:class:`~.rig.place_and_route.routing_tree.RoutingTree`, ...})
          A new RoutingTree is produced rooted as before. A dictionarry mapping
          from (x, y) to the associated RoutingTree is provided for convenience.
  
      Raises
      ------
      :py:class:~rig.place_and_route.exceptions.MachineHasDisconnectedSubregion`
          If a path to reconnect the tree cannot be found.
      """
      # Make a copy of the RoutingTree with all broken parts disconnected
      root, lookup, broken_links = copy_and_disconnect_tree(root, machine)
  
      # For each disconnected subtree, use A* to connect the tree to *any* other
      # disconnected subtree. Note that this process will eventually result in
      # all disconnected subtrees being connected, the result is a fully
      # connected tree.
      for parent, child in broken_links:
          child_chips = set(c.chip for c in lookup[child])
  
          # Try to reconnect broken links to any other part of the tree
          # (excluding this broken subtree itself since that would create a
          # cycle).
          path = a_star(child, parent,
                        set(lookup).difference(child_chips),
                        machine, wrap_around)
  
          # Add new RoutingTree nodes to reconnect the child to the tree.
          last_node = lookup[path[0][1]]
          last_direction = path[0][0]
          for direction, (x, y) in path[1:]:
              if (x, y) not in child_chips:
                  # This path segment traverses new ground so we must create a
                  # new RoutingTree for the segment.
                  new_node = RoutingTree((x, y))
                  # A* will not traverse anything but chips in this tree so this
                  # assert is meerly a sanity check that this ocurred correctly.
                  assert (x, y) not in lookup, "Cycle created."
                  lookup[(x, y)] = new_node
              else:
                  # This path segment overlaps part of the disconnected tree
                  # (A* doesn't know where the disconnected tree is and thus
                  # doesn't avoid it). To prevent cycles being introduced, this
                  # overlapped node is severed from its parent and merged as part
                  # of the A* path.
                  new_node = lookup[(x, y)]
  
                  # Find the node's current parent and disconnect it.
                  for node in lookup[child]:  # pragma: no branch
                      dn = [(d, n) for d, n in node.children if n == new_node]
                      assert len(dn) <= 1
                      if dn:
                          node.children.remove(dn[0])
                          # A node can only have one parent so we can stop now.
                          break
              last_node.children.append((Routes(last_direction), new_node))
              last_node = new_node
              last_direction = direction
          last_node.children.append((last_direction, lookup[child]))
  
      return (root, lookup)