def helper_search(mass_min, mass_max, formula_mass, formula, last_index, delta, restrict):
    """
    This function is called when the formula_mass is less than the mass_max
    It attempts to add a new element and
        if still in the mass tolerance window and complies with the first rule
            calls itself recursively
    :param mass_min: Minimum mass accepted for solution
    :param mass_max: Maximum mass accepted for solution
    :param formula_mass: The mass of the formula now
    :param formula: The formula now
    :param last_index: The last element added
    :param delta: Computation error allowed
    :return: not explicit, it adds to global list of formulas all 7rules complying formulas
    """
    for index, element in enumerate(formula):
        if index >= last_index:
            new_formula_mass = formula_mass + element['freqisotope']['mass']
            if mass_max - new_formula_mass >= -delta:
                new_formula = formula.copy()
                new_formula[element] += 1
                if new_formula_mass - mass_min >= -delta:
                    # formula in tolerance interval, add it to solutions
                    if filter_formula(new_formula, restrict):
                        formulas.append(new_formula)
                else:
                    if rule1(new_formula, restrict):
                        # still some mass left, keep searching
                        helper_search(mass_min, mass_max, new_formula_mass, new_formula, index, delta, restrict)
def search(mass, tolerance, delta, restrict):
    """
    :param mass: the formula mass
    :param tolerance: tolerance to accommodate equipment errors
    :param delta: computation error allowed
    :param restrict: True indicates CHNOPS_restricted
    :return: a list of candidate formulas
    """
    global formulas
    formulas = []
    if restrict:
        formula = {element: 0 for element in CHNOPS}
        # order is important, need to be decreasing
        element_list = [SULFUR, PHOSPHORUS, OXYGEN, NITROGEN, CARBON, HYDROGEN]
    else:
        formula = {element: 0 for element in elements}
        element_list = elements

    # scale the input data
    mass *= scalar
    tolerance *= scalar

    # this algorithm work with int masses
    mass_total = int(math.ceil(mass) + 1)


    # n is the enumber of decision points
    # for this unbounded implementation: n <= number_elements * mass
    # but early termination is implemented
    n = len(formula) * mass_total

    # initialise row 0
    odd_row = []
    even_row = [(0, formula) for i in range(0, mass_total)]

    i = 0
    last_index = -1
    repetitions = 0
    terminate = False

    while i < n and not terminate:
        i += 1
        if i % 2 == 0:
            (even_row, last_index, repetitions, terminate) = helper_search(odd_row, mass_total, element_list,
                                                                           last_index, repetitions, formula, terminate, restrict)
            (candidate_mass, candidate) = even_row[-1]
            if candidate_mass - mass > tolerance + delta and len(even_row) > 1:
                (candidate_mass, candidate) = even_row[-2]
                candidate_mass = get_formula_mass(candidate)
            if candidate_mass + tolerance - mass >= -delta and mass + tolerance - candidate_mass >= -delta and not candidate in formulas and the_7rules.filter_formula(
                    candidate, restrict):
                formulas.append(candidate)
        else:
            (odd_row, last_index, repetitions, terminate) = helper_search(even_row, mass_total, element_list,
                                                                          last_index, repetitions, formula, terminate, restrict)
            (candidate_mass, candidate) = odd_row[-1]
            if candidate_mass - mass > tolerance + delta and len(odd_row) > 1:
                (candidate_mass, candidate) = odd_row[-2]
            if candidate_mass + tolerance - mass >= -delta and mass + tolerance - candidate_mass >= -delta and not candidate in formulas and the_7rules.filter_formula(
                    candidate, restrict):
                formulas.append(candidate)
    return formulas