docs: 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