Accepting request 753326 from home:bnavigator:branches:devel:languages:python:numeric
- python-control-pr345.patch: PR#345 to fix fails on some architectures because of machine precision OBS-URL: https://build.opensuse.org/request/show/753326 OBS-URL: https://build.opensuse.org/package/show/devel:languages:python:numeric/python-control?expand=0&rev=6
This commit is contained in:
238
python-control-pr345.patch
Normal file
238
python-control-pr345.patch
Normal file
@@ -0,0 +1,238 @@
|
||||
diff --git a/control/tests/xferfcn_test.py b/control/tests/xferfcn_test.py
|
||||
index 35e411b..ee779f9 100644
|
||||
--- a/control/tests/xferfcn_test.py
|
||||
+++ b/control/tests/xferfcn_test.py
|
||||
@@ -403,8 +403,57 @@ class TestXferFcn(unittest.TestCase):
|
||||
np.testing.assert_array_equal(omega, true_omega)
|
||||
|
||||
# Tests for TransferFunction.pole and TransferFunction.zero.
|
||||
-
|
||||
- @unittest.skipIf(not slycot_check(), "slycot not installed")
|
||||
+
|
||||
+ def test_common_den(self):
|
||||
+ """ Test the helper function to compute common denomitators."""
|
||||
+
|
||||
+ # _common_den() computes the common denominator per input/column.
|
||||
+ # The testing columns are:
|
||||
+ # 0: no common poles
|
||||
+ # 1: regular common poles
|
||||
+ # 2: poles with multiplicity,
|
||||
+ # 3: complex poles
|
||||
+ # 4: complex poles below threshold
|
||||
+
|
||||
+ eps = np.finfo(float).eps
|
||||
+ tol_imag = np.sqrt(eps*5*2*2)*0.9
|
||||
+
|
||||
+ numin = [[[1.], [1.], [1.], [1.], [1.]],
|
||||
+ [[1.], [1.], [1.], [1.], [1.]]]
|
||||
+ denin = [[[1., 3., 2.], # 0: poles: [-1, -2]
|
||||
+ [1., 6., 11., 6.], # 1: poles: [-1, -2, -3]
|
||||
+ [1., 6., 11., 6.], # 2: poles: [-1, -2, -3]
|
||||
+ [1., 6., 11., 6.], # 3: poles: [-1, -2, -3]
|
||||
+ [1., 6., 11., 6.]], # 4: poles: [-1, -2, -3],
|
||||
+ [[1., 12., 47., 60.], # 0: poles: [-3, -4, -5]
|
||||
+ [1., 9., 26., 24.], # 1: poles: [-2, -3, -4]
|
||||
+ [1., 7., 16., 12.], # 2: poles: [-2, -2, -3]
|
||||
+ [1., 7., 17., 15.], # 3: poles: [-2+1J, -2-1J, -3],
|
||||
+ np.poly([-2 + tol_imag * 1J, -2 - tol_imag * 1J, -3])]]
|
||||
+ numref = np.array([
|
||||
+ [[0., 0., 1., 12., 47., 60.],
|
||||
+ [0., 0., 0., 1., 4., 0.],
|
||||
+ [0., 0., 0., 1., 2., 0.],
|
||||
+ [0., 0., 0., 1., 4., 5.],
|
||||
+ [0., 0., 0., 1., 2., 0.]],
|
||||
+ [[0., 0., 0., 1., 3., 2.],
|
||||
+ [0., 0., 0., 1., 1., 0.],
|
||||
+ [0., 0., 0., 1., 1., 0.],
|
||||
+ [0., 0., 0., 1., 3., 2.],
|
||||
+ [0., 0., 0., 1., 1., 0.]]])
|
||||
+ denref = np.array(
|
||||
+ [[1., 15., 85., 225., 274., 120.],
|
||||
+ [1., 10., 35., 50., 24., 0.],
|
||||
+ [1., 8., 23., 28., 12., 0.],
|
||||
+ [1., 10., 40., 80., 79., 30.],
|
||||
+ [1., 8., 23., 28., 12., 0.]])
|
||||
+ sys = TransferFunction(numin, denin)
|
||||
+ num, den, denorder = sys._common_den()
|
||||
+ np.testing.assert_array_almost_equal(num[:2, :, :], numref)
|
||||
+ np.testing.assert_array_almost_equal(num[2:, :, :],
|
||||
+ np.zeros((3, 5, 6)))
|
||||
+ np.testing.assert_array_almost_equal(den, denref)
|
||||
+
|
||||
def test_pole_mimo(self):
|
||||
"""Test for correct MIMO poles."""
|
||||
|
||||
@@ -414,13 +463,12 @@ class TestXferFcn(unittest.TestCase):
|
||||
|
||||
np.testing.assert_array_almost_equal(p, [-2., -2., -7., -3., -2.])
|
||||
|
||||
- @unittest.skipIf(not slycot_check(), "slycot not installed")
|
||||
def test_double_cancelling_poles_siso(self):
|
||||
-
|
||||
+
|
||||
H = TransferFunction([1, 1], [1, 2, 1])
|
||||
p = H.pole()
|
||||
np.testing.assert_array_almost_equal(p, [-1, -1])
|
||||
-
|
||||
+
|
||||
# Tests for TransferFunction.feedback
|
||||
def test_feedback_siso(self):
|
||||
"""Test for correct SISO transfer function feedback."""
|
||||
diff --git a/control/xferfcn.py b/control/xferfcn.py
|
||||
index 1ef0661..a913061 100644
|
||||
--- a/control/xferfcn.py
|
||||
+++ b/control/xferfcn.py
|
||||
@@ -57,7 +57,6 @@ from numpy import angle, array, empty, finfo, ndarray, ones, \
|
||||
polyadd, polymul, polyval, roots, sqrt, zeros, squeeze, exp, pi, \
|
||||
where, delete, real, poly, nonzero
|
||||
import scipy as sp
|
||||
-from numpy.polynomial.polynomial import polyfromroots
|
||||
from scipy.signal import lti, tf2zpk, zpk2tf, cont2discrete
|
||||
from copy import deepcopy
|
||||
from warnings import warn
|
||||
@@ -774,8 +773,6 @@ class TransferFunction(LTI):
|
||||
output numerator array num is modified to use the common
|
||||
denominator for this input/column; the coefficient arrays are also
|
||||
padded with zeros to be the same size for all num/den.
|
||||
- num is an sys.outputs by sys.inputs
|
||||
- by len(d) array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -786,17 +783,20 @@ class TransferFunction(LTI):
|
||||
Returns
|
||||
-------
|
||||
num: array
|
||||
- Multi-dimensional array of numerator coefficients. num[i][j]
|
||||
- gives the numerator coefficient array for the ith input and jth
|
||||
- output, also prepared for use in td04ad; matches the denorder
|
||||
- order; highest coefficient starts on the left.
|
||||
+ n by n by kd where n = max(sys.outputs,sys.inputs)
|
||||
+ kd = max(denorder)+1
|
||||
+ Multi-dimensional array of numerator coefficients. num[i,j]
|
||||
+ gives the numerator coefficient array for the ith output and jth
|
||||
+ input; padded for use in td04ad ('C' option); matches the
|
||||
+ denorder order; highest coefficient starts on the left.
|
||||
|
||||
den: array
|
||||
+ sys.inputs by kd
|
||||
Multi-dimensional array of coefficients for common denominator
|
||||
polynomial, one row per input. The array is prepared for use in
|
||||
slycot td04ad, the first element is the highest-order polynomial
|
||||
- coefficiend of s, matching the order in denorder, if denorder <
|
||||
- number of columns in den, the den is padded with zeros
|
||||
+ coefficient of s, matching the order in denorder. If denorder <
|
||||
+ number of columns in den, the den is padded with zeros.
|
||||
|
||||
denorder: array of int, orders of den, one per input
|
||||
|
||||
@@ -810,16 +810,18 @@ class TransferFunction(LTI):
|
||||
|
||||
# Machine precision for floats.
|
||||
eps = finfo(float).eps
|
||||
+ real_tol = sqrt(eps * self.inputs * self.outputs)
|
||||
|
||||
- # Decide on the tolerance to use in deciding of a pole is complex
|
||||
+ # The tolerance to use in deciding if a pole is complex
|
||||
if (imag_tol is None):
|
||||
- imag_tol = 1e-8 # TODO: figure out the right number to use
|
||||
+ imag_tol = 2 * real_tol
|
||||
|
||||
# A list to keep track of cumulative poles found as we scan
|
||||
# self.den[..][..]
|
||||
poles = [[] for j in range(self.inputs)]
|
||||
|
||||
# RvP, new implementation 180526, issue #194
|
||||
+ # BG, modification, issue #343, PR #354
|
||||
|
||||
# pre-calculate the poles for all num, den
|
||||
# has zeros, poles, gain, list for pole indices not in den,
|
||||
@@ -838,30 +840,37 @@ class TransferFunction(LTI):
|
||||
poleset[-1].append([z, p, k, [], 0])
|
||||
|
||||
# collect all individual poles
|
||||
- epsnm = eps * self.inputs * self.outputs
|
||||
for j in range(self.inputs):
|
||||
for i in range(self.outputs):
|
||||
currentpoles = poleset[i][j][1]
|
||||
nothave = ones(currentpoles.shape, dtype=bool)
|
||||
for ip, p in enumerate(poles[j]):
|
||||
- idx, = nonzero(
|
||||
- (abs(currentpoles - p) < epsnm) * nothave)
|
||||
- if len(idx):
|
||||
- nothave[idx[0]] = False
|
||||
+ collect = (np.isclose(currentpoles.real, p.real,
|
||||
+ atol=real_tol) &
|
||||
+ np.isclose(currentpoles.imag, p.imag,
|
||||
+ atol=imag_tol) &
|
||||
+ nothave)
|
||||
+ if np.any(collect):
|
||||
+ # mark first found pole as already collected
|
||||
+ nothave[nonzero(collect)[0][0]] = False
|
||||
else:
|
||||
# remember id of pole not in tf
|
||||
poleset[i][j][3].append(ip)
|
||||
for h, c in zip(nothave, currentpoles):
|
||||
if h:
|
||||
+ if abs(c.imag) < imag_tol:
|
||||
+ c = c.real
|
||||
poles[j].append(c)
|
||||
# remember how many poles now known
|
||||
poleset[i][j][4] = len(poles[j])
|
||||
|
||||
# figure out maximum number of poles, for sizing the den
|
||||
- npmax = max([len(p) for p in poles])
|
||||
- den = zeros((self.inputs, npmax + 1), dtype=float)
|
||||
+ maxindex = max([len(p) for p in poles])
|
||||
+ den = zeros((self.inputs, maxindex + 1), dtype=float)
|
||||
num = zeros((max(1, self.outputs, self.inputs),
|
||||
- max(1, self.outputs, self.inputs), npmax + 1), dtype=float)
|
||||
+ max(1, self.outputs, self.inputs),
|
||||
+ maxindex + 1),
|
||||
+ dtype=float)
|
||||
denorder = zeros((self.inputs,), dtype=int)
|
||||
|
||||
for j in range(self.inputs):
|
||||
@@ -872,11 +881,10 @@ class TransferFunction(LTI):
|
||||
num[i, j, 0] = poleset[i][j][2]
|
||||
else:
|
||||
# create the denominator matching this input
|
||||
- # polyfromroots gives coeffs in opposite order from what we use
|
||||
- # coefficients should be padded on right, ending at np
|
||||
- np = len(poles[j])
|
||||
- den[j, np::-1] = polyfromroots(poles[j]).real
|
||||
- denorder[j] = np
|
||||
+ # coefficients should be padded on right, ending at maxindex
|
||||
+ maxindex = len(poles[j])
|
||||
+ den[j, :maxindex+1] = poly(poles[j])
|
||||
+ denorder[j] = maxindex
|
||||
|
||||
# now create the numerator, also padded on the right
|
||||
for i in range(self.outputs):
|
||||
@@ -885,22 +893,15 @@ class TransferFunction(LTI):
|
||||
# add all poles not found in the original denominator,
|
||||
# and the ones later added from other denominators
|
||||
for ip in chain(poleset[i][j][3],
|
||||
- range(poleset[i][j][4], np)):
|
||||
+ range(poleset[i][j][4], maxindex)):
|
||||
nwzeros.append(poles[j][ip])
|
||||
|
||||
- numpoly = poleset[i][j][2] * polyfromroots(nwzeros).real
|
||||
- # print(numpoly, den[j])
|
||||
- # polyfromroots gives coeffs in opposite order => invert
|
||||
+ numpoly = poleset[i][j][2] * np.atleast_1d(poly(nwzeros))
|
||||
# numerator polynomial should be padded on left and right
|
||||
- # ending at np to line up with what td04ad expects...
|
||||
- num[i, j, np + 1 - len(numpoly):np + 1] = numpoly[::-1]
|
||||
+ # ending at maxindex to line up with what td04ad expects.
|
||||
+ num[i, j, maxindex+1-len(numpoly):maxindex+1] = numpoly
|
||||
# print(num[i, j])
|
||||
|
||||
- if (abs(den.imag) > epsnm).any():
|
||||
- print("Warning: The denominator has a nontrivial imaginary part: %f"
|
||||
- % abs(den.imag).max())
|
||||
- den = den.real
|
||||
-
|
||||
return num, den, denorder
|
||||
|
||||
def sample(self, Ts, method='zoh', alpha=None):
|
||||
@@ -1,3 +1,9 @@
|
||||
-------------------------------------------------------------------
|
||||
Wed Nov 27 18:13:20 UTC 2019 - Benjamin Greiner <code@bnavigator.de>
|
||||
|
||||
- python-control-pr345.patch: PR#345 to fix fails on some
|
||||
architectures because of machine precision
|
||||
|
||||
-------------------------------------------------------------------
|
||||
Mon Nov 4 13:25:48 UTC 2019 - Benjamin Greiner <code@bnavigator.de>
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
#
|
||||
# spec file for package python-control
|
||||
#
|
||||
# Copyright (c) 2019 SUSE LINUX GmbH, Nuernberg, Germany.
|
||||
# Copyright (c) 2019 SUSE LLC
|
||||
#
|
||||
# All modifications and additions to the file contributed by third parties
|
||||
# remain the property of their copyright owners, unless otherwise agreed
|
||||
@@ -26,6 +26,7 @@ URL: http://python-control.sourceforge.net
|
||||
Source: https://files.pythonhosted.org/packages/source/c/control/control-%{version}.tar.gz
|
||||
Patch0: python-control-fixtestaugw.patch
|
||||
Patch1: python-control-pr317.patch
|
||||
Patch2: python-control-pr345.patch
|
||||
BuildRequires: %{python_module setuptools}
|
||||
BuildRequires: fdupes
|
||||
BuildRequires: python-rpm-macros
|
||||
@@ -54,6 +55,7 @@ operations for analysis and design of feedback control systems.
|
||||
%setup -q -n control-%{version}
|
||||
%patch0 -p1
|
||||
%patch1 -p1
|
||||
%patch2 -p1
|
||||
|
||||
%build
|
||||
%python_build
|
||||
|
||||
Reference in New Issue
Block a user