Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit2f29a4c

Browse files
authored
Merge pull request#739 from Mark-Yeatman/passivity-tools
Add passivity module, ispassive function, and passivity_test. Introduces optional dependency cvxopt.
2 parentsd98ed29 +c2255b0 commit2f29a4c

File tree

7 files changed

+195
-7
lines changed

7 files changed

+195
-7
lines changed

‎.github/workflows/python-package-conda.yml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ on: [push, pull_request]
44

55
jobs:
66
test-linux:
7-
name:Python ${{ matrix.python-version }}${{ matrix.slycot && format(' with Slycot from {0}', matrix.slycot) || ' without Slycot' }}${{ matrix.pandas && ', with pandas' || '' }}${{ matrix.array-and-matrix == 1 && ', array and matrix' || '' }}
7+
name:Python ${{ matrix.python-version }}${{ matrix.slycot && format(' with Slycot from {0}', matrix.slycot) || ' without Slycot' }}${{ matrix.pandas && ', with pandas' || '' }}${{ matrix.array-and-matrix == 1 && ', array and matrix' || '' }}${{ matrix.cvxopt && format(' with cvxopt from {0}', matrix.cvxopt) || ' without cvxopt' }}
88
runs-on:ubuntu-latest
99

1010
strategy:
@@ -13,6 +13,7 @@ jobs:
1313
python-version:[3.7, 3.9]
1414
slycot:["", "conda"]
1515
pandas:[""]
16+
cvxopt:["", "conda"]
1617
array-and-matrix:[0]
1718
include:
1819
-python-version:3.9
@@ -45,6 +46,9 @@ jobs:
4546
if [[ '${{matrix.pandas}}' == 'conda' ]]; then
4647
conda install pandas
4748
fi
49+
if [[ '${{matrix.cvxopt}}' == 'conda' ]]; then
50+
conda install -c conda-forge cvxopt
51+
fi
4852
4953
-name:Test with pytest
5054
env:

‎control/exception.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,15 @@ def pandas_check():
8484
except:
8585
pandas_installed=False
8686
returnpandas_installed
87+
88+
# Utility function to see if cvxopt is installed
89+
cvxopt_installed=None
90+
defcvxopt_check():
91+
globalcvxopt_installed
92+
ifcvxopt_installedisNone:
93+
try:
94+
importcvxopt
95+
cvxopt_installed=True
96+
except:
97+
cvxopt_installed=False
98+
returncvxopt_installed

‎control/lti.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
"""
1414

1515
importnumpyasnp
16+
1617
fromnumpyimportabsolute,real,angle,abs
1718
fromwarningsimportwarn
1819
from .importconfig
@@ -21,6 +22,7 @@
2122
__all__= ['poles','zeros','damp','evalfr','frequency_response',
2223
'freqresp','dcgain','pole','zero']
2324

25+
2426
classLTI(NamedIOSystem):
2527
"""LTI is a parent class to linear time-invariant (LTI) system objects.
2628
@@ -44,6 +46,7 @@ class LTI(NamedIOSystem):
4446
Note: dt processing has been moved to the NamedIOSystem class.
4547
4648
"""
49+
4750
def__init__(self,inputs=1,outputs=1,states=None,name=None,**kwargs):
4851
"""Assign the LTI object's numbers of inputs and ouputs."""
4952
super().__init__(
@@ -71,8 +74,7 @@ def _set_inputs(self, value):
7174

7275
#: Deprecated
7376
inputs=property(
74-
_get_inputs,_set_inputs,doc=
75-
"""
77+
_get_inputs,_set_inputs,doc="""
7678
Deprecated attribute; use :attr:`ninputs` instead.
7779
7880
The ``inputs`` attribute was used to store the number of system
@@ -94,8 +96,7 @@ def _set_outputs(self, value):
9496

9597
#: Deprecated
9698
outputs=property(
97-
_get_outputs,_set_outputs,doc=
98-
"""
99+
_get_outputs,_set_outputs,doc="""
99100
Deprecated attribute; use :attr:`noutputs` instead.
100101
101102
The ``outputs`` attribute was used to store the number of system
@@ -201,6 +202,11 @@ def _dcgain(self, warn_infinite):
201202
else:
202203
returnzeroresp
203204

205+
defispassive(self):
206+
# importing here prevents circular dependancy
207+
fromcontrol.passivityimportispassive
208+
returnispassive(self)
209+
204210
#
205211
# Deprecated functions
206212
#
@@ -321,7 +327,7 @@ def damp(sys, doprint=True):
321327
wn,damping,poles=sys.damp()
322328
ifdoprint:
323329
print('_____Eigenvalue______ Damping___ Frequency_')
324-
forp,d,winzip(poles,damping,wn):
330+
forp,d,winzip(poles,damping,wn):
325331
ifabs(p.imag)<1e-12:
326332
print("%10.4g %10.4g %10.4g"%
327333
(p.real,1.0,-p.real))
@@ -330,6 +336,7 @@ def damp(sys, doprint=True):
330336
(p.real,p.imag,d,w))
331337
returnwn,damping,poles
332338

339+
333340
defevalfr(sys,x,squeeze=None):
334341
"""Evaluate the transfer function of an LTI system for complex frequency x.
335342
@@ -388,6 +395,7 @@ def evalfr(sys, x, squeeze=None):
388395
"""
389396
returnsys.__call__(x,squeeze=squeeze)
390397

398+
391399
deffrequency_response(sys,omega,squeeze=None):
392400
"""Frequency response of an LTI system at multiple angular frequencies.
393401

‎control/passivity.py

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
'''
2+
Author: Mark Yeatman
3+
Date: May 15, 2022
4+
'''
5+
6+
importnumpyasnp
7+
fromcontrolimportstatespasss
8+
9+
try:
10+
importcvxoptascvx
11+
exceptImportErrorase:
12+
cvx=None
13+
14+
15+
defispassive(sys):
16+
'''
17+
Indicates if a linear time invariant (LTI) system is passive
18+
19+
Constructs a linear matrix inequality and a feasibility optimization
20+
such that if a solution exists, the system is passive.
21+
22+
The source for the algorithm is:
23+
McCourt, Michael J., and Panos J. Antsaklis.
24+
"Demonstrating passivity and dissipativity using computational methods." ISIS 8 (2013).
25+
26+
Parameters
27+
----------
28+
sys: A continuous LTI system
29+
System to be checked.
30+
31+
Returns
32+
-------
33+
bool:
34+
The input system passive.
35+
'''
36+
ifcvxisNone:
37+
raiseModuleNotFoundError("cvxopt required for passivity module")
38+
39+
sys=ss._convert_to_statespace(sys)
40+
41+
A=sys.A
42+
B=sys.B
43+
C=sys.C
44+
D=sys.D
45+
46+
# account for strictly proper systems
47+
[n,m]=D.shape
48+
D=D+np.nextafter(0,1)*np.eye(n,m)
49+
50+
[n,_]=A.shape
51+
A=A-np.nextafter(0,1)*np.eye(n)
52+
53+
defmake_LMI_matrix(P):
54+
V=np.vstack((
55+
np.hstack((A.T @P+P@A,P@B)),
56+
np.hstack((B.T@P,np.zeros_like(D))))
57+
)
58+
returnV
59+
60+
matrix_list= []
61+
state_space_size=sys.nstates
62+
foriinrange(0,state_space_size):
63+
forjinrange(0,state_space_size):
64+
ifj<=i:
65+
P=np.zeros_like(A)
66+
P[i,j]=1.0
67+
P[j,i]=1.0
68+
matrix_list.append(make_LMI_matrix(P).flatten())
69+
70+
coefficents=np.vstack(matrix_list).T
71+
72+
constants=-np.vstack((
73+
np.hstack((np.zeros_like(A),-C.T)),
74+
np.hstack((-C,-D-D.T)))
75+
)
76+
77+
number_of_opt_vars=int(
78+
(state_space_size**2-state_space_size)/2+state_space_size)
79+
c=cvx.matrix(0.0, (number_of_opt_vars,1))
80+
81+
# crunch feasibility solution
82+
cvx.solvers.options['show_progress']=False
83+
sol=cvx.solvers.sdp(c,
84+
Gs=[cvx.matrix(coefficents)],
85+
hs=[cvx.matrix(constants)])
86+
87+
return (sol["x"]isnotNone)

‎control/tests/conftest.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
# pytest.param(marks=)
1818
slycotonly=pytest.mark.skipif(notcontrol.exception.slycot_check(),
1919
reason="slycot not installed")
20+
cvxoptonly=pytest.mark.skipif(notcontrol.exception.cvxopt_check(),
21+
reason="cvxopt not installed")
2022
matrixfilter=pytest.mark.filterwarnings("ignore:.*matrix subclass:"
2123
"PendingDeprecationWarning")
2224
matrixerrorfilter=pytest.mark.filterwarnings("error:.*matrix subclass:"

‎control/tests/passivity_test.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
'''
2+
Author: Mark Yeatman
3+
Date: May 30, 2022
4+
'''
5+
importpytest
6+
importnumpy
7+
fromcontrolimportss,passivity,tf
8+
fromcontrol.tests.conftestimportcvxoptonly
9+
10+
11+
pytestmark=cvxoptonly
12+
13+
14+
deftest_ispassive():
15+
A=numpy.array([[0,1], [-2,-2]])
16+
B=numpy.array([[0], [1]])
17+
C=numpy.array([[-1,2]])
18+
D=numpy.array([[1.5]])
19+
sys=ss(A,B,C,D)
20+
21+
# happy path is passive
22+
assert(passivity.ispassive(sys))
23+
24+
# happy path not passive
25+
D=-D
26+
sys=ss(A,B,C,D)
27+
28+
assert(notpassivity.ispassive(sys))
29+
30+
31+
A_d=numpy.array([[-2,0], [0,0]])
32+
A=numpy.array([[-3,0], [0,-2]])
33+
B=numpy.array([[0], [1]])
34+
C=numpy.array([[-1,2]])
35+
D=numpy.array([[1.5]])
36+
37+
38+
@pytest.mark.parametrize(
39+
"test_input,expected",
40+
[((A,B,C,D*0.0),True),
41+
((A_d,B,C,D),True),
42+
((A*1e12,B,C,D*0),True),
43+
((A,B*0,C*0,D),True),
44+
((A*0,B,C,D),True),
45+
((A*0,B*0,C*0,D*0),True)])
46+
deftest_ispassive_edge_cases(test_input,expected):
47+
48+
# strictly proper
49+
A=test_input[0]
50+
B=test_input[1]
51+
C=test_input[2]
52+
D=test_input[3]
53+
sys=ss(A,B,C,D)
54+
assert(passivity.ispassive(sys)==expected)
55+
56+
57+
deftest_transfer_function():
58+
sys=tf([1], [1,2])
59+
assert(passivity.ispassive(sys))
60+
61+
sys=tf([1], [1,-2])
62+
assert(notpassivity.ispassive(sys))
63+
64+
65+
deftest_oo_style():
66+
A=numpy.array([[0,1], [-2,-2]])
67+
B=numpy.array([[0], [1]])
68+
C=numpy.array([[-1,2]])
69+
D=numpy.array([[1.5]])
70+
sys=ss(A,B,C,D)
71+
assert(sys.ispassive())
72+
73+
sys=tf([1], [1,2])
74+
assert(sys.ispassive())

‎setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@
4646
'matplotlib'],
4747
extras_require={
4848
'test': ['pytest','pytest-timeout'],
49-
'slycot': ['slycot>=0.4.0' ]
49+
'slycot': ['slycot>=0.4.0' ],
50+
'cvxopt': ['cvxopt>=1.2.0' ]
5051
}
5152
)

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp