def prob_solution(a, x, b, y, c, z):
    
    num_pp = len(safe_primes)
        
    mr = MontgomeryReduction()
    plus = True
    minus = True
    
    for i in range(num_ps_tests):
        #using safe primes ensures that the order of a and b modulo m is high, thus ensuring the probability of a^x+/-b^y=c^z is low, on the order of 1/m
        m = safe_primes[randrange(num_pp)]
        
        mr.set_modulus(m)
        
        ra, rb, rc = mr.convert(a), mr.convert(b), mr.convert(c)
        rax, rby, rcz = mr.exp(ra, x), mr.exp(rb, y), mr.exp(rc, z)
        
        if plus and (rax + rby) % m != rcz:
            plus = False
            s = '-'
            
            if not minus:
                break
        
        if minus and (rax - rby) % m != rcz:
            minus = False
            s = '+'
            
            if not plus:
                break
    else:
        
        return [True, s]
    
    return [False, s]
def beals_solver(a, b, c, z):
    """finds x and y s.t. a^x +/- b^y = c^z
    
    a, b, c mutually coprime and a is a generator modulo b
    b should be prime, but we'll accept (very) strong probable primes to make finding generators modulo b easier to find, b will be a safe prime
    c must be positive"""
    
    x = 0
    y = 0
    
    #take ceiling for rounding errors (possible underflow)
    lrab = math.ceil(math.log(a, b))
    max_yc = z * math.ceil(math.log(c, b))
    
    b_fact = all_fact[b]
    #for modulus b^k for k > 1, we want the discrete log to be over a "totient" of b, since the possible values of x satisfying g^x=h (mod b^k) will range over 0 to b.  Giving the factorization of b as the factorization of the totient allows us to use the speedup afforded by the Pohlig-Hellman algorithm.  The second element is None because we don't need the factorization of the inverse totient of b
    bt = [b, None, b_fact[1]]
    
    m = b
    lt = 1
    t = b_fact[0]
    mr = MontgomeryReduction(m, b_fact)
    
    n = 0
    ps = [False, None]
    
    while x < max_pow and y < max_pow and not ps[0]:       
        
        x = get_x(a, x, c, z, mr, t, lt)
        y = get_y(a, x, c, z, b, lrab, max_yc)
        
        lt = t
        m *= b
        t *= b
        mr.set_modulus(m, bt)
        
        ps = prob_solution(a, x, b, y, c, z)
        n += 1
        
        #print("{!s}: ({!s}, {!s})".format(n, x, y))
        
    return [x, y, ps]
def get_y(a, x, c, z, b, lrab, max_yc):
    
    y_res_mod = []
    
    max_ya = x * lrab
    max_y = max(max_ya, max_yc)
    
    p = 1
    mr = MontgomeryReduction()
    
    for i, pprime in enumerate(prob_primes):
        if is_gen_n_k(b, pprime, all_fact[pprime]): 
            m = pprime**2
            
            #squaring ensures totient is divisible by prime
            mr.set_modulus(m, prime_sq_fact[i])
                    
            ra, rc, rb = mr.convert(a), mr.convert(c), mr.convert(b)
            rax, rcz = mr.exp(ra, x), mr.exp(rc, z)
            
            rd = (rax - rcz) % m
            
            if is_coprime(rd, m):                
                y_sp = mr.dlog_mod(rb, rd, [pprime, 1])
                y_res_mod.append([y_sp, pprime])
                
                p *= pprime
                
                if p > max_y:
                    break
    else:
        ipdb.set_trace()
        print("Not enough primes to get y")
        #raise Error("Not enough primes to get y")
    
    return crt(y_res_mod)