docs: def operator_oropt(self, graph, solution, op_diff_round_digits, anim=None):
          # TODO: check docstring
          """Applies Or-Opt intra-route operator to solution
          
          Takes chains of nodes (length=3..1 consecutive nodes) from a given
          route and calculates savings when inserted into another position on the
          same route (all possible positions). Performes best move (max. saving)
          and starts over again with new route until 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
          -----
          Since Or-Opt is an intra-route operator, it has not to be checked if route can allocate (Route's method
          can_allocate()) nodes during relocation regarding max. peak load/current because the line/cable type is the
          same along the entire route. However, node order within a route has an impact on the voltage stability
          so the check would be actually required. Due to large line capacity (load factor of lines/cables ~60 %)
          the voltage stability issues are neglected.
  
          (Inner) Loop variables:
          
          * s: length (count of consecutive nodes) of the chain that is moved. Values: 3..1
          * i: node that precedes the chain before moving (position in the route `tour`, not node name)
          * j: node that precedes the chain after moving (position in the route `tour`, not node name)
          
          Todo
          ----
          * insert literature reference for Or-algorithm here
          * Remove ugly nested loops, convert to more efficient matrix operations
          """
          no_ctr = 100
          # shorter var names for loop
          dm = graph._matrix
          dn = graph._nodes
          
          for route in solution.routes():
  
              # exclude routes with single high-demand nodes (Load Areas)
              if len(route._nodes) == 1:
                  if solution._problem._is_aggregated[str(route._nodes[0])]:
                      continue
  
              n = len(route._nodes)+1
  
              # create tour by adding depot at start and end
              tour = [graph._depot] + route._nodes + [graph._depot]
              
              # Or-Opt: Search better solutions by checking possible chain moves
              while True:
                  length = route.length()
                  length_best = length
                  
                  for s in range(3,0,-1):
                      for i in range(1,n-s):
                          length_diff = (length -
                                         dm[dn[tour[i-1].name()]][dn[tour[i].name()]] -
                                         dm[dn[tour[i+s-1].name()]][dn[tour[i+s].name()]] +
                                         dm[dn[tour[i-1].name()]][dn[tour[i+s].name()]])
                          for j in range(i+s+1,n+1):
                              if j == n:
                                  j2 = 1
                              else:
                                  j2 = j+1
                              length_new = (length_diff +
                                            dm[dn[tour[j-1].name()]][dn[tour[i].name()]] +
                                            dm[dn[tour[i+s-1].name()]][dn[tour[j2-1].name()]] -
                                            dm[dn[tour[j-1].name()]][dn[tour[j2-1].name()]])
                              if length_new < length_best:
                                  length_best = length_new
                                  s_best, i_best, j_best = s, i, j
                  if length_best < length:
                      tour = tour[0:i_best] + tour[i_best+s_best:j_best] + tour[i_best:i_best+s_best] + tour[j_best:n+1]
  
                      if anim is not None:
                          solution.draw_network(anim)
  
                  # no improvement found
                  if length_best == length:
                      # replace old route by new (same arg for allocation and deallocation since node order is considered at allocation)
                      solution._routes[solution._routes.index(route)].deallocate(tour[1:-1])
                      solution._routes[solution._routes.index(route)].allocate(tour[1:-1])
                      
                      break
          
          #solution = LocalSearchSolution(solution, graph, new_routes)
          return solution