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)
            (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:
                formulas.append(candidate)
        else:
            (odd_row, last_index, repetitions, terminate) = helper_search(even_row, mass_total, element_list,
                                                                          last_index, repetitions, formula, terminate)
            (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:
                formulas.append(candidate)
    return formulas
def find_all(mass_in, mass, i, c, a, rt):
    if i == -1:
        c[0] = mass / a[0]
        formula = {elem: c[i] for i, elem, in enumerate(sorted_CHNOPS())}
        formula_mass = get_formula_mass(formula)
        if abs(formula_mass - mass_in) < 1:
            formulas.append(formula)
    else:
        lcm = a[0] * a[i] / fractions.gcd(a[0],a[i])
        l = lcm / a[i]
        for j in range(0, l):
            c[i] = j
            m = mass - j * a[i]
            r = m % a[0]
            lbound = rt[i-1][r]
            while m >= lbound:
                find_all(mass_in, m, i-1, c, a, rt)
                m = m - lcm
                c[i] = c[i] + l
def rule1(formula, restrict):
    """
    restrictions for the number of elements, from table 1 in 7 golden rules paper
    using the largest from the two sets, rather than a consecvent set
    :param formula: a formula
    :param restrict: True if the formula is within restrictions
    :return: true if it passes the test
    """
    mass = get_formula_mass(formula)
    if mass < 500:
        if element_numbers_restriction(formula, 39, 72, 20, 20, 9, 10, 16, 10, 5, restrict):
            return False
    elif mass < 1000:
        if element_numbers_restriction(formula, 78, 126, 25, 27, 9, 14, 34, 12, 8, restrict):
            return False
    elif mass < 2000:
        if element_numbers_restriction(formula, 156, 236, 32, 63, 9, 12, 48, 12, 10, restrict):
            return False
    elif mass < 3000:
        if element_numbers_restriction(formula, 162, 208, 48, 78, 6, 9, 16, 11, 8, restrict):
            return False
    return True