def operator_relocate(self, graph, solution, op_diff_round_digits, anim):
        """applies Relocate inter-route operator to solution
        
        Takes every node from every route and calculates savings when inserted
        into all possible positions in other routes. Insertion is done at
        position with max. saving and procedure starts over again with newly
        created graph as input. Stops when no improvement is found.
        
        Args
        ----
        graph: :networkx:`NetworkX Graph Obj< >`
            A NetworkX graaph is used.
        solution: BaseSolution
            BaseSolution instance
        op_diff_round_digits: float
            Precision (floating point digits) for rounding route length differences.
            
            *Details*: In some cases when an exchange is performed on two routes with one node each,
            the difference between the both solutions (before and after the exchange) is not zero.
            This is due to internal rounding errors of float type. So the loop won't break
            (alternating between these two solutions), we need an additional criterion to avoid
            this behaviour: A threshold to handle values very close to zero as if they were zero
            (for a more detailed description of the matter see http://floating-point-gui.de or
            https://docs.python.org/3.5/tutorial/floatingpoint.html)
        anim: AnimationDing0
            AnimationDing0 object
        
        Returns
        -------
        LocalSearchSolution
           A solution (LocalSearchSolution class)
        
        Notes
        -----
        (Inner) Loop variables:
        
        * i: node that is checked for possible moves (position in the route `tour`, not node name)
        * j: node that precedes the insert position in target route (position in the route `target_tour`, not node name)
            
        Todo
        ----
        * Remove ugly nested loops, convert to more efficient matrix operations
        """
        # shorter var names for loop
        dm = graph._matrix
        dn = graph._nodes        
        
        # Relocate: Search better solutions by checking possible node moves
        while True:
            length_diff_best = 0
            
            for route in solution.routes():

                # exclude origin routes with single high-demand nodes (Load Areas)
                if len(route._nodes) == 1:
                    if solution._problem._is_aggregated[str(route._nodes[0])]:
                        continue

                # create tour by adding depot at start and end
                tour = [graph._depot] + route._nodes + [graph._depot]
                
                for target_route in solution.routes():

                    # exclude (origin+target) routes with single high-demand nodes (Load Areas)
                    if len(target_route._nodes) == 1:
                        if solution._problem._is_aggregated[str(target_route._nodes[0])]:
                            continue

                    target_tour = [graph._depot] + target_route._nodes + [graph._depot]
                    
                    if route == target_route:
                        continue
                    
                    n = len(route._nodes)
                    nt = len(target_route._nodes)+1

                    for i in range(0,n):
                        node = route._nodes[i]
                        for j in range(0,nt):
                            #target_node = target_route._nodes[j]
                            
                            if target_route.can_allocate([node]):
                                length_diff = (-dm[dn[tour[i].name()]][dn[tour[i+1].name()]] -
                                                dm[dn[tour[i+1].name()]][dn[tour[i+2].name()]] +
                                                dm[dn[tour[i].name()]][dn[tour[i+2].name()]] +
                                                dm[dn[target_tour[j].name()]][dn[tour[i+1].name()]] +
                                                dm[dn[tour[i+1].name()]][dn[target_tour[j+1].name()]] -
                                                dm[dn[target_tour[j].name()]][dn[target_tour[j+1].name()]])

                                if length_diff < length_diff_best:
                                    length_diff_best = length_diff
                                    node_best, target_route_best, j_best = node, target_route, j
                                        
            if length_diff_best < 0:
                # insert new node
                target_route_best.insert([node_best], j_best)
                # remove empty routes from solution
                solution._routes = [route for route in solution._routes if route._nodes]

                if anim is not None:
                    solution.draw_network(anim)
                
                #print('Bessere Loesung gefunden:', node_best, target_node_best, target_route_best, length_diff_best)
            
            # no improvement found
            if round(length_diff_best, op_diff_round_digits) == 0:
                break

            
        return solution
applies Relocate inter-route operator to solution
        
        Takes every node from every route and calculates savings when inserted
        into all possible positions in other routes. Insertion is done at
        position with max. saving and procedure starts over again with newly
        created graph as input. Stops when no improvement is found.
        
        Args
        ----
        graph: :networkx:`NetworkX Graph Obj< >`
            A NetworkX graaph is used.
        solution: BaseSolution
            BaseSolution instance
        op_diff_round_digits: float
            Precision (floating point digits) for rounding route length differences.
            
            *Details*: In some cases when an exchange is performed on two routes with one node each,
            the difference between the both solutions (before and after the exchange) is not zero.
            This is due to internal rounding errors of float type. So the loop won't break
            (alternating between these two solutions), we need an additional criterion to avoid
            this behaviour: A threshold to handle values very close to zero as if they were zero
            (for a more detailed description of the matter see http://floating-point-gui.de or
            https://docs.python.org/3.5/tutorial/floatingpoint.html)
        anim: AnimationDing0
            AnimationDing0 object
        
        Returns
        -------
        LocalSearchSolution
           A solution (LocalSearchSolution class)
        
        Notes
        -----
        (Inner) Loop variables:
        
        * i: node that is checked for possible moves (position in the route `tour`, not node name)
        * j: node that precedes the insert position in target route (position in the route `target_tour`, not node name)
            
        Todo
        ----
        * Remove ugly nested loops, convert to more efficient matrix operations