Coverage for pyrc \ tests \ test_symbolic.py: 100%
77 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-13 16:59 +0200
« prev ^ index » next coverage.py v7.13.5, created at 2026-04-13 16:59 +0200
1# -------------------------------------------------------------------------------
2# Copyright (C) 2026 Joel Kimmich, Tim Jourdan
3# ------------------------------------------------------------------------------
4# License
5# This file is part of PyRC, distributed under GPL-3.0-or-later.
6# ------------------------------------------------------------------------------
8from unittest import TestCase
10import numpy as np
11from sympy import SparseMatrix, Symbol, sin, cos
12from pyrc.core.solver.symbolic import SparseSymbolicEvaluator
14t, w = Symbol("t"), Symbol("w")
17class TestSparseSymbolicEvaluator(TestCase):
19 def test_constant_matrix(self):
20 """Matrix with only constant entries evaluates correctly."""
21 M = SparseMatrix(3, 3, {(0, 0): 1.0, (1, 2): 3.0, (2, 1): -2.0})
22 ev = SparseSymbolicEvaluator(M, [])
23 result = ev.evaluate()
24 expected = np.array([[1, 0, 0], [0, 0, 3], [0, -2, 0]], dtype=float)
25 np.testing.assert_allclose(result.toarray(), expected)
27 def test_symbolic_single_entry(self):
28 """Single symbolic entry is evaluated and placed correctly alongside a constant."""
29 M = SparseMatrix(2, 2, {(0, 0): t, (1, 1): 2.0})
30 ev = SparseSymbolicEvaluator(M, [t])
31 result = ev.evaluate(np.array([3.0]))
32 expected = np.array([[3.0, 0], [0, 2.0]])
33 np.testing.assert_allclose(result.toarray(), expected)
35 def test_symbolic_multiple_symbols(self):
36 """Multiple symbols in multiple entries are evaluated correctly."""
37 M = SparseMatrix(2, 2, {(0, 0): t + w, (0, 1): w, (1, 0): t})
38 ev = SparseSymbolicEvaluator(M, [t, w])
39 result = ev.evaluate(np.array([1.0, 2.0]))
40 expected = np.array([[3.0, 2.0], [1.0, 0.0]])
41 np.testing.assert_allclose(result.toarray(), expected)
43 def test_nonlinear_expression(self):
44 """Nonlinear symbolic expressions (sin, cos) are evaluated correctly."""
45 M = SparseMatrix(2, 2, {(0, 0): sin(t), (1, 1): cos(t)})
46 ev = SparseSymbolicEvaluator(M, [t])
47 val = np.pi / 4
48 result = ev.evaluate(np.array([val]))
49 expected = np.array([[np.sin(val), 0], [0, np.cos(val)]])
50 np.testing.assert_allclose(result.toarray(), expected)
52 def test_mixed_constant_and_symbolic(self):
53 """Matrix with mixed constant and symbolic entries evaluates correctly."""
54 M = SparseMatrix(2, 2, {(0, 0): t, (0, 1): 5.0, (1, 1): w})
55 ev = SparseSymbolicEvaluator(M, [t, w])
56 result = ev.evaluate(np.array([2.0, -1.0]))
57 expected = np.array([[2.0, 5.0], [0.0, -1.0]])
58 np.testing.assert_allclose(result.toarray(), expected)
60 def test_working_matrix_not_modified_between_calls(self):
61 """Consecutive calls with different values return correct results without state leakage."""
62 M = SparseMatrix(2, 2, {(0, 0): t})
63 ev = SparseSymbolicEvaluator(M, [t])
64 ev.evaluate(np.array([10.0]))
65 result = ev.evaluate(np.array([3.0]))
66 expected = np.array([[3.0, 0], [0, 0.0]])
67 np.testing.assert_allclose(result.toarray(), expected)
69 def test_empty_matrix(self):
70 """Matrix with no nonzero entries returns a zero matrix."""
71 M = SparseMatrix(3, 3, {})
72 ev = SparseSymbolicEvaluator(M, [t])
73 result = ev.evaluate(np.array([1.0]))
74 np.testing.assert_allclose(result.toarray(), np.zeros((3, 3)))
76 def test_shape(self):
77 """Evaluator and output matrix have the correct shape."""
78 M = SparseMatrix(4, 6, {(0, 0): t, (3, 5): 1.0})
79 ev = SparseSymbolicEvaluator(M, [t])
80 self.assertEqual(ev.shape, (4, 6))
81 self.assertEqual(ev.evaluate(np.array([1.0])).shape, (4, 6))
83 def test_large_sparse_matrix(self):
84 """Random 1000x1000 sparse matrix with ~20 entries per row, mixed constant and symbolic, evaluates correctly."""
85 rng = np.random.default_rng(42)
86 n = 1000
87 entries_per_row = 20
88 M_dict = {}
89 expected = np.zeros((n, n))
91 t_val, w_val = 2.5, -1.3
93 for i in range(n):
94 cols = rng.choice(n, size=entries_per_row, replace=False)
95 for j in cols:
96 if rng.random() < 0.5:
97 val = rng.uniform(-10, 10)
98 M_dict[(i, int(j))] = val
99 expected[i, int(j)] += val
100 elif rng.random() < 0.5:
101 M_dict[(i, int(j))] = t
102 expected[i, int(j)] += t_val
103 else:
104 M_dict[(i, int(j))] = w
105 expected[i, int(j)] += w_val
107 M = SparseMatrix(n, n, M_dict)
108 ev = SparseSymbolicEvaluator(M, [t, w])
109 result = ev.evaluate(np.array([t_val, w_val]))
110 np.testing.assert_allclose(result.toarray(), expected)