Skip to content
Snippets Groups Projects
Select Git revision
  • 0635f8ed3fe608bb4784a14400ff382f4a47f9b3
  • master default protected
  • dev
  • install
  • new_master
  • protein_ortho
  • documentation
  • pr18
  • dev-licence
  • docker
  • prodigal_train
  • containers
  • module_all
  • functional_tests
  • opti
  • helpers
  • v1.4.1
  • v1.4.0
  • v1.3.1
  • v1.3.0
  • v1.2.0
  • v1.1.0
  • v1.0.1
  • v1.0
24 results

fastme_func.py

Blame
  • rule.py 29.52 KiB
    # -*- coding: utf-8 -*-
    
    ########################################################################
    # Author: Nicolas Maillet                                              #
    # Copyright © 2018 Institut Pasteur, Paris.                            #
    # See the COPYRIGHT file for details                                   #
    #                                                                      #
    # This file is part of Rapid Peptide Generator (RPG) software.         #
    #                                                                      #
    # RPG is free software: you can redistribute it and/or modify          #
    # it under the terms of the GNU General Public License as published by #
    # the Free Software Foundation, either version 3 of the License, or    #
    # any later version.                                                   #
    #                                                                      #
    # RPG is distributed in the hope that it will be useful,               #
    # but WITHOUT ANY WARRANTY; without even the implied warranty of       #
    # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
    # GNU General Public License for more details.                         #
    #                                                                      #
    # You should have received a copy of the GNU General Public license    #
    # along with RPG (LICENSE file).                                       #
    # If not, see <http://www.gnu.org/licenses/>.                          #
    ########################################################################
    
    """Contains class and functions related to definition of
    cleaving rules
    """
    import re
    from collections import defaultdict
    import core
    
    class Rule:
        """Definition of a principal rule defining where a cleavage occurs.
    
        :param index: position where to look for a specific amino acid
        :param letter: amino acid to look for
        :param cut: cut or not at this position on this amino acid
        :param pos: cut before (0) of after (1) amino acid. -1 for unused value
        :type index: signed int
        :type letter: char
        :type cut: bool
        :type pos: int
    
        :var rules: additional sub-rules of this rule
        :vartype rules: list(:py:class:`Rule`)
        """
        def __init__(self, index, letter, cut, pos):
            self.index = index  # where to look for this aa?
            self.letter = letter  # the aa
            self.cut = cut  # if letter detected at index, cut or not?
            self.pos = pos  # cut before (0) of after (1) letter (-1 if not used)
            self.rules = []  # List of additional rules
    
        # self representation for print
        # lvl handle number of '\t' needed
        def __repr__(self, lvl=0):
            ret = ""
            for _ in range(lvl):
                ret += "\t"
            ret += "index=" + str(self.index) + "\n"
            for _ in range(lvl):
                ret += "\t"
            ret += "letter=" + str(self.letter) + "\n"
            for _ in range(lvl):
                ret += "\t"
            ret += "cut=" + str(self.cut) + "\n"
            for _ in range(lvl):
                ret += "\t"
            ret += "pos=" + str(self.pos) + "\n"
            # Sub-rules
            for i in self.rules:
                ret += str(i.__repr__(lvl + 1))
            return ret
    
        # Equality between two Rules
        def __eq__(self, other):
            if isinstance(self, other.__class__):
                return self.__dict__ == other.__dict__
            return False
    
        # Needed with __eq__ to make it hashable
        def __hash__(self):
            return hash(self.__dict__.values())
    
        def equ(self, other):
            """Test equality with another rule **without taking into
            account sub-rules nor `cut`**.
    
            :param other: rule to compare with
            :type other: :py:class:`Rule`
    
            :return: `True` if rules are equal, `False` otherwise
            :rtype: bool
    
            :note: use '==' for complete equality including sub-rules.
            """
            return (self.index == other.index) and (self.letter == other.letter) \
                and (self.pos == other.pos)
    
        def contains(self, other):
            """Test if another rule is contains in sub-rules **without
            taking into account sub-rules nor `cut`** and return the
            sub-rule if founded.
    
            :param other: rule to compare with
            :type other: :py:class:`Rule`
    
            :return: the sub-rule is founded, `None` otherwise
            :rtype: :py:class:`Rule`
            """
            ret = None
            for i in self.rules:
                if i.equ(other):
                    ret = i
            return ret
    
        def contains_any_level(self, other, ret=False):
            """Test if another rule is contains within **without taking into
            account sub-rules nor `cut`** no matter what level.
    
            :param other: rule to compare with
            :type other: :py:class:`Rule`
            :param ret: previous `ret` value (default: `False`)
            :type ret: bool
    
            :return: `True` if rule is included, `False` otherwise
            :rtype: bool
            """
            for i in self.rules:
                if i.equ(other):
                    ret = True
                else:
                    ret = i.contains_any_level(other, ret)
            return ret
    
        def get_header(self):
            """Format header of the rule in Python.
    
            :return: header of this rule in Python
            :rtype: str
            """
            return 'rule.Rule(' + str(self.index) + ', "' + str(self.letter) + \
                '", ' + str(self.cut) + ', ' + str(self.pos) + ')'
    
        def get_all_headers(self):
            """Format header of the rule and sub-rules in Python.
    
            :return: the complete header in Python
            :rtype: str
            """
            ret = []
            # Header of this rule
            ret.append(self.get_header())
            # Sub-rules
            for i in self.rules:
                ret.append(i.get_all_headers())
            return ret
    
        def format_a_rule(self, prev_name, prev_com):
            """Format the rule in Python.
    
            :param prev_name: name of the upper rule
            :param prev_com: comment of the upper rule
            :type prev_name: str
            :type prev_com: str
    
            :return: this rule in Python
            :rtype: str
            """
            ret = []
            # Var name
            if prev_name == "":
                # No prev name, add the pos to the name
                ret_name = str(self.pos).upper()
            else:
                ret_name = prev_name
            # Should we add the new letter of the rule before or
            # after the prev_name?
            if self.index > 0:
                ret_name += self.letter
            elif not ret_name:
                ret_name = self.letter
            else:
                ret_name = self.letter + "_" + ret_name
            # Add the index on the name to avoid same name
            if self.index != 0:
                ret_name += str(self.index)
            # Change '-' to 'm'
            ret_name = ret_name.replace("-", "M")
            # Comment
            ret_com = prev_com
            # Modify the prev_com if needed
            if prev_com != " # ":
                if self.cut:
                    ret_com = ret_com.replace("Never", "Always")
                else:
                    ret_com = ret_com.replace("Always", "Never")
                # Order of letter
                if self.index > 0:
                    ret_com = ret_com.replace(", except...", ", followed by " +
                                              self.letter + ", except...")
                else:
                    ret_com = ret_com.replace(", except...", ", preceded by " +
                                              self.letter + ", except...")
            # First comment
            else:
                ret_com += "Always" if self.cut else "Never"
                ret_com += " cut "
                ret_com += "after " if self.pos > 0 else "before "
                ret_com += self.letter
                ret_com += ", except..."
            # Full line to write
            ret_total = ret_name + " = " + self.get_header() + ret_com
            # Return all of those
            ret.append(ret_name)
            ret.append(ret_com)
            ret.append(ret_total)
            return ret
    
        def format_rule(self, prev_name="", prev_com=" # "):
            """Format the whole rule, including sub-rules, in Python,
            ready to be written.
    
            :param prev_name: name of the upper rule
            :param prev_com: comment of the upper rule
            :type prev_name: str
            :type prev_com: str
    
            :return: the whole rule in Python
            :rtype: str
            """
            # It is on a enzyme function, we need indentation
            ret = ""
            # Get this rule
            tmp = self.format_a_rule(prev_name, prev_com)
            # Write the line of the rule
            ret += tmp[2] + "\n"
            # Sub-rules:
            for i in self.rules:
                # Pass the current name and comment of this rule
                ret += i.format_rule(tmp[0], tmp[1])
            # Not a main rule
            if prev_name != "":
                # Add this rule to its upper-rule
                ret += prev_name + ".rules.append(" + tmp[0] + ")\n"
            # Main rule
            else:
                # Add it to the return of the enzyme function
                ret += "ENZ.append(" + tmp[0] + ")\n\n"
            return ret
    
    
    def check_rule(exprule):
        """Check if a rule is properly inputed.
    
        :param exprule: the raw expression of a rule
        :type exprule: str
    
        :return: `exprule` if it is correct, empty char otherwise
        :rtype: str
        """
    
        # Get everything in UPPER
        clean_exprule = exprule.upper()
        # Get " or " in lower
        clean_exprule = clean_exprule.replace(" OR ", " or ")
        ret = clean_exprule
    
        # Do we got at least one amino acid?
        res = re.search("[A-Za-z]", clean_exprule)
        if not res:
            core.handle_errors("No amino acid founded")
            ret = ""
    
        # Check all characters
        res = re.search(r"[^A-Za-z ,\(\)]", clean_exprule)
        if res:
            core.handle_errors(", bad character founded at position " +
                               str(res.start() + 1), 2, "Error")
            core.handle_errors(exprule, 2)
            to_print = ""
            for i in range(res.start()):
                to_print += " "
            core.handle_errors(to_print + "^", 2)
            ret = ""
    
        # No space except before and after "or"
        if ret != "":
            res = re.search(r"(?<!r) (?!o)", clean_exprule)
            if res:
                core.handle_errors(", bad space character founded at position " +
                                   str(res.start() + 1), 2, "Error")
                core.handle_errors(exprule, 2)
                to_print = ""
                for i in range(res.start()):
                    to_print += " "
                core.handle_errors(to_print + "^", 2)
                ret = ""
    
        # No letter after a letter (all UPPER except or)
        if ret != "":
            res = re.search(r"[A-Z][A-Z]", clean_exprule)
            if res:
                core.handle_errors(", too many character founded at position " +
                                   str(res.start() + 1), 2, "Error")
                core.handle_errors(exprule, 2)
                to_print = ""
                for i in range(res.start() + 1):
                    to_print += " "
                core.handle_errors(to_print + "^", 2)
                ret = ""
    
        # Bracket verif
        if ret != "":
            opening = False
            if "(" not in clean_exprule:
                core.handle_errors(", no opening bracket founded", 2, "Error")
                ret = ""
            else:
                for i, _ in enumerate(clean_exprule):
                    # Opening bracket
                    if clean_exprule[i] == "(":
                        # If we found another opening but no closing
                        if opening:
                            core.handle_errors(", opening bracket founded without "
                                               "closed one at position " +
                                               str(i + 1), 2, "Error")
                            core.handle_errors(exprule, 2)
                            to_print = ""
                            for _ in range(i):
                                to_print += " "
                            core.handle_errors(to_print + "^", 2)
                            ret = ""
                        else:
                            opening = True
                    # Closing bracket
                    if clean_exprule[i] == ")":
                        # Seems ok
                        if opening:
                            opening = False
                        else:
                            core.handle_errors(", closing bracket founded without "
                                               "opening before at position " +
                                               str(i + 1), 2, "Error")
                            core.handle_errors(exprule, 2)
                            to_print = ""
                            for _ in range(i):
                                to_print += " "
                            core.handle_errors(to_print + "^", 2)
                            ret = ""
            if opening is True:
                core.handle_errors(", an opened bracket was never closed", 2,
                                   "Error")
                ret = ""
    
        # comma verif: max two and in the same bracket system, and min one
        if ret != "":
            if clean_exprule.count(',') > 2:
                core.handle_errors(", too many ',' founded", 2, "Error")
                ret = ""
            elif clean_exprule.count(',') == 0:
                core.handle_errors(", no ',' founded", 2, "Error")
                ret = ""
            else:
                # comma always closed to a bracket
                res = re.search(r"(?<!\(),(?!\))", clean_exprule)
                if res:
                    core.handle_errors(", bad comma founded at position " +
                                       str(res.start() + 1), 2, "Error")
                    core.handle_errors(exprule, 2)
                    to_print = ""
                    for i in range(res.start()):
                        to_print += " "
                    core.handle_errors(to_print + "^", 2)
                    ret = ""
                # If two commas, they should be in the same bracket system
                comma_found = False
                closed_bracket = False
                for i, _ in enumerate(clean_exprule):
                    # First comma
                    if clean_exprule[i] == ",":
                        comma_found = True
                    # Closing bracket before the first comma
                    if comma_found and clean_exprule[i] == ")":
                        closed_bracket = True
                    # If we found another comma after the closed bracket
                    if comma_found and closed_bracket and clean_exprule[i] == ",":
                        core.handle_errors(", bad comma founded at position " +
                                           str(i + 1), 2, "Error")
                        core.handle_errors(exprule, 2)
                        to_print = ""
                        for _ in range(i):
                            to_print += " "
                        core.handle_errors(to_print + "^", 2)
                        ret = ""
        return ret
    
    def split_complex_rule(a_rule):
        """Split a complex rules containing ' or ' into simpler rules.
    
        :param a_rule: the rule to split
        :type a_rule: str
    
        :return: the simple rules
        :rtype: list(str)
        """
    
        # Returned cleaned rules
        clean_rules = []
        # Does it contains a " or "?
        tmp = a_rule.find(" or ")
        if tmp != -1:
            # Get the beginning
            beg = a_rule[:tmp - 1]
            # Get the end: it start at the next ")" after " or "
            endpos = tmp + a_rule[tmp:].find(")")
            # end is the next ")" and what's following
            end = a_rule[endpos:]
            # It might have a "," before
            if a_rule[endpos - 1] == ",":
                end = "," + end
            # first rule (left side of " or ")
            tmp_rule = beg + a_rule[tmp - 1] + end
            # clean this new rule
            clean_rules += split_complex_rule(tmp_rule)
            # second rule (right side of " or ")
            tmp_rule = beg + a_rule[tmp + 4:]
            # clean this new rule
            clean_rules += split_complex_rule(tmp_rule)
        # No " or " founded
        else:
            # add it to return
            clean_rules.append(a_rule)
        return clean_rules
    
    def add_rule(rules_list, a_rule):
        """Add (recursively) a rule to a list of rules.
    
        :param rules_list: the list of rules where to add
        :param a_rule: the rule to add
        :type rules_list: list(:py:class:`Rule`)
        :type a_rule: :py:class:`Rule`
    
        .. warning:: Modify `rules_list` :python:
        """
    
        # Should we add this rule to this rules_list?
        to_add = True
        # Does this rules already exist in list?
        for i in rules_list:
            # Same content except for sub-rules?
            if i.equ(a_rule):
                # This rules exist
                to_add = False
                # Check the sub-rules of the rule to add
                for j in a_rule.rules:
                    add_rule(i.rules, j)
            # Does a sub rules of this rules exist as a rule?
            else:
                # Tmp set for removing inside a for loop
                to_remove = set()
                # For each sub-rules of the rule to add
                for k in a_rule.rules:
                    # If this sub-rules is in the rule list, we need to
                    # change the rule according to the sub-rule
                    # that already exist
                    # Same content except for sub-rules?
                    if k.equ(i):
                        # This rules exist
                        to_add = False
                        # Flag this conflicting rules
                        to_remove.add(k)
                        # Edit the rule
                        a_rule.cut = not a_rule.cut
                        # Add it to the sub-rule that already exist
                        # without modifying it
                        add_rule(i.rules, a_rule)
                # Remove conflicting rules
                for k in to_remove:
                    a_rule.rules.remove(k)
        # This rules is new
        if to_add:
            rules_list.append(a_rule)
    
    def create_rules(all_rules):
        """Create proper rules for an enzyme from raw rules.
    
        :param all_rules: rules corresponding to the enzyme
        :type all_rules: list(:py:class:`Rule`)
    
        :return: rules ready to populate an :py:class:`~rpg.enzyme.Enzyme`
        :rtype: list(:py:class:`Rule`)
    
        This function handle ' or ' keywords, multiple brackets, sort the
        simples rules, create sub-rules, etc. The output is ready to be used
        to create an :py:class:`~rpg.enzyme.Enzyme`.
        """
    
        # All rules correctly added, ready to be printed
        correct_rules = []
        # handle " or " keyword and split it into several rules
        for tmp_rule, tmp_cut in list(all_rules.items()):
            # Remove this rule from main list
            all_rules.pop(tmp_rule)
            # Split this rule on " or "
            tmp_rules = split_complex_rule(tmp_rule)
            # Add all the cleaned rules corresponding
            for rul in tmp_rules:
                all_rules[rul] = tmp_cut
    
        # handle multiple "," in one bracket so we got only ONE "," per rule
        for tmp_rule, tmp_cut in list(all_rules.items()):
            if tmp_rule.count(",") > 1:
                # Remove this complex rule
                all_rules.pop(tmp_rule)
                # Modified beginning and ending of the rules to create
                # Beginning of the complex rule
                begpos = tmp_rule.find("(,")
                # Should be like ...('
                beg_cut = tmp_rule[:begpos + 2]
                # Should be like ...(
                beg_no_cut = tmp_rule[:begpos + 1]
                # Ending of the complex rule
                endpos = tmp_rule.find(",)")
                # Should be like ...')
                end_cut = tmp_rule[endpos:]
                # Should be like ...)
                end_no_cut = tmp_rule[endpos + 1:]
                # Add rules
                all_rules[beg_cut + tmp_rule[begpos + 2] + end_no_cut] = tmp_cut
                all_rules[beg_no_cut + tmp_rule[begpos + 2] + end_cut] = tmp_cut
    
        # Sort rules, smaller first
        all_rules_keys = list(all_rules.keys())
        all_rules_keys.sort(key=lambda s: len(s))
    
        exceptions = []
    
        # For each rules
        for rul in all_rules_keys:
            # Split each amino acid of the rule
            tmp_rule = rul.split("(")
            # Remove first i.e. ""
            tmp_rule.pop(0)
            # Remove residual ')' and find the cleaving zone
            for i, _ in enumerate(tmp_rule):
                tmp_rule[i] = tmp_rule[i].replace(")", "")
                if "," in tmp_rule[i]:
                    ind = i
    
            # Dict of pos/val of this rule
            dict_rule = {}
            for i, val in enumerate(tmp_rule):
                # None empty brackets
                if val != '':
                    dict_rule[i - ind] = val
            # All positions except the cleaving one
            dict_rule_no_main = dict_rule.copy()
            dict_rule_no_main.pop(0)
    
            # Create rule for the cleaving zone
            if dict_rule[0][0] == ",":
                # Before letter
                cleaving_zone = Rule(0, str(dict_rule[0][1]), all_rules[rul], 0)
            else:
                # After letter
                cleaving_zone = Rule(0, str(dict_rule[0][0]), all_rules[rul], 1)
    
            # Is there sub-rule?
            if len(dict_rule) > 1:
                # Reverse the cutting boolean
                cleaving_zone.cut = not cleaving_zone.cut
    
            # Upper-rule
            previous_rule = cleaving_zone
            # Only the deeper rule should have the correct boolean
            cleav = not all_rules[rul]
            # Exceptions to handle
            if cleaving_zone.cut and len(dict_rule) > 1:
                # Backup this exception
                exceptions.append(dict_rule)
            # No exception
            else:
                # Parse all after cleaving site first
                for i in reversed(sorted(dict_rule_no_main.keys())):
                    # Find the deepest rule: left if exist, otherwise, right
                    min_key = min(dict_rule_no_main.keys()) # Deep on left
                    max_key = max(dict_rule_no_main.keys()) # Deep on right
                    if min_key < 0: # Something left, change it (leftest)
                        if i == min_key:
                            cleav = not cleav
                    else: # Nothing left, change deepest rule at right
                        if i == max_key:
                            cleav = not cleav
                    # Create the rule
                    this_rule = Rule(i, dict_rule_no_main[i], cleav, -1)
                    # Add this rule to upper-rule
                    if not previous_rule.equ(this_rule) and\
                       not previous_rule.contains_any_level(this_rule):
                        previous_rule.rules.append(this_rule)
                    # Current rule is the new down-rule
                    previous_rule = this_rule
                # Create the corresponding rule
                add_rule(correct_rules, cleaving_zone)
    
        # Handling exceptions
        for dict_exc in exceptions:
            # Create rule for the cleaving zone
            if dict_exc[0][0] == ",":
                # Before letter
                cleaving_zone = Rule(0, str(dict_exc[0][1]), all_rules[rul], 0)
            else:
                # After letter
                cleaving_zone = Rule(0, str(dict_exc[0][0]), all_rules[rul], 1)
    
            # Default main_rule
            main_rule = cleaving_zone
            # Find correct main rule in correct_rule
            for corrule in correct_rules:
                # We are in the correct rule
                if corrule.equ(cleaving_zone):
                    main_rule = corrule
            # Remove the main rule from it
            dict_exc_no_main = dict_exc.copy()
            dict_exc_no_main.pop(0)
    
            # Finding missing rules
            missing = find_missing_rule(main_rule, dict_exc_no_main)
            # Missing rules already handle
            handle_exceptions = []
            # Add all missing rules
            while missing:
                # Get exceptions to handle, i.e. the deepest
                missing_rules = missing[max(missing, key=int)]
                # Handle the first one
                add_missing_rule(main_rule, dict_exc_no_main, missing_rules[0])
                # Indicate that it has been handle
                handle_exceptions.append(missing_rules[0])
                # Is there other exceptions?
                done = True
                # Scan all exceptions
                for mis in missing_rules:
                    # Is there at least one not handle?
                    if mis not in handle_exceptions:
                        done = False
                # There is more exceptions
                if not done:
                    # Finding missing rules
                    missing = find_missing_rule(main_rule, dict_exc_no_main)
                else:
                    # We are done
                    missing = None
    
        # Return all correct rules
        return correct_rules
    
    # NOT TESTED DIRECTLY
    def find_missing_rule(main_rule, dict_of_rule, depth=0):
        """Find all missing rules of an exception in a main rule
    
            :param main_rule: the main rule to search in
            :type main_rule: :py:class:`Rule`
            :param dict_of_rule: Raw rules of an exception
            :type dict_of_rule: Dict of pos/val
            :param depth: Depth of missing rule (default: `0`)
            :type depth: int
    
            :return: Missing rules and their positions and depth (key)
            :rtype: defaultdict(list)
        """
    
        # Dict of missing rules. Key is depth.
        ret = defaultdict(list)
        # For each rule of the exception
        for i in dict_of_rule:
            # Backup the dict
            dict_of_rule_small = dict_of_rule.copy()
            # Remove current rule
            dict_of_rule_small.pop(i)
            # Create a proper Rule from this rule
            tmp_rule = Rule(i, dict_of_rule[i], False, -1)
            # Is this Rule exist?
            founded = None
            # For all sub-rule of main_rule
            for j in main_rule.rules:
                if j.equ(tmp_rule):
                    # the Rule exist, backup the 'proper' one
                    founded = j
            # If the Rule exist, take the 'proper' one as main_rule
            if founded:
                # Search for remaining rules in sub-rules of current Rule
                tmp_ret = find_missing_rule(founded, dict_of_rule_small,
                                            depth + 1)
                # Update results
                ret.update(tmp_ret)
            # The Rule does not exist in current sub-rules
            else:
                # Add it to results
                ret[depth].append((i, dict_of_rule[i]))
    
        return ret
    
    # NOT TESTED DIRECTLY
    def add_missing_rule(main_rule, dict_of_rule, rule_to_add):
        """Add a rule of an exception in a main rule
    
            :param main_rule: the main rule to add in
            :type main_rule: :py:class:`Rule`
            :param dict_of_rule: Raw rules of an exception
            :type dict_of_rule: Dict of pos/val
            :param rule_to_add: The rule to add
            :type rule_to_add: Tuple(pos/val)
        """
        # Find reachable positions
        reachable_pos = find_rechable_pos(main_rule, dict_of_rule)
        # Smaller is deeper, add to deepest
        where_to_add = reachable_pos[min(reachable_pos)]
        # Create the Rule for this exception
        tmp_rule = Rule(rule_to_add[0], rule_to_add[1], False, -1)
        # Change upper rules to cut
        # Dangerous, works because .equ doesn't look cutting position.
        # I can't find an upper rule that should be at F with sub-rule at F
        # Something like '(D,)(G)': False, '(X)(D,)(G)': False
        # But it is none sens, but this actually BUG...
        # What I should do is checking if the rule already exist. If so, add
        # a new one. But I will end-up with two identical rules at the same
        # level saying T and F.
        where_to_add.cut = True
        # If it is not already there
        if not where_to_add.contains(tmp_rule) and not where_to_add.equ(tmp_rule):
            # Add manually the rule
            where_to_add.rules.append(tmp_rule)
    
    # NOT TESTED DIRECTLY
    def find_rechable_pos(main_rule, dict_of_rule):
        """Find all positions reachable on a main rule according to several
        rules.
    
            :param main_rule: the main rule to search in
            :type main_rule: :py:class:`Rule`
            :param dict_of_rule: Raw rules of an exception
            :type dict_of_rule: Dict of pos/val
    
            :return: Reachable :py:class:`Rule` and their depth (key)
            :rtype: dict()
        """
        ret = {}
        # We finish to parse all rules, we are as deeper as possible
        if not dict_of_rule:
            # This is a possibility
            ret[len(dict_of_rule)] = main_rule
        else:
            # For each rules
            for i in dict_of_rule:
                # Create the corresponding rule
                tmp_rule = Rule(i, dict_of_rule[i], False, -1)
                # Get the corresponding rule in main_rule
                new_main_rule = main_rule.contains(tmp_rule)
                # If it exist
                if new_main_rule:
                    # Copy of the dict
                    dict_of_rule_small = dict_of_rule.copy()
                    # Remove from the dict the rule that exist in main_rule
                    dict_of_rule_small.pop(i)
                    # Search with the smaller dict and the new main_rule
                    tmp_ret = find_rechable_pos(new_main_rule, dict_of_rule_small)
                    # Update results
                    ret.update(tmp_ret)
                # This rule does not exist in main_rule
                else:
                    # Add main_rule to possibilities
                    ret[len(dict_of_rule)] = main_rule
        return ret
    
    def handle_rule(seq, pos, a_rule, cut):
        """Recursive handling of a :py:class:`Rule` determining if a
        sequence must be cutted at a given position according to the rule.
    
        :param seq: sequence to test
        :param pos: position on the sequence
        :param a_rule: the rule
        :param cut: boolean telling if it must be cutted or not
        :type seq: str
        :type pos: int
        :type a_rule: :py:class:`Rule`
        :type cut: bool
    
        :return: `True` if sequence must be cutted
        :rtype: bool
        """
    
        # return of the function: should we cut this?
        ret = cut
        # Need to try because it can be the first/last letter
        # and we need to look before/after
        # After is handle by try, but before, because python like str[-x],
        # we need an if :-/
        try:
            # If the rule applies, i.e. the letter to watch is the good one
            if (pos + a_rule.index) >= 0 and \
                    seq[pos + a_rule.index] == a_rule.letter:
                ret = a_rule.cut
                # Handle the rules (exceptions)
                for rul in a_rule.rules:
                    # Apply the rule: do we need to cut?
                    ret = handle_rule(seq, pos, rul, ret)
        # Doesn't work: begin or end of sequence, don't change cut value
        except IndexError:
            pass
        return ret