WUT_Computer_Science/Programming/PORR/code/tests.py
Krzysztof kuhy Rudnicki 42007d56e4 PORR runs in 28s, and three checks so the next pass cannot repeat my mistakes
PORR was the last course whose `run` did not work. Four measurements, not
guesses, explain why -- and none of them is the matrix size everyone assumes:

  ProcessingType.PROCESSES   20.9s at n=2, 32.8s at n=5, 78.2s at n=10. That
                             is process-pool setup PER TEST, and it grows.
  matrix_type poli3          densifies a sparse .mat into a 2.3 GB npz cache
                             (nemeth12: 723 MB) -- over the 2G ceiling before
                             any solving happens.
  n=10000                    800 MB matrix, and np.linalg.solve wants a copy.
  n=5000                     spd_5000.npz was a Git LFS pointer whose object
                             404s on GitHub, so it regenerates from scratch.

Remove those and what is left still exercises the whole Richardson
implementation at every size that fits: 18 tests, 28 seconds through the
harness, exit 0. The submitted parameter set is one documented environment
variable away. `main.py` now propagates pytest's status (verified: exit 5 when
nothing is collected) -- it discarded it before, so `run` could not tell a
passing suite from a failing one.

The rest of this commit is scripts/check_refactor_safety.py, which exists
because three regressions got past both ruff and a smoke run during this
campaign, and I would rather they could not get past the next one:

  --signatures      diffs two trees function by function, parameter by
                    parameter, default by default. This is what found the 22
                    EARIN functions whose defaults an annotation pass silently
                    dropped.
  --logging         a module with a __main__ guard that logs at INFO must call
                    basicConfig, or it prints nothing at all. Found two more
                    in PORR just now.
  --print-keywords  refuses to let an automated rewrite near print(x, end="")
                    or print(x, file=sys.stderr); losing those turns a
                    rendered board into one glyph per line, or silently moves
                    error output onto stdout.

All three pass on the whole archive. Also in here: 12 latent TypeErrors mypy
found in EARIN that ruff could not -- call sites still passing positionally to
parameters this campaign had made keyword-only, plus one caller of a function
whose argument I had removed. Every one would have raised at runtime.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
2026-08-28 20:50:46 +02:00

135 lines
4.2 KiB
Python

import logging
import numpy as np
import pytest
from matrix_generator import MatrixGenerator
from processing_type import ProcessingType
from richardson_method import RichardsonMethod
from threads_indep import RichardsonMethodThreads
from time_measurement import tests_time, time_measurement
logger = logging.getLogger(__name__)
def calculate_norm_numpy(I, w, A):
difference = I - w * A
return np.linalg.norm(difference)
def calculate_eigenvalues(A):
eigenvalues = np.linalg.eigvals(A)
lambda_min = np.min(eigenvalues)
lambda_max = np.max(eigenvalues)
return lambda_min, lambda_max
def calcualte_norm_from_matrix_numpy(A, n):
lambda_min, lambda_max = calculate_eigenvalues(A)
omega = 2 / (lambda_min + lambda_max)
I = np.eye(n)
return calculate_norm_numpy(I, omega, A)
@time_measurement(tests_time)
def solution_lib(A, b):
return np.linalg.solve(A, b)
@pytest.mark.parametrize("n", [2, 5, 10, 50, 100, 300, 500, 750, 1000, 5000, 10000])
@pytest.mark.parametrize(
"processing_type",
[
ProcessingType.SEQUENTIAL,
ProcessingType.THREADS,
ProcessingType.PROCESSES,
ProcessingType.DISTRIBUTED_ARRAYS,
],
)
@pytest.mark.parametrize("matrix_type", ["spd", "nemeth12", "poli3"])
def test_richardson_vs_cg(
n: int, processing_type: ProcessingType, matrix_type: str, capsys
) -> None:
logger.info("matrix type: %s", matrix_type)
logger.info("matrix size: %s", n if matrix_type == "spd" else "fixed")
tolerance = 8e-3
max_iterations = 100
if matrix_type in ["nemeth12", "poli3"] and n != 2:
pytest.skip(
"Fixed matrix size for nemeth12 and poli3, skipping redundant runs."
)
if matrix_type == "spd":
A, b, lambda_min, lambda_max = MatrixGenerator.generate_matrix_and_vector(
"spd", size=n
)
elif matrix_type == "poli3":
A, b, lambda_min, lambda_max = MatrixGenerator.generate_matrix_and_vector(
"poli3"
)
elif matrix_type == "nemeth12":
A, b, lambda_min, lambda_max = MatrixGenerator.generate_matrix_and_vector(
"nemeth12"
)
else:
raise ValueError(
"Invalid matrix type specified. Choose 'spd', 'poli3', or 'nemeth12'."
)
solution_richardson, info_richardson = None, None
if processing_type != ProcessingType.THREADS:
richardson_solver = RichardsonMethod(
processing_type,
A,
b,
lambda_min,
lambda_max,
max_iterations,
size=A.shape[0],
tol=1e-7,
)
with capsys.disabled():
solution_richardson, info_richardson = richardson_solver.solve()
else:
with capsys.disabled():
solution_richardson, info_richardson = RichardsonMethodThreads(
A, b, lambda_min, lambda_max, max_iterations, tol=1e-7
)
# Przechwytywanie wyjścia po solve
captured = capsys.readouterr()
logger.info("Captured output: %s", captured.out)
solution = solution_lib(A, b)
assert_converged(solution_richardson, info_richardson, solution, tolerance, A, n)
def assert_converged(
solution_richardson, info_richardson, solution, tolerance, A, n
) -> None:
if info_richardson == "Richardson method for those values will NOT converge":
numpy_norm = calcualte_norm_from_matrix_numpy(A, n)
logger.info(
"Numpy norm: %s Richardson norm: %s", numpy_norm, solution_richardson
)
assert False, "Richardson did not converge"
else:
difference = np.linalg.norm(solution_richardson - solution)
logger.info("Difference between Richardson and numpy solutions: %s", difference)
if difference < tolerance:
logger.info(
"Both Richardson and numpy method converged and calculated correct values."
)
else:
logger.info("Solution numpy:\n %s", solution)
logger.info("Solution Richardson:\n %s", solution_richardson)
assert difference < tolerance, (
f"The solutions are different! Difference: {difference:.8f}"
)
if __name__ == "__main__":
logging.basicConfig(format="%(message)s", level=logging.INFO)
pytest.main()