18.15 Linear Congruences

A linear congruence is the modular analogue of a linear equation.

18.15 Linear Congruences

A linear congruence is the modular analogue of a linear equation. Instead of solving

ax = b

over the integers or real numbers, we solve

ax ≡ b (mod m)

over residue classes. This problem appears in modular division, cyclic scheduling, cryptography, Diophantine equations, calendar arithmetic, and many CRT-based algorithms.

The main difference from ordinary algebra is that division is not always valid. The coefficient a may or may not have an inverse modulo m. The Euclidean Algorithm determines exactly when solutions exist and how many there are.

Problem

Given integers:

a
b
m

solve:

ax ≡ b (mod m)

Find all residue classes x modulo m that satisfy the congruence.

Example:

3x ≡ 6 (mod 9)

Testing values gives:

x = 2, 5, 8

because:

3×2 = 6
3×5 = 15 ≡ 6 (mod 9)
3×8 = 24 ≡ 6 (mod 9)

There are three solutions modulo 9.

Convert to a Diophantine Equation

The congruence:

ax ≡ b (mod m)

means:

m | (ax - b)

So there exists an integer y such that:

ax - b = my

Rearrange:

ax - my = b

Equivalently:

ax + mY = b

where Y = -y.

Thus a linear congruence is the same as a linear Diophantine equation:

ax + mY = b

This immediately gives the solvability criterion.

Solvability Criterion

The equation:

ax ≡ b (mod m)

has a solution if and only if:

gcd(a,m) | b

Let:

g = gcd(a,m)

If g does not divide b, no solution exists.

Example with no solution:

6x ≡ 5 (mod 10)

Here:

gcd(6,10) = 2

but:

2 ∤ 5

So no solution exists.

Example with solutions:

6x ≡ 4 (mod 10)

Here:

gcd(6,10) = 2

and:

2 | 4

So solutions exist.

Reducing the Congruence

If:

g = gcd(a,m)

and g | b, divide all three values by g:

a' = a / g
b' = b / g
m' = m / g

Then solve:

a'x ≡ b' (mod m')

Now:

gcd(a',m') = 1

so a' has a modular inverse modulo m'.

The reduced solution is:

x ≡ b' × (a')⁻¹ (mod m')

This gives one residue class modulo m'. In the original modulus m, it expands to g solutions.

Example

Solve:

6x ≡ 4 (mod 10)

Compute:

g = gcd(6,10) = 2

Since:

2 | 4

solutions exist.

Divide by 2:

3x ≡ 2 (mod 5)

The inverse of 3 modulo 5 is 2, because:

3 × 2 ≡ 1 (mod 5)

Thus:

x ≡ 2 × 2 ≡ 4 (mod 5)

Modulo 10, this gives:

x = 4
x = 9

Check:

6 × 4 = 24 ≡ 4 (mod 10)
6 × 9 = 54 ≡ 4 (mod 10)

Both are valid.

General Solution

If:

ax ≡ b (mod m)

has solutions and:

g = gcd(a,m)

then there are exactly g solutions modulo m.

After solving the reduced congruence:

x ≡ x₀ (mod m/g)

the solutions modulo m are:

x = x₀ + k(m/g)

for:

k = 0, 1, ..., g-1

This gives all distinct solutions modulo m.

Implementation

First define Extended Euclid and modular inverse.

from math import gcd

def extended_gcd(a, b):
    if b == 0:
        return a, 1, 0

    g, x1, y1 = extended_gcd(b, a % b)

    x = y1
    y = x1 - (a // b) * y1

    return g, x, y

def mod_inverse(a, m):
    g, x, _ = extended_gcd(a, m)

    if g != 1:
        raise ValueError("inverse does not exist")

    return x % m

Now solve the linear congruence.

def solve_linear_congruence(a, b, m):
    g = gcd(a, m)

    if b % g != 0:
        return []

    a_reduced = a // g
    b_reduced = b // g
    m_reduced = m // g

    inv = mod_inverse(a_reduced, m_reduced)
    x0 = (b_reduced * inv) % m_reduced

    solutions = []

    for k in range(g):
        solutions.append((x0 + k * m_reduced) % m)

    return sorted(solutions)

Example:

print(solve_linear_congruence(6, 4, 10))

Output:

[4, 9]

Solving When gcd(a, m) = 1

The simplest case occurs when a and m are coprime.

Then:

gcd(a,m)=1

and there is exactly one solution modulo m.

The solution is:

x ≡ b × a⁻¹ (mod m)

Example:

7x ≡ 3 (mod 26)

Since:

gcd(7,26)=1

there is one solution.

The inverse of 7 modulo 26 is 15.

Therefore:

x ≡ 3 × 15 ≡ 45 ≡ 19 (mod 26)

Check:

7 × 19 = 133
133 mod 26 = 3

Multiple Solutions

Consider:

4x ≡ 8 (mod 12)

Here:

g = gcd(4,12)=4

and:

4 | 8

Divide:

x ≡ 2 (mod 3)

Modulo 12, this expands to:

2
5
8
11

Check:

4×2  = 8  ≡ 8 (mod 12)
4×5  = 20 ≡ 8 (mod 12)
4×8  = 32 ≡ 8 (mod 12)
4×11 = 44 ≡ 8 (mod 12)

There are exactly four solutions modulo 12.

No Solution Case

Consider:

4x ≡ 7 (mod 12)

Here:

gcd(4,12)=4

but:

4 ∤ 7

No integer x can satisfy the congruence.

This is the most important guard condition in implementation.

Normalizing Inputs

The same congruence may be written with negative or large coefficients.

Examples:

-3x ≡ 4 (mod 7)

Normalize:

-3 ≡ 4 (mod 7)

so solve:

4x ≡ 4 (mod 7)

Similarly:

20x ≡ 13 (mod 7)

becomes:

6x ≡ 6 (mod 7)

In code:

a %= m
b %= m

before solving. This usually simplifies the arithmetic.

Counting Solutions in a Range

Sometimes the task asks for solutions in an interval:

L ≤ x ≤ R

not just residue classes modulo m.

If the solution class is:

x ≡ x₀ (mod q)

then all solutions are:

x = x₀ + tq

Count integers t satisfying:

L ≤ x₀ + tq ≤ R

Implementation:

def ceil_div(a, b):
    return -((-a) // b)

def floor_div(a, b):
    return a // b

def count_in_range(x0, step, left, right):
    t_min = ceil_div(left - x0, step)
    t_max = floor_div(right - x0, step)

    return max(0, t_max - t_min + 1)

This pattern appears in scheduling, periodic event counting, and integer constraint problems.

Application: Modular Division

The congruence:

ax ≡ b (mod m)

is exactly the problem of dividing b by a modulo m.

When gcd(a,m)=1, modular division has a unique answer.

When gcd(a,m)>1, there may be no answer or multiple answers.

This is why modular division requires more care than modular multiplication.

Application: Cyclic Scheduling

Suppose a machine performs a task every m minutes. We want to find a time x such that:

ax ≡ b (mod m)

This can model repeated jumps around a cycle. If the jump size a and cycle size m share a common divisor, only some positions are reachable.

The same criterion applies:

gcd(a,m) | b

Reachability in a cycle is a linear congruence.

Common Mistakes

Dividing Without Checking gcd

This is wrong:

6x ≡ 4 (mod 10)

Directly dividing by 6 is invalid because 6 has no inverse modulo 10.

Returning Only One Solution

If gcd(a,m)>1, there may be multiple solutions modulo m.

Return all residue classes when the problem asks for all solutions.

Forgetting the No-Solution Case

If gcd(a,m) does not divide b, there is no solution.

Mixing Moduli

After reduction, the first solution is modulo:

m/g

The expanded solution set belongs modulo:

m

Keep these two moduli separate.

Mishandling Negative Inputs

Normalize coefficients and remainders before solving, or make sure your language's modulo behavior is well understood.

Testing

Basic tests:

def test_linear_congruence():
    assert solve_linear_congruence(6, 4, 10) == [4, 9]
    assert solve_linear_congruence(4, 8, 12) == [2, 5, 8, 11]
    assert solve_linear_congruence(4, 7, 12) == []
    assert solve_linear_congruence(7, 3, 26) == [19]

A property test can verify each returned solution:

def test_solutions_are_valid():
    cases = [
        (6, 4, 10),
        (4, 8, 12),
        (7, 3, 26),
        (15, 10, 35),
    ]

    for a, b, m in cases:
        for x in solve_linear_congruence(a, b, m):
            assert (a * x - b) % m == 0

Also verify completeness for small moduli by brute force:

def brute_solutions(a, b, m):
    return [x for x in range(m) if (a * x - b) % m == 0]

def test_against_brute_force():
    for m in range(1, 50):
        for a in range(-50, 51):
            for b in range(-50, 51):
                assert solve_linear_congruence(a, b, m) == brute_solutions(a, b, m)

This catches edge cases around zero, repeated solutions, and negative inputs.

Takeaway

A linear congruence ax ≡ b (mod m) is solvable exactly when gcd(a,m) divides b. After dividing by this gcd, the reduced coefficient is invertible, yielding one solution modulo m/g. Expanding that solution gives exactly g solutions modulo m. This framework turns modular division, cyclic reachability, scheduling constraints, and many Diophantine problems into a small, reliable algorithm built on the Extended Euclidean Algorithm.