Gaussian Elimination (Solving Linear Systems) — Practice Tasks¶
All tasks must be solved in Go, Java, and Python. Each task ships with starter code or a precise spec in all three languages. Implement the elimination logic and validate against the evaluation criteria. Always verify the residual —
A·x ≈ b(reals, within a tolerance) orA·x ≡ b mod p(fields) — on small inputs before trusting the solver.
Beginner Tasks (5)¶
Task 1 — Forward elimination to row-echelon form¶
Problem. Implement forward_eliminate(m) that takes an n × (n+1) augmented matrix of reals and reduces it to row-echelon form using partial pivoting (swap the largest-magnitude entry in each column into the pivot position). Do not back-substitute yet.
Input / Output spec. - Read n, then n rows of n+1 floats (the augmented matrix [A | b]). - Print the row-echelon matrix, one row per line, values space-separated, 4 decimals.
Constraints. - 1 ≤ n ≤ 100. Treat |pivot| < 1e-12 as singular and print SINGULAR.
Hint. For each column col, find argmax_{r≥col} |m[r][col]|, swap it to row col, then for r > col do m[r] -= (m[r][col]/m[col][col]) · m[col].
Starter — Go.
package main
import (
"bufio"
"fmt"
"math"
"os"
)
func forwardEliminate(m [][]float64) bool {
n := len(m)
for col := 0; col < n; col++ {
// TODO: partial pivot, swap, eliminate below
_ = col
_ = math.Abs
}
return true
}
func main() {
reader := bufio.NewReader(os.Stdin)
var n int
fmt.Fscan(reader, &n)
m := make([][]float64, n)
for i := range m {
m[i] = make([]float64, n+1)
for j := range m[i] {
fmt.Fscan(reader, &m[i][j])
}
}
if !forwardEliminate(m) {
fmt.Println("SINGULAR")
return
}
for _, row := range m {
for j, v := range row {
if j > 0 {
fmt.Print(" ")
}
fmt.Printf("%.4f", v)
}
fmt.Println()
}
}
Starter — Java.
import java.util.*;
public class Task1 {
static boolean forwardEliminate(double[][] m) {
int n = m.length;
for (int col = 0; col < n; col++) {
// TODO: partial pivot, swap, eliminate below
}
return true;
}
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
double[][] m = new double[n][n + 1];
for (int i = 0; i < n; i++)
for (int j = 0; j <= n; j++) m[i][j] = sc.nextDouble();
if (!forwardEliminate(m)) { System.out.println("SINGULAR"); return; }
StringBuilder sb = new StringBuilder();
for (double[] row : m) {
for (int j = 0; j <= n; j++) {
if (j > 0) sb.append(' ');
sb.append(String.format("%.4f", row[j]));
}
sb.append('\n');
}
System.out.print(sb);
}
}
Starter — Python.
import sys
EPS = 1e-12
def forward_eliminate(m):
n = len(m)
for col in range(n):
# TODO: partial pivot, swap, eliminate below
pass
return True
def main():
data = iter(sys.stdin.read().split())
n = int(next(data))
m = [[float(next(data)) for _ in range(n + 1)] for _ in range(n)]
if not forward_eliminate(m):
print("SINGULAR")
return
print("\n".join(" ".join(f"{v:.4f}" for v in row) for row in m))
if __name__ == "__main__":
main()
Evaluation criteria. - Correct partial pivoting (largest magnitude swapped in). - Zeros below every pivot; upper-triangular result. - SINGULAR reported when a column has no usable pivot.
Task 2 — Back substitution¶
Problem. Given an n × (n+1) matrix already in row-echelon form (upper triangular, non-zero diagonal), implement back_substitute(m) returning the solution vector x.
Input / Output spec. - Read n, then the REF augmented matrix. - Print x[0] … x[n-1] space-separated, 4 decimals.
Constraints. 1 ≤ n ≤ 100, all diagonal entries non-zero.
Hint. Loop i from n-1 down to 0: x[i] = (m[i][n] - Σ_{c>i} m[i][c]·x[c]) / m[i][i].
Evaluation criteria. - Matches hand calculation on the 3×3 example (x = [2,3,-1]). - O(n²). - Combined with Task 1, solves any non-singular real system.
Task 3 — Full real solver with residual check¶
Problem. Combine Tasks 1 and 2 into solve(A, b) that returns x or None/null if singular. Then implement residual(A, x, b) returning max_i |Σ_j A[i][j]·x[j] − b[i]| and assert it is < 1e-9 for solvable systems.
Input / Output spec. - Read n, A (n × n), b. - Print x (4 decimals) and the residual.
Constraints. 1 ≤ n ≤ 200, well-conditioned A.
Hint. Augment, forward-eliminate with partial pivoting, back-substitute, then compute the residual as a correctness oracle.
Evaluation criteria. - Residual < 1e-9 on well-conditioned inputs. - Correctly reports singular systems. - Same answer across Go, Java, Python.
Task 4 — Mod-p solver (unique solution)¶
Problem. Implement solve_mod_p(A, b) over Z/pZ with p = 10^9 + 7, returning the unique solution mod p or reporting "no unique solution". Divide by pivots using the modular inverse (pivot^{p-2} mod p).
Input / Output spec. - Read n, A, b (integers, may be negative). - Print x[0] … x[n-1] mod p, or NO_UNIQUE.
Constraints. 1 ≤ n ≤ 200, entries fit in int64.
Hint. Normalize entries to [0, p) first. Any non-zero pivot works (no magnitude pivoting). Reduce after every multiply to avoid overflow.
Worked check. For A = [[2,1],[1,3]], b = [5,10]: the exact rational solution is x = (1, 3); over Z/pZ you should get [1, 3].
Evaluation criteria. - Exact result mod p (verify A·x ≡ b mod p). - NO_UNIQUE when the matrix is singular mod p. - No overflow (64-bit products before % p).
Task 5 — GF(2) solver with integer-bitset rows¶
Problem. Implement solve_gf2(rows, n) where each row is an (n+1)-bit integer (bit c = coefficient of x_c, bit n = right-hand side). Return any satisfying 0/1 assignment, or "no solution". Use XOR for row operations.
Input / Output spec. - Read n, numRows, then numRows integers (each a packed row). - Print the assignment x[0] … x[n-1], or NO_SOLUTION.
Constraints. 1 ≤ n ≤ 60 (fits in one 64-bit word), numRows ≤ 10^4.
Hint. For each column, find a row with a 1 there, XOR it into every other row with a 1 there, record the pivot row. After elimination, an all-zero coefficient row with b = 1 means no solution; free variables default to 0.
Evaluation criteria. - Correct XOR elimination and pivot tracking. - Detects inconsistency (0 = 1). - Matches a brute-force 2^n enumeration for small n.
Intermediate Tasks (5)¶
Task 6 — Classify: no solution / unique / infinitely many (reals)¶
Problem. Given an m × n real system [A | b], classify it as NO_SOLUTION, UNIQUE, or INFINITE, and output the rank and the number of free variables. Use a tolerance eps for zero tests.
Input / Output spec. - Read m, n, the m × n matrix A, then b. - Print the classification, rank, and free = n − rank (only meaningful when consistent).
Constraints. 1 ≤ m, n ≤ 100.
Hint. Track where[col] (the pivot row of each column). After RREF, scan for an inconsistent row (all coeffs ~0 but b not). Then rank = #pivots; unique iff rank == n; else infinite.
Reference oracle (Python) — compare against numpy.
import numpy as np
def oracle(A, b):
A = np.array(A, float); b = np.array(b, float)
ra = np.linalg.matrix_rank(A)
rab = np.linalg.matrix_rank(np.column_stack([A, b]))
if ra != rab:
return "NO_SOLUTION"
return "UNIQUE" if ra == A.shape[1] else "INFINITE"
Evaluation criteria. - Matches the Rouché–Capelli verdict on random and crafted systems. - Correct rank and free-variable count. - Agrees with the numpy oracle.
Task 7 — Matrix inverse via Gauss-Jordan¶
Problem. Implement inverse(A) over the reals returning A⁻¹, or None if singular. Augment A with the identity [A | I] and row-reduce to [I | A⁻¹] (full RREF with partial pivoting).
Input / Output spec. - Read n, then A (n × n). - Print A⁻¹ (4 decimals) or SINGULAR.
Constraints. 1 ≤ n ≤ 100, well-conditioned A.
Hint. Build the n × 2n augmented matrix. Reduce each pivot to 1 and clear its column everywhere. The right half becomes the inverse. Verify A · A⁻¹ ≈ I.
Evaluation criteria. - A · A⁻¹ is the identity within 1e-9. - SINGULAR for non-invertible A. - O(n³).
Task 8 — Mod-p inverse via Gauss-Jordan¶
Problem. Implement inverse_mod_p(A) over Z/pZ (prime p = 10^9 + 7), returning A⁻¹ mod p or "singular". Same Gauss-Jordan structure as Task 7, but exact, dividing by the modular inverse.
Constraints. 1 ≤ n ≤ 150.
Hint. Augment [A | I] mod p, eliminate using the pivot's modular inverse, read the right half. Verify A · A⁻¹ ≡ I mod p.
Evaluation criteria. - A · A⁻¹ ≡ I (mod p) exactly. - Reports singular when det(A) ≡ 0 mod p. - No overflow.
Task 9 — Determinant via elimination¶
Problem. Implement determinant(A) for an n × n real matrix as the signed product of pivots: det = (−1)^{#swaps} · ∏ diagonal pivots after forward elimination with partial pivoting. (See sibling 18-matrix-determinant.)
Constraints. 1 ≤ n ≤ 200. Return 0.0 if singular.
Hint. Track the swap parity. Each row swap flips the sign; add-multiple operations leave the determinant unchanged. After triangularization, multiply the diagonal by the sign.
Reference oracle (Python).
Evaluation criteria. - Matches numpy det within a relative tolerance on random matrices. - Correct sign (test on a known swap-inducing matrix). - det = 0 for singular matrices.
Task 10 — Solve over Z/pZ with free variables (general solution)¶
Problem. Given an m × n system over Z/pZ that may be under-determined, return either NO_SOLUTION or a particular solution (free variables set to 0) together with a basis of the homogeneous null space (one vector per free variable). Count solutions as p^{n−rank}.
Constraints. 1 ≤ m, n ≤ 100, prime p = 998244353.
Hint. RREF tracking where[col]. Particular solution: set free vars to 0, read each basic var from its pivot row. Null-space basis: for each free column f, set x_f = 1, others free to 0, and solve for the basic vars.
Evaluation criteria. - NO_SOLUTION when inconsistent. - Particular solution satisfies A·x ≡ b; each null-space vector satisfies A·v ≡ 0. - Reports p^{n−rank} as the solution count.
Advanced Tasks (5)¶
Task 11 — CRT-combined exact rational solve¶
Problem. Solve A·x = b for an integer matrix exactly over the rationals. Solve mod two coprime primes, CRT-combine the residues, and use rational reconstruction (continued fractions, sibling 16) to recover each x_i = num/den. Output reduced fractions.
Constraints. 1 ≤ n ≤ 40, integer entries with |·| ≤ 1000; answer numerators/denominators bounded so reconstruction succeeds.
Hint. Run solve_mod_p under p₁ = 10^9+7 and p₂ = 998244353, CRT to get each coordinate mod p₁·p₂, then reconstruct the fraction with the half-GCD / continued-fraction bound |num|, |den| < √(P/2).
Two-prime CRT (Python).
def crt2(r1, p1, r2, p2):
inv = pow(p1, -1, p2)
t = ((r2 - r1) * inv) % p2
return r1 + p1 * t # in [0, p1*p2)
Evaluation criteria. - Recovers exact fractions for systems with a known rational solution. - Each modular run agrees with (true) mod pᵢ. - Reconstructed fractions are in lowest terms and satisfy A·x = b exactly.
Task 12 — Fraction-free (Bareiss) integer determinant¶
Problem. Implement Bareiss fraction-free elimination to compute the exact integer determinant of an n × n integer matrix without floating point and without fractions. Compare against the modular determinant under a large prime.
Constraints. 1 ≤ n ≤ 60, integer entries |·| ≤ 1000. Use big integers (Go math/big, Java BigInteger, Python native int).
Hint. Bareiss update: m[i][j] = (m[k][k]·m[i][j] − m[i][k]·m[k][j]) / prevPivot, with prevPivot starting at 1. The division is always exact. The final pivot is the determinant (up to swap sign).
Evaluation criteria. - Exact determinant for matrices where the true value is known (e.g. Vandermonde). - Agrees with the mod-p determinant reduced mod p. - No overflow (big integers) and exact divisions throughout.
Task 13 — GF(2) system at scale with multi-word bitsets¶
Problem. Solve a GF(2) XOR system with n up to 2000 variables by packing each row into an array of 64-bit words and XORing word-by-word. Report a solution or NO_SOLUTION, and the number of free variables.
Constraints. 1 ≤ n ≤ 2000, numRows ≤ 4000.
Hint. Each row is ⌈(n+1)/64⌉ words. The pivot test reads bit col; the elimination XORs whole word arrays. This is the O(n³/64) solve. Use []uint64 (Go), long[] / BitSet (Java), or Python big ints.
Degree / sanity check. Verify A·x ≡ b over GF(2) by recomputing each equation's XOR; assert it equals the stored b bit for every row.
Evaluation criteria. - Handles n = 2000 within a fraction of a second. - Correct word-parallel XOR (matches a single-bit reference for small n). - Correct free-variable count and inconsistency detection.
Task 14 — LU factorization with multi-RHS reuse¶
Problem. Implement lu_decompose(A) returning PA = LU (in place, with a permutation array and swap parity), then lu_solve(LU, perm, b) answering each right-hand side in O(n²). Solve a batch of Q right-hand sides sharing one A.
Constraints. 1 ≤ n ≤ 300, 1 ≤ Q ≤ 1000.
Hint. Store the multipliers in the lower triangle of the factored matrix. lu_solve does forward substitution with L (unit diagonal) then back substitution with U. Determinant falls out as sign · ∏ U[i][i].
Evaluation criteria. - Factor once (O(n³)); each solve O(n²). - All Q solutions match an independent per-RHS Gaussian solve. - Determinant from the factorization matches Task 9.
Task 15 — Decide the right tool / field for a system¶
Problem. Given a problem description with (type, n, sparsity, field, conditioning), implement classify_method(problem) returning one of REAL_PARTIAL_PIVOT, CHOLESKY, MOD_P, GF2_BITSET, BAREISS_EXACT, SPARSE_DIRECT, ITERATIVE, or SVD_REGULARIZE, with a one-line justification.
Constraints / cases to handle. - Dense real, well-conditioned, unique → REAL_PARTIAL_PIVOT. - Symmetric positive-definite → CHOLESKY. - Exact answer mod prime → MOD_P; exact integer determinant → BAREISS_EXACT. - XOR/boolean equations → GF2_BITSET. - Huge sparse → SPARSE_DIRECT or ITERATIVE. - Severely ill-conditioned / least-squares / rank-deficient → SVD_REGULARIZE.
Hint. Encode the decision rules from senior.md (§decision summary). The trap is treating an exact problem with floats (rounding) or an ill-conditioned one as if pivoting could save it.
Evaluation criteria. - Correctly classifies all canonical cases. - Justification cites the right complexity and the failure mode avoided (rounding, fill-in, conditioning, overflow). - References the matching sibling topic where relevant (14, 18, 19, 18-bit-manipulation).
Benchmark Task¶
Task B — Dense vs bitset GF(2), and float vs mod-p timing¶
Problem. Empirically compare solver variants on random systems of fixed size n:
- (a) Real solver (
double, partial pivoting) —O(n³). - (b) Mod-p solver (
Z/pZ, modular inverse) —O(n³). - (c) GF(2) single-bit elimination (boolean array per row) —
O(n³). - (d) GF(2) bitset elimination (packed 64-bit words) —
O(n³/64).
Measure wall-clock time for n ∈ {50, 100, 200, 500} (skip variants that get too slow) and report a table. Confirm (d) is roughly 64× faster than (c).
Starter — Python.
import random
import time
from typing import List
MOD = 1_000_000_007
def gen_real(n, seed):
r = random.Random(seed)
A = [[r.uniform(-1, 1) for _ in range(n)] for _ in range(n)]
b = [r.uniform(-1, 1) for _ in range(n)]
return A, b
def gen_gf2(n, seed):
r = random.Random(seed)
# each row a packed (n+1)-bit integer
return [r.getrandbits(n + 1) for _ in range(n)]
def solve_real(A, b):
# TODO: partial pivoting, O(n^3)
return [0.0] * len(A)
def solve_gf2_bits(rows, n):
# TODO: per-bit boolean elimination, O(n^3)
return [0] * n
def solve_gf2_bitset(rows, n):
# TODO: integer-word XOR elimination, O(n^3/64)
return [0] * n
def bench(fn, *args) -> float:
start = time.perf_counter()
fn(*args)
return time.perf_counter() - start
def mean_ms(samples: List[float]) -> float:
return sum(samples) / len(samples) * 1000.0
def main() -> None:
print("n real_ms gf2_bits_ms gf2_bitset_ms")
for n in [50, 100, 200, 500]:
A, b = gen_real(n, 7)
rows = gen_gf2(n, 7)
rr = [bench(solve_real, [r[:] for r in A], b[:]) for _ in range(3)]
rg = [bench(solve_gf2_bitset, rows[:], n) for _ in range(3)]
if n <= 200:
rb = [bench(solve_gf2_bits, rows[:], n) for _ in range(3)]
print(f"{n:<6d} {mean_ms(rr):<11.2f} {mean_ms(rb):<13.2f} {mean_ms(rg):<.2f}")
else:
print(f"{n:<6d} {mean_ms(rr):<11.2f} {'(skipped)':<13} {mean_ms(rg):<.2f}")
if __name__ == "__main__":
main()
Evaluation criteria. - Same seed produces the same system across Go, Java, and Python. - The bitset GF(2) solver (d) is roughly 64× faster than the per-bit version (c). - Real and mod-p solvers scale as O(n³); report the measured constants. - Report includes the mean across at least 3 runs and excludes generation/cloning time. - Writeup: a short note on which field is cheapest per element and why (bit-parallelism, modular reduction cost, float FLOPs).
General Guidance for All Tasks¶
- Always verify the residual first.
A·x ≈ b(reals, withineps) orA·x ≡ b mod p(fields). It catches the overwhelming majority of bugs. - Pivot by field. Largest magnitude for floats (stability); any non-zero for
Z/pZand GF(2) (correctness only). - Use
eps, not== 0, for floats. Exact-zero comparisons misclassify rank and consistency after float arithmetic. - Mind overflow in mod-p. Normalize entries to
[0, p), use 64-bit products, and reduce after every multiply; for largepuse 128-bit or Montgomery (sibling14). - Divide by the modular inverse, not by
1/pivot, overZ/pZ(siblings04,06). Over GF(2) the only inverse is1, so just XOR. - Track
where[col]to report rank, free variables, and the general solution (Rouché–Capelli). - Factor once, solve many. For multiple right-hand sides, compute
A = LUonce and reuse (O(n²)perb); never invert to solve. - Use bitsets for GF(2) — the 64× word-parallel XOR is the difference between feasible and not at scale.
- Get
A^0/the identity case right per field. The pivot of a1×1system, the empty system, and the identity matrix are the cheap edge cases that catch off-by-one and base-case bugs. - Distinguish the three verdicts cleanly. No solution (inconsistent row
0…0|c,c≠0), unique (rank = unknowns), infinitely many (a free column). Conflating "no solution" with "infinitely many" is the most common classification bug.
Suggested progression¶
- Beginner (Tasks 1–5): master the mechanics — forward elimination, back substitution, the full real solver, then the two exact fields (mod-p, GF(2)).
- Intermediate (Tasks 6–10): classification by rank, inverses (real and mod-p), determinant, free-variable general solutions, and the real-valued (Markov-style) variant.
- Advanced (Tasks 11–15): exact rational solving via CRT + reconstruction, fraction-free Bareiss determinants, GF(2) at scale with multi-word bitsets, LU reuse across many right-hand sides, and the meta-skill of choosing the right method/field.
Do not skip the residual check at any level — it is the single habit that separates a solver you can trust from one you merely hope works.
Bugs the test cases are designed to catch¶
| Test input | Bug it exposes |
|---|---|
k = 0 / 1×1 system | wrong base case, off-by-one in loops |
| Matrix needing a pivot swap (zero on the diagonal) | missing pivot search / division by zero |
| Hilbert / near-singular matrix | no condition awareness; returning noise as if exact |
| Singular matrix (duplicate row) | failure to detect "no unique solution" |
Inconsistent system (0 = c) | missing the consistency scan |
| Rank-deficient consistent system | wrong free-variable count; reporting unique when infinite |
| Negative entries (mod-p) | unnormalized residues differing across languages |
Large p (near 2^62) | overflow before the modular reduction |
Wide GF(2) system (n > 64) | single-word assumption; multi-word XOR bugs |
Build each of these into your test suite. A solver that passes all of them on randomized instances — verified by the residual — is production-grade; one validated only on the worked 3×3 example is not.
Each task must be implemented and tested in Go, Java, and Python, with identical outputs across all three on the same seeded inputs.