Files
sim-numerics/tests/test_relaxation.py
T

96 lines
3.6 KiB
Python

"""The relaxation parameter: the fourth domain, and the fourth time the same
pattern appears.
omega in SOR is the oldest hand-tuned step length in numerical computing. The
similarity form - omega = 1 + cos between successive corrections - lands
exactly in the admissible interval [1, 2] with nothing chosen, and it beats
Gauss-Seidel five-fold. It loses to a swept omega two-fold.
A second map, derived from the theory rather than from the geometry, was tried
and is worse than the first. Both are pinned: the working one so it is not
lost, the refuted one so it is not retried.
"""
from __future__ import annotations
import math
import os
import sys
import numpy as np
import pytest
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from relax.sor import (ContractionOmega, Fixed, SimilarityOmega, poisson_rhs,
residual, solve, sweep_gs)
from stage23_relaxation import anisotropic, convection, theory_check
N = 14
F = poisson_rhs(N)
W_OPT = 2.0 / (1.0 + math.sin(math.pi / (N + 1)))
def test_the_sweep_reproduces_the_sor_theory():
"""The instrument before the experiment: for the model problem the SOR
spectral radius is exactly omega* - 1."""
_w, theory, measured = theory_check(16)
assert abs(theory - measured) < 0.02, (theory, measured)
def test_the_similarity_omega_lands_in_the_admissible_interval():
"""Range of the similarity and convergence condition of the method
coincide, which is why nothing had to be clipped by hand."""
r = SimilarityOmega()
r.reset()
d1 = np.array([1.0, 2.0, 3.0])
assert r.next_omega(None, None) == 1.0
assert r.next_omega(d1, d1) == pytest.approx(2.0) # identical
assert r.next_omega(d1, -d1) == pytest.approx(1.0) # opposed
assert r.next_omega(np.array([1.0, 0.0, 0.0]),
np.array([0.0, 1.0, 0.0])) == pytest.approx(1.0)
@pytest.mark.parametrize("label,coeff", [
("poisson", None), ("aniso", anisotropic(N, 0.1))])
def test_similarity_beats_gauss_seidel_by_a_wide_margin(label, coeff):
gs, _ = solve(F, Fixed(1.0), coeff=coeff)
sim, _ = solve(F, SimilarityOmega(), coeff=coeff)
assert sim * 3 < gs, (label, sim, gs)
@pytest.mark.parametrize("label,coeff", [
("poisson", None), ("aniso", anisotropic(N, 0.1))])
def test_and_loses_to_a_swept_omega(label, coeff):
"""The other half, stated so the claim is never inflated."""
sim, _ = solve(F, SimilarityOmega(), coeff=coeff)
best = min(solve(F, Fixed(w), coeff=coeff)[0]
for w in (1.2, 1.4, 1.6, 1.8, W_OPT))
assert best < sim, (label, best, sim)
def test_the_similarity_rule_over_relaxes():
"""The diagnosis, not just the outcome: the cosine between successive SOR
corrections sits near 1, so 1 + cos saturates near 2 and the rule picks a
larger omega than the optimum."""
_sim, mean_w = solve(F, SimilarityOmega())
assert mean_w > W_OPT, (mean_w, W_OPT)
def test_the_contraction_map_is_refuted_and_worse():
"""The second attempt, pinned so it is not tried again. Undamped
feedback pins omega at the limit from the first sweep."""
sim, _ = solve(F, SimilarityOmega())
con, mean_w = solve(F, ContractionOmega())
assert con > sim, (con, sim)
assert mean_w > 1.9, mean_w
def test_convection_is_where_it_actually_hurts():
"""On a strongly non-symmetric problem Gauss-Seidel is already fast and
the rule makes it slower - the one case where the similarity form is not
merely second but harmful."""
coeff = convection(N, 10.0)
gs, _ = solve(F, Fixed(1.0), coeff=coeff)
sim, _ = solve(F, SimilarityOmega(), coeff=coeff)
assert sim > gs, (sim, gs)