<SYSTEM_TASK:>
Solve the following problem using Python, implementing the functions described below, one line at a time
<END_TASK>
<USER_TASK:>
Description:
 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`, 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)