mirror of
https://github.com/kuhyx/WUT_Computer_Science.git
synced 2026-11-10 21:47:08 +01:00
598 findings to none, and the suite ran through `./run.sh run` after each step rather than at the end -- 18 passed, 27s, every time. The shape of this one was different from the other courses. PORR is four implementations of the same linear algebra (sequential, threads, processes, dask), so nearly every finding came in groups of four: - 229 missing annotations. Rather than `object`, which mypy strict rejects on every attribute access, each module declares what these actually are: `Matrix = np.ndarray | list[list[float]]` and the vector equivalent. The backends really are called with both -- comparing them is the point of the course. - 76 names: `A` -> matrix, `Ab` -> augmented, `Ax` -> product, `I` -> identity, `dA` -> dask_matrix, and the two CamelCase functions. Renamed through `tokenize`, not regex, so no string or comment was touched. - 121 missing docstrings, one per method name since the names repeat across all four backends. - Seven functions over the argument limit, all taking the same seven or nine loose values. They are two frozen dataclasses now, in a new richardson_problem.py: `Problem(matrix, rhs, lambda_min, lambda_max)` and `Settings(max_iterations, x0, tol)`. `RichardsonMethod.__init__` also took a `size` argument it never read. - threads_indep_old's iteration did "split into chunks, submit, wait" four times by hand (C901 11, PLR0915 54). One `_run_chunked` helper. - tests.py's two bare asserts became explicit raises, per the no-ignores rule. `assert False` was also B011: `python -O` deletes it, so the non-convergence branch would have silently passed under optimisation. Real fixes rather than style: np.random -> Generator, `os.path.exists` -> Path, an `except Exception` around dask narrowed to the errors it actually raises, and two runnable modules that logged at INFO without ever calling basicConfig -- caught by scripts/check_refactor_safety.py, which is now doing the job it was written for. Repo-wide: 1,781 findings -> 1,183, all of them in the five courses left. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
180 lines
5.7 KiB
Python
Executable File
180 lines
5.7 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Richardson iteration split across MPI ranks."""
|
|
|
|
import logging
|
|
import time
|
|
|
|
import numpy as np
|
|
from mpi4py import MPI
|
|
from richardson_problem import Problem, Settings
|
|
|
|
# These helpers are called with numpy arrays in some backends and plain
|
|
# Python lists in others, which is the point of the comparison this course
|
|
# is about, so the aliases name both.
|
|
Matrix = np.ndarray | list[list[float]]
|
|
Vector = np.ndarray | list[float]
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
def richardson_parallel(
|
|
problem: Problem, settings: Settings | None = None
|
|
) -> tuple[Vector, float]:
|
|
"""Run Richardson across MPI ranks."""
|
|
if settings is None:
|
|
settings = Settings(max_iterations=1000)
|
|
matrix = problem.matrix
|
|
b = problem.rhs
|
|
lambda_min = problem.lambda_min
|
|
lambda_max = problem.lambda_max
|
|
tol = settings.tol
|
|
max_iter = settings.max_iterations
|
|
comm = MPI.COMM_WORLD
|
|
rank = comm.Get_rank()
|
|
size = comm.Get_size()
|
|
|
|
# Rozmiar macierzy A
|
|
n = matrix.shape[0]
|
|
|
|
# Obliczanie wartości własnych tylko na jednym procesie
|
|
omega = 2 / (lambda_min + lambda_max) if rank == 0 else None
|
|
|
|
# Rozgłoszenie omega do wszystkich procesów
|
|
omega = comm.bcast(omega, root=0)
|
|
|
|
# Inicjalizacja wektora rozwiązania jako float64
|
|
x = np.zeros_like(b, dtype=np.float64)
|
|
|
|
# Dzielimy pracę między procesy
|
|
local_rows = n // size
|
|
start_row = rank * local_rows
|
|
end_row = start_row + local_rows if rank != size - 1 else n
|
|
|
|
# Przydzielenie lokalnych porcji A i b
|
|
local_matrix = matrix[start_row:end_row, :]
|
|
local_b = b[start_row:end_row]
|
|
|
|
# Lokalny wektor residuum
|
|
local_r = np.zeros_like(local_b, dtype=np.float64)
|
|
|
|
# Globalny wektor residuum (pełny rozmiar b)
|
|
global_r = np.zeros_like(b, dtype=np.float64)
|
|
|
|
start_time = time.time()
|
|
|
|
for _i in range(max_iter):
|
|
# Oblicz lokalny residuum r = b - A @ x
|
|
local_r[:] = local_b - np.dot(local_matrix, x)
|
|
|
|
# Tworzymy tymczasowy wektor o pełnym rozmiarze i kopiujemy lokalne dane
|
|
temp_r = np.zeros_like(b, dtype=np.float64)
|
|
temp_r[start_row:end_row] = local_r
|
|
|
|
# Sumujemy lokalne residuum przez wszystkie procesy
|
|
comm.Allreduce(temp_r, global_r, op=MPI.SUM)
|
|
|
|
# Aktualizujemy x równolegle na wszystkich procesach
|
|
x += omega * global_r
|
|
|
|
# Sprawdzamy warunek stopu (norma residuum)
|
|
if np.linalg.norm(global_r) < tol:
|
|
break
|
|
|
|
end_time = time.time()
|
|
|
|
execution_time = end_time - start_time
|
|
|
|
return x, execution_time
|
|
|
|
|
|
def check_solution(
|
|
matrix: Matrix, b: Vector, x_approx: Vector, tolerance: float = 0.008
|
|
) -> tuple[bool, float]:
|
|
"""Report whether an approximate solution is within tolerance."""
|
|
x_true = np.linalg.solve(matrix, b)
|
|
error = np.linalg.norm(x_true - x_approx)
|
|
return error < tolerance, error
|
|
|
|
|
|
if __name__ == "__main__":
|
|
logging.basicConfig(format="%(message)s", level=logging.INFO)
|
|
comm = MPI.COMM_WORLD
|
|
rank = comm.Get_rank()
|
|
|
|
from matrix_generator import MatrixGenerator
|
|
|
|
sizes = [2, 5, 10, 50, 80, 100, 300, 500, 750, 1000, 5000, 10000]
|
|
for i in sizes:
|
|
if rank == 0:
|
|
matrix, b, lambda_min, lambda_max = (
|
|
MatrixGenerator.generate_matrix_and_vector("spd", size=i)
|
|
)
|
|
else:
|
|
matrix = None
|
|
b = None
|
|
lambda_min = None
|
|
lambda_max = None
|
|
|
|
matrix = comm.bcast(matrix, root=0)
|
|
b = comm.bcast(b, root=0)
|
|
|
|
# Rozwiązanie przy użyciu zrównoleglonej metody Richardsona
|
|
x, time_taken = richardson_parallel(Problem(matrix, b, lambda_min, lambda_max))
|
|
|
|
# Sprawdzanie poprawności rozwiązania (na procesie 0)
|
|
if rank == 0:
|
|
logger.info("Spd matrix with size %s", i)
|
|
is_correct, error = check_solution(matrix, b, x)
|
|
logger.info("Czas wykonania [s]: %s", time_taken)
|
|
logger.info(
|
|
"Czy rozwiązanie jest poprawne: %s", "Tak" if is_correct else "Nie"
|
|
)
|
|
logger.info("Błąd rozwiązania: %s", error)
|
|
|
|
if rank == 0:
|
|
matrix, b, lambda_min, lambda_max = MatrixGenerator.generate_matrix_and_vector(
|
|
"nemeth12"
|
|
)
|
|
else:
|
|
matrix = None
|
|
b = None
|
|
lambda_min = None
|
|
lambda_max = None
|
|
|
|
matrix = comm.bcast(matrix, root=0)
|
|
b = comm.bcast(b, root=0)
|
|
|
|
# Rozwiązanie przy użyciu zrównoleglonej metody Richardsona
|
|
x, time_taken = richardson_parallel(Problem(matrix, b, lambda_min, lambda_max))
|
|
|
|
# Sprawdzanie poprawności rozwiązania (na procesie 0)
|
|
if rank == 0:
|
|
logger.info("Nemeth12 matrix")
|
|
is_correct, error = check_solution(matrix, b, x)
|
|
logger.info("Czas wykonania [s]: %s", time_taken)
|
|
logger.info("Czy rozwiązanie jest poprawne: %s", "Tak" if is_correct else "Nie")
|
|
logger.info("Błąd rozwiązania: %s", error)
|
|
|
|
if rank == 0:
|
|
matrix, b, lambda_min, lambda_max = MatrixGenerator.generate_matrix_and_vector(
|
|
"poli3"
|
|
)
|
|
else:
|
|
matrix = None
|
|
b = None
|
|
lambda_min = None
|
|
lambda_max = None
|
|
|
|
matrix = comm.bcast(matrix, root=0)
|
|
b = comm.bcast(b, root=0)
|
|
|
|
# Rozwiązanie przy użyciu zrównoleglonej metody Richardsona
|
|
x, time_taken = richardson_parallel(Problem(matrix, b, lambda_min, lambda_max))
|
|
|
|
# Sprawdzanie poprawności rozwiązania (na procesie 0)
|
|
if rank == 0:
|
|
logger.info("Poli3 matrix")
|
|
is_correct, error = check_solution(matrix, b, x)
|
|
logger.info("Czas wykonania [s]: %s", time_taken)
|
|
logger.info("Czy rozwiązanie jest poprawne: %s", "Tak" if is_correct else "Nie")
|
|
logger.info("Błąd rozwiązania: %s", error)
|