Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions control/mateqn.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

import numpy as np
import scipy as sp
from numpy import eye, finfo, inexact
from numpy import eye
from scipy.linalg import eigvals, solve

from .exception import ControlArgument, ControlDimension, ControlSlycot, \
Expand Down Expand Up @@ -69,7 +69,7 @@ def _warn_ill_conditioned_E(E):
not measurably more accurate in that regime.
"""
condE = np.linalg.cond(E)
if condE > 1.0 / np.sqrt(finfo(float).eps):
if condE > 1.0 / np.sqrt(np.finfo(float).eps):
warnings.warn(
f"E is ill-conditioned (cond(E) = {condE:.2g}); the generalized "
"Lyapunov solution may have reduced accuracy. The problem itself "
Expand Down Expand Up @@ -383,7 +383,7 @@ def dlyap(A, Q, C=None, E=None, method=None):
# Solvability requires lam_A * lam_Q != 1 for all pairs of
# eigenvalues (the diagonals of the triangular factors)
if np.min(np.abs(np.outer(np.diag(Tq), np.diag(Ta)) - 1.)) \
< finfo(float).eps * max(
< np.finfo(float).eps * max(
1., np.abs(np.diag(Ta)).max()
* np.abs(np.diag(Tq)).max()):
raise ControlArgument(
Expand Down Expand Up @@ -787,8 +787,7 @@ def _check_shape(M, n, m, square=False, symmetric=False, name="??"):
# Utility function to check if a matrix is symmetric
def _is_symmetric(M):
M = np.atleast_2d(M)
if isinstance(M[0, 0], inexact):
eps = finfo(M.dtype).eps
return ((M - M.T) < eps).all()
if issubclass(M.dtype.type, (np.inexact, float, complex)):
return np.allclose(M, M.conj().T)
else:
return (M == M.T).all()
20 changes: 20 additions & 0 deletions control/tests/mateqn_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,26 @@
class TestMatrixEquations:
"""These are tests for the matrix equation solvers in mateqn.py"""

def test_is_symmetric_robustness(self):

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test method was put before the docstring, but it should go after the docstring.

"""Test _is_symmetric with scale-aware tolerance and asymmetric matrices (Issue #1174)."""
from control.mateqn import _is_symmetric
# Truly symmetric
M_sym = array([[1.0, 2.0], [2.0, 1.0]])
assert _is_symmetric(M_sym)

# Scale-aware numerical roundoff symmetry
M_roundoff = array([[1e6, 2.0 + 1e-12], [2.0, 1e6]])
assert _is_symmetric(M_roundoff)

# Asymmetric with negative difference (previously failed due to missing abs())
M_asym = array([[1.0, 0.0], [10.0, 1.0]])
assert not _is_symmetric(M_asym)

# Complex Hermitian matrix
M_herm = array([[1.0, 1.0 - 2.0j], [1.0 + 2.0j, 3.0]])
assert _is_symmetric(M_herm)


@pytest.mark.parametrize('method',
['scipy',
pytest.param('slycot', marks=pytest.mark.slycot)])
Expand Down
Loading