WUT_Computer_Science/Programming/PORR/code/matrix_generator.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

109 lines
3.9 KiB
Python

import os
import numpy as np
import scipy.io
class MatrixGenerator:
@staticmethod
def generate_spd_matrix(n: int) -> np.ndarray:
A = np.random.rand(n, n)
return np.dot(A, A.T) + n * MatrixGenerator.generate_identity_matrix(
n
) # Adding n*I ensures positive definiteness
@staticmethod
def generate_identity_matrix(size):
return np.eye(size)
@staticmethod
def generate_alternating_vector(size):
return np.tile([1, 2], int(np.ceil(size / 2)))[:size]
@staticmethod
def get_matrix_from_file(file_path, problem):
mat_contents = scipy.io.loadmat(file_path)
problem_record = mat_contents["Problem"][0][0]
A = problem_record[problem]
if scipy.sparse.issparse(A):
A_dense = A.todense()
else:
A_dense = A
return np.array(A_dense)
@staticmethod
def generate_matrix_and_vector(type, size=None):
if type == "spd":
if size is None:
raise ValueError("Size must be provided for SPD matrix generation.")
try:
matrix, vector, lambda_min, lambda_max = MatrixGenerator.load_from_file(
"spd_" + str(size) + ".npz"
)
except FileNotFoundError:
matrix = MatrixGenerator.generate_spd_matrix(size)
vector = np.random.uniform(-1, 1, size)
lambda_min, lambda_max = MatrixGenerator.calculate_eigenvalues(matrix)
MatrixGenerator.save_to_file(
matrix, vector, lambda_min, lambda_max, "spd_" + str(size) + ".npz"
)
elif type == "nemeth12":
try:
matrix, vector, lambda_min, lambda_max = MatrixGenerator.load_from_file(
"nemeth12.npz"
)
except FileNotFoundError:
matrix = -1 * MatrixGenerator.get_matrix_from_file("nemeth12.mat", 1)
size = matrix.shape[0]
vector = MatrixGenerator.generate_alternating_vector(size)
lambda_min, lambda_max = MatrixGenerator.calculate_eigenvalues(matrix)
MatrixGenerator.save_to_file(
matrix, vector, lambda_min, lambda_max, "nemeth12.npz"
)
elif type == "poli3":
try:
matrix, vector, lambda_min, lambda_max = MatrixGenerator.load_from_file(
"poli3.npz"
)
except FileNotFoundError:
matrix = MatrixGenerator.get_matrix_from_file("poli3.mat", 2)
size = matrix.shape[0]
vector = MatrixGenerator.generate_alternating_vector(size)
lambda_min, lambda_max = MatrixGenerator.calculate_eigenvalues(matrix)
MatrixGenerator.save_to_file(
matrix, vector, lambda_min, lambda_max, "poli3.npz"
)
else:
raise ValueError(
"Invalid type specified. Choose 'spd', 'nemeth12', or 'poli3'."
)
return matrix, vector, lambda_min, lambda_max
@staticmethod
def calculate_eigenvalues(A):
eigenvalues = np.linalg.eigvals(A)
lambda_min = np.min(eigenvalues)
lambda_max = np.max(eigenvalues)
return lambda_min, lambda_max
@staticmethod
def save_to_file(matrix, vector, lambda_min, lambda_max, file_path) -> None:
np.savez(
file_path,
matrix=matrix,
vector=vector,
lambda_min=lambda_min,
lambda_max=lambda_max,
)
def load_from_file(file_path):
if not os.path.exists(file_path):
raise FileNotFoundError(f"The file {file_path} does not exist.")
data = np.load(file_path)
matrix = data["matrix"]
vector = data["vector"]
lambda_min = data["lambda_min"]
lambda_max = data["lambda_max"]
return matrix, vector, lambda_min, lambda_max