From e3b15d3c121bb4ca2bdfaf681ca79bf5231a6384 Mon Sep 17 00:00:00 2001 From: Simon Bjurek <simbj106@student.liu.se> Date: Wed, 12 Feb 2025 11:17:21 +0100 Subject: [PATCH] ldlt inverse now verified for matrices up to and including size 3x3 --- .gitignore | 1 + b_asic/core_operations.py | 17 ++++++++--- b_asic/sfg_generators.py | 29 +++++++++---------- test/test_sfg_generators.py | 56 +++++++++++++++++++++++++++++++++++++ 4 files changed, 85 insertions(+), 18 deletions(-) diff --git a/.gitignore b/.gitignore index c5b2148a..d240bbd1 100644 --- a/.gitignore +++ b/.gitignore @@ -116,6 +116,7 @@ TODO.txt b_asic/_version.py docs_sphinx/_build/ docs_sphinx/examples +docs_sphinx/sg_execution_times.rst result_images/ .coverage Digraph.gv diff --git a/b_asic/core_operations.py b/b_asic/core_operations.py index 70d43370..67d6b07f 100644 --- a/b_asic/core_operations.py +++ b/b_asic/core_operations.py @@ -1123,7 +1123,7 @@ class MADS(AbstractOperation): def __init__( self, - is_add: Optional[bool] = False, + is_add: Optional[bool] = True, src0: Optional[SignalSourceProvider] = None, src1: Optional[SignalSourceProvider] = None, src2: Optional[SignalSourceProvider] = None, @@ -1142,15 +1142,24 @@ class MADS(AbstractOperation): latency_offsets=latency_offsets, execution_time=execution_time, ) - self._is_add = is_add - # self.set_param("is_add", is_add) + self.set_param("is_add", is_add) @classmethod def type_name(cls) -> TypeName: return TypeName("mads") def evaluate(self, a, b, c): - return a + b * c if self._is_add else a - b * c + return a + b * c if self.is_add else a - b * c + + @property + def is_add(self) -> bool: + """Get if operation is an addition.""" + return self.param("is_add") + + @is_add.setter + def is_add(self, is_add: bool) -> None: + """Set if operation is an addition.""" + self.set_param("is_add", is_add) @property def is_linear(self) -> bool: diff --git a/b_asic/sfg_generators.py b/b_asic/sfg_generators.py index 0ceeb4f6..a0847de4 100644 --- a/b_asic/sfg_generators.py +++ b/b_asic/sfg_generators.py @@ -456,40 +456,41 @@ def ldlt_matrix_inverse(N: int, is_complex: bool) -> SFG: # R*di*R^T factorization for i in range(N): - for k in range(1, i): - D[i] = MADS(False, D[i], M[k][i], R[k][i]) + for k in range(i): + D[i] = MADS(False, D[i], M[k][i], R[k][i], name="M1") D_inv[i] = Reciprocal(D[i]) - for j in range(i, N): + for j in range(i + 1, N): R[i][j] = A[i][j] - for k in range(1, i): - R[i][j] = MADS(False, R[i][j], M[k][i], R[k][j]) + for k in range(i): + R[i][j] = MADS(False, R[i][j], M[k][i], R[k][j], name="M2") if is_complex: M[i][j] = ComplexConjugate(R[i][j]) else: M[i][j] = R[i][j] - R[i][j] = MADS(True, Constant(0, name="0"), R[i][j], D_inv[i]) + R[i][j] = MADS(True, Constant(0, name="0"), R[i][j], D_inv[i], name="M3") # back substitution A_inv = [[None for _ in range(N)] for _ in range(N)] for i in reversed(range(N)): A_inv[i][i] = D_inv[i] - for j in reversed(range(i)): - for k in reversed(range(j, N)): + for j in reversed(range(i + 1)): + for k in reversed(range(j + 1, N)): if k == N - 1 and i != j: - # my custom - if A_inv[k][i]: + A_inv[j][i] = MADS( + False, Constant(0, name="0"), R[j][k], A_inv[i][k], name="M4" + ) + else: + if A_inv[i][k]: A_inv[j][i] = MADS( - False, Constant(0, name="0"), R[j][k], A_inv[k][i] + False, A_inv[j][i], R[j][k], A_inv[i][k], name="M5" ) else: - A_inv[j][i] = Constant(0, name="0") - else: - A_inv[j][i] = MADS(False, A_inv[j][i], R[j][k], A_inv[k][i]) + A_inv[j][i] = MADS(False, A_inv[j][i], R[j][k], A_inv[k][i]) outputs = [] for i in range(N): diff --git a/test/test_sfg_generators.py b/test/test_sfg_generators.py index 7064658c..e3f56ec0 100644 --- a/test/test_sfg_generators.py +++ b/test/test_sfg_generators.py @@ -12,6 +12,7 @@ from b_asic.sfg_generators import ( direct_form_1_iir, direct_form_2_iir, direct_form_fir, + ldlt_matrix_inverse, radix_2_dif_fft, transposed_direct_form_fir, wdf_allpass, @@ -637,3 +638,58 @@ class TestRadix2FFT: POINTS = 5 with pytest.raises(ValueError, match="Points must be a power of two."): radix_2_dif_fft(points=POINTS) + + +class TestLdltMatrixInverse: + def test_1x1(self): + sfg = ldlt_matrix_inverse(N=1, is_complex=False) + + A_input = [Constant(5)] + + sim = Simulation(sfg, A_input) + sim.run_for(1) + + res = sim.results + assert np.isclose(res["0"], 0.2) + + def test_2x2_simple_spd(self): + sfg = ldlt_matrix_inverse(N=2, is_complex=False) + + A = np.array([[1, 2], [2, 1]]) + A_input = [Constant(1), Constant(2), Constant(1)] + + A_inv = np.linalg.inv(A) + + sim = Simulation(sfg, A_input) + sim.run_for(1) + + res = sim.results + assert np.isclose(res["0"], A_inv[0, 0]) + assert np.isclose(res["1"], A_inv[0, 1]) + assert np.isclose(res["2"], A_inv[1, 1]) + + def test_3x3_simple_spd(self): + sfg = ldlt_matrix_inverse(N=3, is_complex=False) + + A = np.array([[2, -1, 0], [-1, 3, -1], [0, -1, 2]]) + A_input = [ + Constant(2), + Constant(-1), + Constant(0), + Constant(3), + Constant(-1), + Constant(2), + ] + + A_inv = np.linalg.inv(A) + + sim = Simulation(sfg, A_input) + sim.run_for(1) + + res = sim.results + assert np.isclose(res["0"], A_inv[0, 0]) + assert np.isclose(res["1"], A_inv[0, 1]) + assert np.isclose(res["2"], A_inv[0, 2]) + assert np.isclose(res["3"], A_inv[1, 1]) + assert np.isclose(res["4"], A_inv[1, 2]) + assert np.isclose(res["5"], A_inv[2, 2]) -- GitLab