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