WUT_Computer_Science/Programming/PORR/code/threads_indep.py
Krzysztof kuhy Rudnicki 8798105aa1 Grade against 3.14 too, not just run on it
The pre-commit workflow already installs `python-version: "3.x"`, but ruff was
still grading the archive against py310 -- so it kept withholding the pyupgrade
and type-checking fixes that only become safe at the newer target.
`pin_ci_python.py` rewrites the knob.

The 17 findings that appeared are what the stale target was hiding:
`datetime.timezone.utc` where `datetime.UTC` is now correct (UP017), and 14
imports used only in annotations that belong under `TYPE_CHECKING` (TC002,
TC003) -- safe here precisely because 3.14's PEP 649 defers annotation
evaluation, which is the same reason ruff would not offer them at py310.

`ruff check` and `ruff format --check` are clean again, so the zero-findings
state this repo reached earlier today holds at the new target.

Still contradictory and deliberately untouched: the four
`Programming/ERSMS-project/*/Dockerfile` files say `FROM python:3.10-slim`.
They build an archived coursework service against its own era's pinned
dependencies, and nothing in this repo runs them; bumping the base image would
be a rewrite of that project, not a lint-target change.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SfnJmxxsZ534AKk16rC2Gr
2026-08-29 11:55:43 +02:00

153 lines
4.5 KiB
Python
Executable File

#!/usr/bin/env python3
"""Threaded Richardson with njit-compiled kernels."""
import gc
import logging
import time
from typing import TYPE_CHECKING
import linear_algebra_utils as lin_alg
import numpy as np
from numba import njit, prange
from time_measurement import (
longest_threads_time_accumulator,
tests_time,
time_measurement_longest,
)
if TYPE_CHECKING:
from richardson_problem import Problem, Settings
logger = logging.getLogger(__name__)
# Called with numpy arrays here and with plain lists in the sequential
# backend; the aliases name both.
Matrix = np.ndarray | list[list[float]]
Vector = np.ndarray | list[float]
@njit(parallel=True)
def numba_matrix_vector_multiply(
matrix: Matrix, input_x: Vector, product: Vector
) -> None:
"""Matrix-vector product, njit-compiled."""
for i in prange(len(matrix)):
acc = 0.0
for j in range(len(input_x)):
acc += matrix[i][j] * input_x[j]
product[i] = acc
@njit(parallel=True)
def numba_vector_vector_subtraction(
b: Vector, product: Vector, residual: Vector
) -> None:
"""Elementwise subtraction, njit-compiled."""
for i in prange(len(b)):
residual[i] = b[i] - product[i]
@njit(nopython=True)
def numba_scalar_vector_multiply(omega: float, vector: Vector, result: Vector) -> None:
"""Scalar-vector product, njit-compiled."""
omega_real = omega.real
for i in range(len(vector)):
result[i] = omega_real * vector[i]
@njit(parallel=True)
def numba_vector_vector_addition(
input_x: Vector, vector: Vector, output_x: Vector
) -> None:
"""Elementwise addition, njit-compiled."""
for i in prange(len(input_x)):
output_x[i] = input_x[i] + vector[i]
# Funkcje z dekoratorem
@time_measurement_longest(longest_threads_time_accumulator)
def matrix_vector_multiply(matrix: Matrix, input_x: Vector, product: Vector) -> None:
"""Return the matrix-vector product."""
numba_matrix_vector_multiply(matrix, input_x, product)
@time_measurement_longest(longest_threads_time_accumulator)
def vector_vector_subtraction(b: Vector, product: Vector, residual: Vector) -> None:
"""Subtract one vector from another, elementwise."""
numba_vector_vector_subtraction(b, product, residual)
@time_measurement_longest(longest_threads_time_accumulator)
def scalar_vector_multiply(omega: float, vector: Vector, result: Vector) -> None:
"""Multiply every element of a vector by a scalar."""
numba_scalar_vector_multiply(omega, vector, result)
@time_measurement_longest(longest_threads_time_accumulator)
def vector_vector_addition(input_x: Vector, vector: Vector, output_x: Vector) -> None:
"""Add two vectors elementwise."""
numba_vector_vector_addition(input_x, vector, output_x)
# Metoda Richardson z obsługą wątków
def richardson_method_threads(
problem: Problem, settings: Settings
) -> tuple[Vector, str | None]:
"""Run Richardson with one thread per chunk."""
matrix = problem.matrix
b = problem.rhs
lambda_min = problem.lambda_min
lambda_max = problem.lambda_max
max_iterations = settings.max_iterations
x0 = settings.x0
tol = settings.tol
longest_threads_time_accumulator.hard_reset()
gc.disable()
start_time = time.perf_counter()
x0 = x0 if x0 is not None else [0.0] * len(b)
x = x0[:]
omega = 2 / (lambda_min + lambda_max)
n = len(b)
for _iteration in range(max_iterations):
product = [0.0] * n
matrix_vector_multiply(matrix, x, product)
longest_threads_time_accumulator.save_lap_and_reset()
residual = [0.0] * n
vector_vector_subtraction(b, product, residual)
longest_threads_time_accumulator.save_lap_and_reset()
change_vector = [0.0] * n
scalar_vector_multiply(omega, residual, change_vector)
longest_threads_time_accumulator.save_lap_and_reset()
_x = [0.0] * n
vector_vector_addition(x, change_vector, _x)
longest_threads_time_accumulator.save_lap_and_reset()
x = _x[:]
if lin_alg.SequentialLinearAlgebraUtils.vector_norm(residual) < tol:
break
end_time = time.perf_counter()
gc.enable()
total_time = end_time - start_time
sequential_time = total_time - longest_threads_time_accumulator.total_time
logger.info(
"Total: %ss, Seq: %ss, Parallel (threads): %ss, Tests time: %ss",
total_time,
sequential_time,
longest_threads_time_accumulator.total_time,
tests_time.total_time,
)
return x, 0