Python Linear Diophantine Equation

Question:

I am doing the homework for class and I was doing some research. I found the example equation for finding the diophantine equation.
The assignment is given when there is gcd(a, b) == 1, then there is a diophantine equation where
ax + by = 1.

I understood how this equation finds the value for x and y but I am not sure how this function is working. I got the part where there is divmod and recursive method inside the function, but I do not know when does it stop.

sorry for complex and ambiguous questions, but I wanna know how this equation works. I mean, how this equation ends.
For example, when there is given number 43 and 486 then gcd(43, 486) == 1.
That mean there’s a equation where 43x + 486y = 1. the solution said 43(-113) + 486(10) = 1. And the function solve got the x and y value as well.

I was trying to follow through the code and see how it process, but I do not understand else: part.

def isolve(a, b):
    quotient, remainder = divmod(a, b)
    if remainder == 0:
        return [0, 1 / b]
    else:
        sol = isolve(b, remainder)
        x = sol[0]
        y = sol[1]
        return [y, x - quotient * y]
Asked By: Chris

||

Answers:

I am not sure I understand exactly what you are after, but let’s consider the following modified code:

def simple_linear_diophantine_r(a, b):
    q, r = divmod(a, b)
    if r == 0:
        return (0, b)
    else:
        x, y = simple_linear_diophantine_r(b, r)
        return (y, x - q * y)


a, b = 43, 486
x, y = simple_linear_diophantine_r(a, b)
print(f'({a}) * ({x}) + ({b}) * ({y}) == {a * x + b * y}')
# (43) * (-113) + (486) * (10) == 1

which works as expected. Compared to the original code, I have rewritten the math in a way that only int-safe operations are used (no float division is used).
Additionally, I have renamed the function itself and some of the internal variables.

So far, this is more or less what you knew already.

Now, one way to understand what is happening is to use a Python’s debugger.

A simpler to illustrate approach is to place some print() calls in strategic places:

def simple_linear_diophantine_r(a, b):
    q, r = divmod(a, b)
    print(f'a={a}, b={b}, q={q}, r={r}')  # DEBUG
    if r == 0:
        return (0, b)
    else:
        x, y = simple_linear_diophantine_r(b, r)
        print(f'x={x}, y={y}')  # DEBUG
        return (y, x - q * y)


a, b = 43, 486
x, y = simple_linear_diophantine_r(a, b)
print(f'(a={a}) * (x={x}) + (b={b}) * (y={y}) == {a * x + b * y}')
# a=43, b=486, q=0, r=43
# a=486, b=43, q=11, r=13
# a=43, b=13, q=3, r=4
# a=13, b=4, q=3, r=1
# a=4, b=1, q=4, r=0
# x=0, y=1, q=3
# x=1, y=-3, q=3
# x=-3, y=10, q=11
# x=10, y=-113, q=0
# -113 10
# (a=43) * (x=-113) + (b=486) * (y=10) == 1

and now you can more easily follow what is happening at each function call.


The same result can be obtained with an iterative function:

def simple_linear_diophantine_i(a, b, debug=False):
    qs = []
    while True:
        q, r = divmod(a, b)
        if debug:
            print(f'a={a}, b={b}, q={q}, r={r}')
        a = b
        b = r
        if r != 0:
            qs.append(q)
        else:
            break
    x, y = b, a
    for q in qs[::-1]:
        if debug:
            print(f'x={x}, y={y}, q={q}')
        x, y = y, x - q * y
    return x, y


a, b = 43, 486
x, y = simple_linear_diophantine_i(a, b, True)
print(x, y)
# a=43, b=486, q=0, r=43
# a=486, b=43, q=11, r=13
# a=43, b=13, q=3, r=4
# a=13, b=4, q=3, r=1
# a=4, b=1, q=4, r=0
# x=0, y=1, q=3
# x=1, y=-3, q=3
# x=-3, y=10, q=11
# x=10, y=-113, q=0
# (a=43) * (x=-113) + (b=486) * (y=10) == 1

with somewhat similar timing:

a = 123464357645765475246432152143432154321543256432654367547658641353276548767132465498760908567542543215
b = 65432654786531234532634712432

funcs = simple_linear_diophantine_r, simple_linear_diophantine_i
base = funcs[0](a, b)
print(base)
# (-14645224987639174972552062305, 27633958939547408859252562549330013276609719423270432707256961858787144635978588031424076994432005543)
for func in funcs:
    res = func(a, b)
    is_good = base == res
    timed = %timeit -n 128 -r 128 -oq func(a, b)
    timing = timed.best * 1e6
    print(f"{func.__name__:>24}  {is_good}  {timing:10.3f} µs")
# simple_linear_diophantine_r  True      25.855 µs
# simple_linear_diophantine_i  True      22.439 µs
Answered By: norok2

I also wrote a Linear Diophantine program that has stats and is faster than egcd python programs iv’e used so wanted to include it here for others interested in programs that solve these:

def llinear_diophantinex(a, b, divmodx=1, x=1, y=0, withstats=False):
 origa, origb = a, b
 r=a 
 q = a//b
 prevq=1 
 if withstats == True:
   print(f"a = {a}, b = {b}, q = {q}, r = {r}")  
 while r != 0: 
      prevr = r 
      a,r,b = b, b, r  
      q,r = divmod(a,b)
      x, y = y, x - q * y
      if withstats == True:
        print(f"a = {a}, b = {b}, q = {q}, r = {r}, x = {x}, y = {y}") 
 y = 1 - origb*x // origa - 1
 x,y=y,x
 modx = (-abs(x)*divmodx)%origb
 if withstats == True:
   print(f"x = {x}, y = {y}, modx = {modx}")
 return x, y, modx
In [5410]: llinear_diophantinex(272,1009, withstats=True)                                                                       
a = 272, b = 1009, q = 0, r = 272
a = 1009, b = 272, q = 3, r = 193, x = 0, y = 1
a = 272, b = 193, q = 1, r = 79, x = 1, y = -1
a = 193, b = 79, q = 2, r = 35, x = -1, y = 3
a = 79, b = 35, q = 2, r = 9, x = 3, y = -7
a = 35, b = 9, q = 3, r = 8, x = -7, y = 24
a = 9, b = 8, q = 1, r = 1, x = 24, y = -31
a = 8, b = 1, q = 8, r = 0, x = -31, y = 272
x = 115, y = -31, modx = 894
Out[5410]: (115, -31, 894)
Answered By: oppressionslayer
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.