A general linear nonhomogeneous diophantine equation is of the form
a0x0 + a1x1 + ⋯ + anxn = c
where the coefficients ai and the constant c are integers. The components xi of its solutions are also expected to be integers.
The possible values of the left hand side of the equation form a subgroup of the additive group of integers. It consists of the multiples of its least positive element d, which is the greatest common divisor of the coefficients ai.
Hence the equation is solvable if and only if c is divisible by d. Moreover, if (x0, …, xn) is a solution with the right hand side d, then a solution for a multiple c = kd is given by (kx0, …, kxn).
The difference of two solutions of the equation is a solution of the homogeneous diophantine equation whose right hand side is zero. Conversely, the sum of a particular solution of the equation and a solution of the homogeneous equation is a solution of the nonhomogeneous equation.
The solutions of the homogeneous equation form a subgroup of the group Zn + 1 of all families (x0, …, xn) of integers. Assuming the coefficients ai non-zero, as we may, the group of solutions is a free group of rank n. Hence its elements can be represented by n parameters taking integral values.
For the complete solution of the nonhomogeneous linear diophantine equation it suffices to find
- the greatest common divisor d of the coefficients ai,
- a particular solution of the equation with the right hand side d,
- a general solution (l0, …, ln) of the homogeneous equation where the li are linear combinations of n parameters t0, …, tn − 1 with integer coefficients.
These can be computed recursively as follows. For any integers xi we have
a0x0 + a1x1 + ⋯ + anxn = a0x0 + gy,
where g = gcd (a1, …, an) and y is an integer. Then d = gcd (a0, g) and by means of the extended Euclidean algorithm we can find integers x and y such that
a0x + gy = d.
Now, if a1y1 + ⋯ + anyn = g is a particular solution of the equation with n variables, we obtain a new particular solution for n + 1 variables
a0x + a1y1y + ⋯ + anyny = d.
If (x0, x1, …, xn) is a solution of the homogeneous equation, then
a0x0 + gy = 0
for some integer y. This is equivalent to
ax0 + by = 0,
where a = a0/d and b = g/d are relatively prime. Hence x0 = bt0 for some integer t0. Conversely, if this is true, then y = − at0 satisfies the equation, and we obtain
a0bt0 − a(a1y1 + ⋯ + anyn)t0 = 0,
so (bt0, − ay1t0, …, − aynt0) is a solution of the homogeneous equation.
The difference of any two solutions of the homogeneous equation having the same first component bt0 is a solution of the homogeneneous equation of n variables
a1x1 + … + anxn = 0.
Hence, if (l1, …, ln) is a general solution of this equation in terms of the parameters t1, …, tn − 1,
(bt0, l1 − ay1t0, …, ln − aynt0)
is a general solution of the homogeneous equation of n + 1 variables.
This recursive procedure can be coded as follows.
def _linear_diop(coeffs, params):
"""Helper function for _diop_linear.
Recursively solve a linear diophantine equation.
Parameters
==========
coeffs: Coefficients of a homogeneous linear equation.
List of n + 1 non-zero integers.
params: Parameters for the general homogeneous solution.
List of n symbols.
Returns
=======
d : int
GCD of the coefficients.
part : list of int
Particular solution of the equation with right hand side ``d``.
gen : list of Expr
General solution of the homogeneous equation in terms of
``params``.
"""
if len(coeffs) == 1:
return abs(coeffs[0]), [sign(coeffs[0])], [0]
g, part, gen = _linear_diop(coeffs[1:], params[1:])
x, y, d = extended_euclid(coeffs[0], g)
a, b = coeffs[0]//d, g//d
t = params[0]
return (d, [x] + [c*y for c in part],
[b*t] + [-a*c*t + f for c, f in zip(part, gen)])
SymPy's _diop_linear may then be given the following form.
def _diop_linear(var, coeff, param):
if not var:
return ()
coeffs = [coeff[v] for v in var]
params = list(symbols(param.name +':' + str(len(var) - 1), integer=True))
d, part, gen = _linear_diop(coeffs, params)
if Integer(1) not in coeff:
return tuple(gen)
c = -coeff[Integer(1)]
if c%d != 0:
return tuple([None]*len(var))
c //= d
return tuple([c*x + f for x, f in zip(part, gen)])