diff --git a/control/iosys.py b/control/iosys.py index 28c6f2632..2c9e3aba5 100644 --- a/control/iosys.py +++ b/control/iosys.py @@ -32,6 +32,7 @@ from warnings import warn from .statesp import StateSpace, tf2ss, _convert_to_statespace +from .xferfcn import TransferFunction from .timeresp import _check_convert_array, _process_time_response, \ TimeResponseData from .lti import isctime, isdtime, common_timebase @@ -120,6 +121,9 @@ class for a set of subclasses that are used to implement specific """ + # Allow ndarray * InputOutputSystem to give IOSystem._rmul_() priority + __array_priority__ = 12 # override ndarray, matrix, SS types + _idCounter = 0 def _name_or_default(self, name=None): @@ -195,14 +199,19 @@ def __str__(self): def __mul__(sys2, sys1): """Multiply two input/output systems (series interconnection)""" + # Note: order of arguments is flipped so that self = sys2, + # corresponding to the ordering convention of sys2 * sys1 + # Convert sys1 to an I/O system if needed if isinstance(sys1, (int, float, np.number)): - # TODO: Scale the output - raise NotImplemented("Scalar multiplication not yet implemented") + sys1 = LinearIOSystem(StateSpace( + [], [], [], sys1 * np.eye(sys2.ninputs))) elif isinstance(sys1, np.ndarray): - # TODO: Post-multiply by a matrix - raise NotImplemented("Matrix multiplication not yet implemented") + sys1 = LinearIOSystem(StateSpace([], [], [], sys1)) + + elif isinstance(sys1, (StateSpace, TransferFunction)): + sys1 = LinearIOSystem(sys1) elif not isinstance(sys1, InputOutputSystem): raise TypeError("Unknown I/O system object ", sys1) @@ -239,42 +248,41 @@ def __mul__(sys2, sys1): def __rmul__(sys1, sys2): """Pre-multiply an input/output systems by a scalar/matrix""" - if isinstance(sys2, InputOutputSystem): - # Both systems are InputOutputSystems => use __mul__ - return InputOutputSystem.__mul__(sys2, sys1) - - elif isinstance(sys2, (int, float, np.number)): - # TODO: Scale the output - raise NotImplemented("Scalar multiplication not yet implemented") + # Convert sys2 to an I/O system if needed + if isinstance(sys2, (int, float, np.number)): + sys2 = LinearIOSystem(StateSpace( + [], [], [], sys2 * np.eye(sys1.noutputs))) elif isinstance(sys2, np.ndarray): - # TODO: Post-multiply by a matrix - raise NotImplemented("Matrix multiplication not yet implemented") + sys2 = LinearIOSystem(StateSpace([], [], [], sys2)) - elif isinstance(sys2, StateSpace): - # TODO: Should eventuall preserve LinearIOSystem structure - return StateSpace.__mul__(sys2, sys1) + elif isinstance(sys2, (StateSpace, TransferFunction)): + sys2 = LinearIOSystem(sys2) - else: - raise TypeError("Unknown I/O system object ", sys1) + elif not isinstance(sys2, InputOutputSystem): + raise TypeError("Unknown I/O system object ", sys2) + + return InputOutputSystem.__mul__(sys2, sys1) def __add__(sys1, sys2): """Add two input/output systems (parallel interconnection)""" - # TODO: Allow addition of scalars and matrices + # Convert sys1 to an I/O system if needed if isinstance(sys2, (int, float, np.number)): - # TODO: Scale the output - raise NotImplemented("Scalar addition not yet implemented") + sys2 = LinearIOSystem(StateSpace( + [], [], [], sys2 * np.eye(sys1.ninputs))) elif isinstance(sys2, np.ndarray): - # TODO: Post-multiply by a matrix - raise NotImplemented("Matrix addition not yet implemented") + sys2 = LinearIOSystem(StateSpace([], [], [], sys2)) + + elif isinstance(sys2, (StateSpace, TransferFunction)): + sys2 = LinearIOSystem(sys2) elif not isinstance(sys2, InputOutputSystem): raise TypeError("Unknown I/O system object ", sys2) # Make sure number of input and outputs match if sys1.ninputs != sys2.ninputs or sys1.noutputs != sys2.noutputs: - raise ValueError("Can't add systems with different numbers of " + raise ValueError("Can't add systems with incompatible numbers of " "inputs or outputs.") ninputs = sys1.ninputs noutputs = sys1.noutputs @@ -293,16 +301,87 @@ def __add__(sys1, sys2): # Return the newly created InterconnectedSystem return newsys - # TODO: add __radd__ to allow postaddition by scalars and matrices + def __radd__(sys1, sys2): + """Parallel addition of input/output system to a compatible object.""" + # Convert sys2 to an I/O system if needed + if isinstance(sys2, (int, float, np.number)): + sys2 = LinearIOSystem(StateSpace( + [], [], [], sys2 * np.eye(sys1.noutputs))) + + elif isinstance(sys2, np.ndarray): + sys2 = LinearIOSystem(StateSpace([], [], [], sys2)) + + elif isinstance(sys2, (StateSpace, TransferFunction)): + sys2 = LinearIOSystem(sys2) + + elif not isinstance(sys2, InputOutputSystem): + raise TypeError("Unknown I/O system object ", sys2) + + return InputOutputSystem.__add__(sys2, sys1) + + def __sub__(sys1, sys2): + """Subtract two input/output systems (parallel interconnection)""" + # Convert sys1 to an I/O system if needed + if isinstance(sys2, (int, float, np.number)): + sys2 = LinearIOSystem(StateSpace( + [], [], [], sys2 * np.eye(sys1.ninputs))) + + elif isinstance(sys2, np.ndarray): + sys2 = LinearIOSystem(StateSpace([], [], [], sys2)) + + elif isinstance(sys2, (StateSpace, TransferFunction)): + sys2 = LinearIOSystem(sys2) + + elif not isinstance(sys2, InputOutputSystem): + raise TypeError("Unknown I/O system object ", sys2) + + # Make sure number of input and outputs match + if sys1.ninputs != sys2.ninputs or sys1.noutputs != sys2.noutputs: + raise ValueError("Can't add systems with incompatible numbers of " + "inputs or outputs.") + ninputs = sys1.ninputs + noutputs = sys1.noutputs + + # Create a new system to handle the composition + inplist = [[(0, i), (1, i)] for i in range(ninputs)] + outlist = [[(0, i), (1, i, -1)] for i in range(noutputs)] + newsys = InterconnectedSystem( + (sys1, sys2), inplist=inplist, outlist=outlist) + + # If both systems are linear, create LinearICSystem + if isinstance(sys1, StateSpace) and isinstance(sys2, StateSpace): + ss_sys = StateSpace.__sub__(sys1, sys2) + return LinearICSystem(newsys, ss_sys) + + # Return the newly created InterconnectedSystem + return newsys + + def __rsub__(sys1, sys2): + """Parallel subtraction of I/O system to a compatible object.""" + # Convert sys2 to an I/O system if needed + if isinstance(sys2, (int, float, np.number)): + sys2 = LinearIOSystem(StateSpace( + [], [], [], sys2 * np.eye(sys1.noutputs))) + + elif isinstance(sys2, np.ndarray): + sys2 = LinearIOSystem(StateSpace([], [], [], sys2)) + + elif isinstance(sys2, (StateSpace, TransferFunction)): + sys2 = LinearIOSystem(sys2) + + elif not isinstance(sys2, InputOutputSystem): + raise TypeError("Unknown I/O system object ", sys2) + + return InputOutputSystem.__sub__(sys2, sys1) def __neg__(sys): """Negate an input/output systems (rescale)""" if sys.ninputs is None or sys.noutputs is None: raise ValueError("Can't determine number of inputs or outputs") + # Create a new system to hold the negation inplist = [(0, i) for i in range(sys.ninputs)] outlist = [(0, i, -1) for i in range(sys.noutputs)] - # Create a new system to hold the negation newsys = InterconnectedSystem( (sys,), dt=sys.dt, inplist=inplist, outlist=outlist) @@ -344,7 +423,7 @@ def _find_signal(self, name, sigdict): return sigdict.get(name, None) # Update parameters used for _rhs, _out (used by subclasses) def _update_params(self, params, warning=False): - if (warning): + if warning: warn("Parameters passed to InputOutputSystem ignored.") def _rhs(self, t, x, u, params={}): @@ -667,8 +746,8 @@ class LinearIOSystem(InputOutputSystem, StateSpace): Parameters ---------- - linsys : StateSpace - LTI StateSpace system to be converted + linsys : StateSpace or TransferFunction + LTI system to be converted inputs : int, list of str or None, optional Description of the system inputs. This can be given as an integer count or as a list of strings that name the individual signals. If an @@ -711,12 +790,17 @@ def __init__(self, linsys, inputs=None, outputs=None, states=None, states. The new system can be a continuous or discrete time system. """ - if not isinstance(linsys, StateSpace): - raise TypeError("Linear I/O system must be a state space object") + if isinstance(linsys, TransferFunction): + # Convert system to StateSpace + linsys = _convert_to_statespace(linsys) + + elif not isinstance(linsys, StateSpace): + raise TypeError("Linear I/O system must be a state space " + "or transfer function object") # Look for 'input' and 'output' parameter name variants inputs = _parse_signal_parameter(inputs, 'input', kwargs) - outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True) + outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True) # Create the I/O system object super(LinearIOSystem, self).__init__( @@ -837,7 +921,7 @@ def __init__(self, updfcn, outfcn=None, inputs=None, outputs=None, """Create a nonlinear I/O system given update and output functions.""" # Look for 'input' and 'output' parameter name variants inputs = _parse_signal_parameter(inputs, 'input', kwargs) - outputs = _parse_signal_parameter(outputs, 'output', kwargs) + outputs = _parse_signal_parameter(outputs, 'output', kwargs) # Store the update and output functions self.updfcn = updfcn @@ -1399,13 +1483,12 @@ def set_output_map(self, output_map): self.output_map = output_map self.noutputs = output_map.shape[0] - def unused_signals(self): """Find unused subsystem inputs and outputs Returns ------- - + unused_inputs : dict A mapping from tuple of indices (isys, isig) to string @@ -1430,66 +1513,61 @@ def unused_signals(self): unused_sysinp = sorted(set(range(nsubsysinp)) - used_sysinp) unused_sysout = sorted(set(range(nsubsysout)) - used_sysout) - inputs = [(isys,isig, f'{sys.name}.{sig}') + inputs = [(isys, isig, f'{sys.name}.{sig}') for isys, sys in enumerate(self.syslist) for sig, isig in sys.input_index.items()] - outputs = [(isys,isig,f'{sys.name}.{sig}') + outputs = [(isys, isig, f'{sys.name}.{sig}') for isys, sys in enumerate(self.syslist) for sig, isig in sys.output_index.items()] - return ({inputs[i][:2]:inputs[i][2] - for i in unused_sysinp}, - {outputs[i][:2]:outputs[i][2] - for i in unused_sysout}) - + return ({inputs[i][:2]: inputs[i][2] for i in unused_sysinp}, + {outputs[i][:2]: outputs[i][2] for i in unused_sysout}) def _find_inputs_by_basename(self, basename): """Find all subsystem inputs matching basename Returns ------- - Mapping from (isys, isig) to '{sys}.{sig}' + Mapping from (isys, isig) to '{sys}.{sig}' """ - return {(isys, isig) : f'{sys.name}.{basename}' + return {(isys, isig): f'{sys.name}.{basename}' for isys, sys in enumerate(self.syslist) for sig, isig in sys.input_index.items() if sig == (basename)} - def _find_outputs_by_basename(self, basename): """Find all subsystem outputs matching basename Returns ------- - Mapping from (isys, isig) to '{sys}.{sig}' + Mapping from (isys, isig) to '{sys}.{sig}' """ - return {(isys, isig) : f'{sys.name}.{basename}' + return {(isys, isig): f'{sys.name}.{basename}' for isys, sys in enumerate(self.syslist) for sig, isig in sys.output_index.items() if sig == (basename)} - def check_unused_signals(self, ignore_inputs=None, ignore_outputs=None): """Check for unused subsystem inputs and outputs If any unused inputs or outputs are found, emit a warning. - + Parameters ---------- ignore_inputs : list of input-spec Subsystem inputs known to be unused. input-spec can be any of: 'sig', 'sys.sig', (isys, isig), ('sys', isig) - + If the 'sig' form is used, all subsystem inputs with that name are considered ignored. ignore_outputs : list of output-spec Subsystem outputs known to be unused. output-spec can be any of: 'sig', 'sys.sig', (isys, isig), ('sys', isig) - + If the 'sig' form is used, all subsystem outputs with that name are considered ignored. @@ -1509,10 +1587,12 @@ def check_unused_signals(self, ignore_inputs=None, ignore_outputs=None): if isinstance(ignore_input, str) and '.' not in ignore_input: ignore_idxs = self._find_inputs_by_basename(ignore_input) if not ignore_idxs: - raise ValueError(f"Couldn't find ignored input {ignore_input} in subsystems") + raise ValueError("Couldn't find ignored input " + f"{ignore_input} in subsystems") ignore_input_map.update(ignore_idxs) else: - ignore_input_map[self._parse_signal(ignore_input, 'input')[:2]] = ignore_input + ignore_input_map[self._parse_signal( + ignore_input, 'input')[:2]] = ignore_input # (isys, isig) -> signal-spec ignore_output_map = {} @@ -1520,16 +1600,18 @@ def check_unused_signals(self, ignore_inputs=None, ignore_outputs=None): if isinstance(ignore_output, str) and '.' not in ignore_output: ignore_found = self._find_outputs_by_basename(ignore_output) if not ignore_found: - raise ValueError(f"Couldn't find ignored output {ignore_output} in subsystems") + raise ValueError("Couldn't find ignored output " + f"{ignore_output} in subsystems") ignore_output_map.update(ignore_found) else: - ignore_output_map[self._parse_signal(ignore_output, 'output')[:2]] = ignore_output + ignore_output_map[self._parse_signal( + ignore_output, 'output')[:2]] = ignore_output dropped_inputs = set(unused_inputs) - set(ignore_input_map) dropped_outputs = set(unused_outputs) - set(ignore_output_map) used_ignored_inputs = set(ignore_input_map) - set(unused_inputs) - used_ignored_outputs = set(ignore_output_map) - set(unused_outputs) + used_ignored_outputs = set(ignore_output_map) - set(unused_outputs) if dropped_inputs: msg = ('Unused input(s) in InterconnectedSystem: ' @@ -2170,7 +2252,7 @@ def _find_size(sysval, vecval): """ if hasattr(vecval, '__len__'): if sysval is not None and sysval != len(vecval): - raise ValueError("Inconsistend information to determine size " + raise ValueError("Inconsistent information to determine size " "of system component") return len(vecval) # None or 0, which is a valid value for "a (sysval, ) vector of zeros". @@ -2407,7 +2489,7 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[], """ # Look for 'input' and 'output' parameter name variants inputs = _parse_signal_parameter(inputs, 'input', kwargs) - outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True) + outputs = _parse_signal_parameter(outputs, 'output', kwargs, end=True) if not check_unused and (ignore_inputs or ignore_outputs): raise ValueError('check_unused is False, but either ' @@ -2507,7 +2589,6 @@ def interconnect(syslist, connections=None, inplist=[], outlist=[], inputs=inputs, outputs=outputs, states=states, params=params, dt=dt, name=name) - # check for implicity dropped signals if check_unused: newsys.check_unused_signals(ignore_inputs, ignore_outputs) @@ -2598,7 +2679,7 @@ def _parse_list(signals, signame='input', prefix='u'): # Look for 'input' and 'output' parameter name variants inputs = _parse_signal_parameter(inputs, 'input', kwargs) - output = _parse_signal_parameter(output, 'outputs', kwargs, end=True) + output = _parse_signal_parameter(output, 'outputs', kwargs, end=True) # Default values for inputs and output if inputs is None: @@ -2623,8 +2704,8 @@ def _parse_list(signals, signame='input', prefix='u'): ninputs = ninputs * dimension output_names = ["%s[%d]" % (name, dim) - for name in output_names - for dim in range(dimension)] + for name in output_names + for dim in range(dimension)] noutputs = noutputs * dimension elif dimension is not None: raise ValueError( diff --git a/control/tests/iosys_test.py b/control/tests/iosys_test.py index ba56fcea3..4c8001797 100644 --- a/control/tests/iosys_test.py +++ b/control/tests/iosys_test.py @@ -1,4 +1,4 @@ -"""iosys_test.py - test input/output system oeprations +"""iosys_test.py - test input/output system operations RMM, 17 Apr 2019 @@ -595,6 +595,58 @@ def test_bdalg_functions(self, tsys): ios_t, ios_y = ios.input_output_response(iosys_feedback, T, U, X0) np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.) + @noscipy0 + def test_algebraic_functions(self, tsys): + """Test algebraic operations on I/O systems""" + # Set up parameters for simulation + T = tsys.T + U = [np.sin(T), np.cos(T)] + X0 = 0 + + # Set up systems to be composed + linsys1 = tsys.mimo_linsys1 + linio1 = ios.LinearIOSystem(linsys1) + linsys2 = tsys.mimo_linsys2 + linio2 = ios.LinearIOSystem(linsys2) + + # Multiplication + linsys_mul = linsys2 * linsys1 + iosys_mul = linio2 * linio1 + lin_t, lin_y = ct.forced_response(linsys_mul, T, U, X0) + ios_t, ios_y = ios.input_output_response(iosys_mul, T, U, X0) + np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.) + + # Make sure that systems don't commute + linsys_mul = linsys1 * linsys2 + lin_t, lin_y = ct.forced_response(linsys_mul, T, U, X0) + assert not (np.abs(lin_y - ios_y) < 1e-3).all() + + # Addition + linsys_add = linsys1 + linsys2 + iosys_add = linio1 + linio2 + lin_t, lin_y = ct.forced_response(linsys_add, T, U, X0) + ios_t, ios_y = ios.input_output_response(iosys_add, T, U, X0) + np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.) + + # Subtraction + linsys_sub = linsys1 - linsys2 + iosys_sub = linio1 - linio2 + lin_t, lin_y = ct.forced_response(linsys_sub, T, U, X0) + ios_t, ios_y = ios.input_output_response(iosys_sub, T, U, X0) + np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.) + + # Make sure that systems don't commute + linsys_sub = linsys2 - linsys1 + lin_t, lin_y = ct.forced_response(linsys_sub, T, U, X0) + assert not (np.abs(lin_y - ios_y) < 1e-3).all() + + # Negation + linsys_negate = -linsys1 + iosys_negate = -linio1 + lin_t, lin_y = ct.forced_response(linsys_negate, T, U, X0) + ios_t, ios_y = ios.input_output_response(iosys_negate, T, U, X0) + np.testing.assert_allclose(ios_y, lin_y,atol=0.002,rtol=0.) + @noscipy0 def test_nonsquare_bdalg(self, tsys): # Set up parameters for simulation @@ -1196,6 +1248,91 @@ def test_lineariosys_statespace(self, tsys): np.testing.assert_allclose(io_series.C, ss_series.C) np.testing.assert_allclose(io_series.D, ss_series.D) + @pytest.mark.parametrize( + "Pout, Pin, C, op, PCout, PCin", [ + (2, 2, 'rss', ct.LinearIOSystem.__mul__, 2, 2), + (2, 2, 2, ct.LinearIOSystem.__mul__, 2, 2), + (2, 3, 2, ct.LinearIOSystem.__mul__, 2, 3), + (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__mul__, 2, 2), + (2, 2, 'rss', ct.LinearIOSystem.__rmul__, 2, 2), + (2, 2, 2, ct.LinearIOSystem.__rmul__, 2, 2), + (2, 3, 2, ct.LinearIOSystem.__rmul__, 2, 3), + (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__rmul__, 2, 2), + (2, 2, 'rss', ct.LinearIOSystem.__add__, 2, 2), + (2, 2, 2, ct.LinearIOSystem.__add__, 2, 2), + (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__add__, 2, 2), + (2, 2, 'rss', ct.LinearIOSystem.__radd__, 2, 2), + (2, 2, 2, ct.LinearIOSystem.__radd__, 2, 2), + (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__radd__, 2, 2), + (2, 2, 'rss', ct.LinearIOSystem.__sub__, 2, 2), + (2, 2, 2, ct.LinearIOSystem.__sub__, 2, 2), + (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__sub__, 2, 2), + (2, 2, 'rss', ct.LinearIOSystem.__rsub__, 2, 2), + (2, 2, 2, ct.LinearIOSystem.__rsub__, 2, 2), + (2, 2, np.random.rand(2, 2), ct.LinearIOSystem.__rsub__, 2, 2), + + ]) + def test_operand_conversion(self, Pout, Pin, C, op, PCout, PCin): + P = ct.LinearIOSystem( + ct.rss(2, Pout, Pin, strictly_proper=True), name='P') + if isinstance(C, str) and C == 'rss': + # Need to generate inside class to avoid matrix deprecation error + C = ct.rss(2, 2, 2) + PC = op(P, C) + assert isinstance(PC, ct.LinearIOSystem) + assert isinstance(PC, ct.StateSpace) + assert PC.noutputs == PCout + assert PC.ninputs == PCin + + @pytest.mark.parametrize( + "Pout, Pin, C, op", [ + (2, 2, 'rss32', ct.LinearIOSystem.__mul__), + (2, 2, 'rss23', ct.LinearIOSystem.__rmul__), + (2, 2, 'rss32', ct.LinearIOSystem.__add__), + (2, 2, 'rss23', ct.LinearIOSystem.__radd__), + (2, 3, 2, ct.LinearIOSystem.__add__), + (2, 3, 2, ct.LinearIOSystem.__radd__), + (2, 2, 'rss32', ct.LinearIOSystem.__sub__), + (2, 2, 'rss23', ct.LinearIOSystem.__rsub__), + (2, 3, 2, ct.LinearIOSystem.__sub__), + (2, 3, 2, ct.LinearIOSystem.__rsub__), + ]) + def test_operand_incompatible(self, Pout, Pin, C, op): + P = ct.LinearIOSystem( + ct.rss(2, Pout, Pin, strictly_proper=True), name='P') + if isinstance(C, str) and C == 'rss32': + C = ct.rss(2, 3, 2) + elif isinstance(C, str) and C == 'rss23': + C = ct.rss(2, 2, 3) + with pytest.raises(ValueError, match="incompatible"): + PC = op(P, C) + + @pytest.mark.parametrize( + "C, op", [ + (None, ct.LinearIOSystem.__mul__), + (None, ct.LinearIOSystem.__rmul__), + (None, ct.LinearIOSystem.__add__), + (None, ct.LinearIOSystem.__radd__), + (None, ct.LinearIOSystem.__sub__), + (None, ct.LinearIOSystem.__rsub__), + ]) + def test_operand_badtype(self, C, op): + P = ct.LinearIOSystem( + ct.rss(2, 2, 2, strictly_proper=True), name='P') + with pytest.raises(TypeError, match="Unknown"): + op(P, C) + + def test_neg_badsize(self): + # Create a system of unspecified size + sys = ct.InputOutputSystem() + with pytest.raises(ValueError, match="Can't determine"): + -sys + + def test_bad_signal_list(self): + # Create a ystem with a bad signal list + with pytest.raises(TypeError, match="Can't parse"): + ct.InputOutputSystem(inputs=[1, 2, 3]) + def test_docstring_example(self): P = ct.LinearIOSystem( ct.rss(2, 2, 2, strictly_proper=True), name='P') diff --git a/control/tests/type_conversion_test.py b/control/tests/type_conversion_test.py index 3f51c2bbc..dadcc587e 100644 --- a/control/tests/type_conversion_test.py +++ b/control/tests/type_conversion_test.py @@ -62,28 +62,28 @@ def sys_dict(): ('add', 'ss', ['ss', 'ss', 'xrd', 'ss', 'xos', 'ss', 'ss' ]), ('add', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]), ('add', 'frd', ['xrd', 'xrd', 'frd', 'xrd', 'E', 'xrd', 'xrd']), - ('add', 'lio', ['xio', 'xio', 'xrd', 'lio', 'ios', 'xio', 'xio']), - ('add', 'ios', ['xos', 'xos', 'E', 'ios', 'ios', 'xos', 'xos']), - ('add', 'arr', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'arr']), - ('add', 'flt', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'flt']), + ('add', 'lio', ['lio', 'lio', 'xrd', 'lio', 'ios', 'lio', 'lio']), + ('add', 'ios', ['ios', 'ios', 'E', 'ios', 'ios', 'ios', 'ios']), + ('add', 'arr', ['ss', 'tf', 'xrd', 'lio', 'ios', 'arr', 'arr']), + ('add', 'flt', ['ss', 'tf', 'xrd', 'lio', 'ios', 'arr', 'flt']), # op left ss tf frd lio ios arr flt ('sub', 'ss', ['ss', 'ss', 'xrd', 'ss', 'xos', 'ss', 'ss' ]), ('sub', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]), ('sub', 'frd', ['xrd', 'xrd', 'frd', 'xrd', 'E', 'xrd', 'xrd']), - ('sub', 'lio', ['xio', 'xio', 'xrd', 'lio', 'ios', 'xio', 'xio']), - ('sub', 'ios', ['xos', 'xio', 'E', 'ios', 'xos' 'xos', 'xos']), - ('sub', 'arr', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'arr']), - ('sub', 'flt', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'flt']), + ('sub', 'lio', ['lio', 'lio', 'xrd', 'lio', 'ios', 'lio', 'lio']), + ('sub', 'ios', ['ios', 'ios', 'E', 'ios', 'ios', 'ios', 'ios']), + ('sub', 'arr', ['ss', 'tf', 'xrd', 'lio', 'ios', 'arr', 'arr']), + ('sub', 'flt', ['ss', 'tf', 'xrd', 'lio', 'ios', 'arr', 'flt']), # op left ss tf frd lio ios arr flt ('mul', 'ss', ['ss', 'ss', 'xrd', 'ss', 'xos', 'ss', 'ss' ]), ('mul', 'tf', ['tf', 'tf', 'xrd', 'tf', 'xos', 'tf', 'tf' ]), ('mul', 'frd', ['xrd', 'xrd', 'frd', 'xrd', 'E', 'xrd', 'frd']), - ('mul', 'lio', ['xio', 'xio', 'xrd', 'lio', 'ios', 'xio', 'xio']), - ('mul', 'ios', ['xos', 'xos', 'E', 'ios', 'ios', 'xos', 'xos']), - ('mul', 'arr', ['ss', 'tf', 'xrd', 'xio', 'xos', 'arr', 'arr']), - ('mul', 'flt', ['ss', 'tf', 'frd', 'xio', 'xos', 'arr', 'flt']), + ('mul', 'lio', ['lio', 'lio', 'xrd', 'lio', 'ios', 'lio', 'lio']), + ('mul', 'ios', ['ios', 'ios', 'E', 'ios', 'ios', 'ios', 'ios']), + ('mul', 'arr', ['ss', 'tf', 'xrd', 'lio', 'ios', 'arr', 'arr']), + ('mul', 'flt', ['ss', 'tf', 'frd', 'lio', 'ios', 'arr', 'flt']), # op left ss tf frd lio ios arr flt ('truediv', 'ss', ['xs', 'tf', 'xrd', 'xio', 'xos', 'xs', 'xs' ]), pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy