@@ -15,6 +15,7 @@ def sys_dict():
1515 sdict = {}
1616 sdict ['ss' ] = ct .ss ([[- 1 ]], [[1 ]], [[1 ]], [[0 ]])
1717 sdict ['tf' ] = ct .tf ([1 ],[0.5 , 1 ])
18+ sdict ['tfx' ] = ct .tf ([1 , 1 ],[1 ]) # non-proper transfer function
1819 sdict ['frd' ] = ct .frd ([10 + 0j , 9 + 1j , 8 + 2j ], [1 ,2 ,3 ])
1920 sdict ['lio' ] = ct .LinearIOSystem (ct .ss ([[- 1 ]], [[5 ]], [[5 ]], [[0 ]]))
2021 sdict ['ios' ] = ct .NonlinearIOSystem (
@@ -29,7 +30,7 @@ def sys_dict():
2930 'ios' : ct .InterconnectedSystem , 'arr' : np .ndarray , 'flt' : float }
3031
3132#
32- # Table of expected conversions
33+ # Current table of expected conversions
3334#
3435# This table describes all of the conversions that are supposed to
3536# happen for various system combinations. This is written out this way
@@ -50,6 +51,10 @@ def sys_dict():
5051# Note 2: eventually the operator entry for this table can be pulled out and
5152# tested as a separate parameterized variable (since all operators should
5253# return consistent values).
54+ #
55+ # Note 3: this table documents the current state, but not actually the desired
56+ # state. See bottom of the file for the (eventual) desired behavior.
57+ #
5358
5459rtype_list = ['ss' , 'tf' , 'frd' , 'lio' , 'ios' , 'arr' , 'flt' ]
5560conversion_table = [
@@ -97,7 +102,7 @@ def sys_dict():
97102 test_matrix .append ([opname , ltype , rtype , expected ])
98103
99104@pytest .mark .parametrize ("opname, ltype, rtype, expected" , test_matrix )
100- def test_xferfcn_ndarray_precedence (opname , ltype , rtype , expected , sys_dict ):
105+ def test_operator_type_conversion (opname , ltype , rtype , expected , sys_dict ):
101106 op = getattr (operator , opname )
102107 leftsys = sys_dict [ltype ]
103108 rightsys = sys_dict [rtype ]
@@ -117,3 +122,66 @@ def test_xferfcn_ndarray_precedence(opname, ltype, rtype, expected, sys_dict):
117122
118123 # Print out what we are testing in case something goes wrong
119124 assert isinstance (result , type_dict [expected ])
125+
126+ #
127+ # Updated table that describes desired outputs for all operators
128+ #
129+ # General rules (subject to change)
130+ #
131+ # * For LTI/LTI, keep the type of the left operand whenever possible. This
132+ # prioritizes the first operand, but we need to watch out for non-proper
133+ # transfer functions (in which case TransferFunction should be returned)
134+ #
135+ # * For FRD/LTI, convert LTI to FRD by evaluating the LTI transfer function
136+ # at the FRD frequencies (can't got the other way since we can't convert
137+ # an FRD object to state space/transfer function).
138+ #
139+ # * For IOS/LTI, convert to IOS. In the case of a linear I/O system (LIO),
140+ # this will preserve the linear structure since the LTI system will
141+ # be converted to state space.
142+ #
143+ # * When combining state space or transfer with linear I/O systems, the
144+ # * output should be of type Linear IO system, since that maintains the
145+ # * underlying state space attributes.
146+ #
147+ # Note: tfx = non-proper transfer function, order(num) > order(den)
148+ #
149+
150+ type_list = ['ss' , 'tf' , 'tfx' , 'frd' , 'lio' , 'ios' , 'arr' , 'flt' ]
151+ conversion_table = [
152+ # L / R ['ss', 'tf', 'tfx', 'frd', 'lio', 'ios', 'arr', 'flt']
153+ ('ss' , ['ss' , 'ss' , 'tf' 'frd' , 'lio' , 'ios' , 'ss' , 'ss' ]),
154+ ('tf' , ['tf' , 'tf' , 'tf' 'frd' , 'lio' , 'ios' , 'tf' , 'tf' ]),
155+ ('tfx' , ['tf' , 'tf' , 'tf' , 'frd' , 'E' , 'E' , 'tf' , 'tf' ]),
156+ ('frd' , ['frd' , 'frd' , 'frd' , 'frd' , 'E' , 'E' , 'frd' , 'frd' ]),
157+ ('lio' , ['lio' , 'lio' , 'E' , 'E' , 'lio' , 'ios' , 'lio' , 'lio' ]),
158+ ('ios' , ['ios' , 'ios' , 'E' , 'E' , 'ios' , 'ios' , 'ios' , 'ios' ]),
159+ ('arr' , ['ss' , 'tf' , 'tf' 'frd' , 'lio' , 'ios' , 'arr' , 'arr' ]),
160+ ('flt' , ['ss' , 'tf' , 'tf' 'frd' , 'lio' , 'ios' , 'arr' , 'flt' ])]
161+
162+ @pytest .mark .skip (reason = "future test; conversions not yet fully implemented" )
163+ # @pytest.mark.parametrize("opname", ['add', 'sub', 'mul', 'truediv'])
164+ # @pytest.mark.parametrize("ltype", type_list)
165+ # @pytest.mark.parametrize("rtype", type_list)
166+ def test_binary_op_type_conversions (opname , ltype , rtype , sys_dict ):
167+ op = getattr (operator , opname )
168+ leftsys = sys_dict [ltype ]
169+ rightsys = sys_dict [rtype ]
170+ expected = \
171+ conversion_table [type_list .index (ltype )][1 ][type_list .index (rtype )]
172+
173+ # Get rid of warnings for InputOutputSystem objects by making a copy
174+ if isinstance (leftsys , ct .InputOutputSystem ) and leftsys == rightsys :
175+ rightsys = leftsys .copy ()
176+
177+ # Make sure we get the right result
178+ if expected == 'E' or expected [0 ] == 'x' :
179+ # Exception expected
180+ with pytest .raises (TypeError ):
181+ op (leftsys , rightsys )
182+ else :
183+ # Operation should work and return the given type
184+ result = op (leftsys , rightsys )
185+
186+ # Print out what we are testing in case something goes wrong
187+ assert isinstance (result , type_dict [expected ])
0 commit comments