96 lines
3.6 KiB
Python
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)
|