Skip to content

Conversation

namannimmo10
Copy link
Member

@namannimmo10 namannimmo10 commented Jul 14, 2020

References to other Issues or PRs

#19352

Brief description of what is fixed or changed

This PR introduces MIMO transfer functions in sympy.physics.control.

Other comments

Release Notes

  • physics.control
    • Added TransferFunctionMatrix class in physics.control

@sympy-bot
Copy link

sympy-bot commented Jul 14, 2020

Hi, I am the SymPy bot (v161). I'm here to help you write a release notes entry. Please read the guide on how to write release notes.

Your release notes are in good order.

Here is what the release notes will look like:

  • physics.control

This will be added to https://github.com/sympy/sympy/wiki/Release-Notes-for-1.9.

Click here to see the pull request description that was parsed.
<!-- Your title above should be a short description of what
was changed. Do not include the issue number in the title. -->

#### References to other Issues or PRs
<!-- If this pull request fixes an issue, write "Fixes #NNNN" in that exact
format, e.g. "Fixes #1234" (see
https://tinyurl.com/auto-closing for more information). Also, please
write a comment on that issue linking back to this pull request once it is
open. -->
https://github.com/sympy/sympy/issues/19352

#### Brief description of what is fixed or changed
This PR introduces MIMO transfer functions in `sympy.physics.control`.

#### Other comments


#### Release Notes

<!-- Write the release notes for this release below. See
https://github.com/sympy/sympy/wiki/Writing-Release-Notes for more information
on how to write release notes. The bot will check your release notes
automatically to see if they are formatted correctly. -->

<!-- BEGIN RELEASE NOTES -->
*  physics.control
    * Added `TransferFunctionMatrix` class in physics.control
<!-- END RELEASE NOTES -->

Update

The release notes on the wiki have been updated.

@namannimmo10 namannimmo10 marked this pull request as ready for review July 14, 2020 19:17
@namannimmo10
Copy link
Member Author

ping @ishanaj!

@namannimmo10
Copy link
Member Author

Ready for review!

@codecov
Copy link

codecov bot commented Jul 16, 2020

Codecov Report

Merging #19761 into master will increase coverage by 0.009%.
The diff coverage is 81.366%.

@@              Coverage Diff              @@
##            master    #19761       +/-   ##
=============================================
+ Coverage   75.868%   75.877%   +0.009%     
=============================================
  Files          669       669               
  Lines       173564    173811      +247     
  Branches     40969     41074      +105     
=============================================
+ Hits        131680    131884      +204     
- Misses       36147     36171       +24     
- Partials      5737      5756       +19     

@namannimmo10
Copy link
Member Author

I have added a MIMO transfer function object and its important properties. What needs to be done for the interconnection of TransferFunctionMatrix objects? We could perform in-place operations by just implementing __add__ & __mul__. But I guess mimo block diagram will not work after that. Can I start off with two new classes just like Series and Parallel in SISO TF?

Copy link
Member

@Sc0rpi0n101 Sc0rpi0n101 left a comment

Choose a reason for hiding this comment

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

If we've supported block diagram for SISO systems, I think we should also do the same for MIMO systems.

@namannimmo10
Copy link
Member Author

OK, I will start working on these classes now!

@namannimmo10
Copy link
Member Author

@moorepants, @sylee957 Can either one of you (or both) also confirm if the work done till now looks fine? Everything else is built on top of TransferFunctionMatrix.

@ilayn
Copy link

ilayn commented Jul 17, 2020

I am just passing by but I think the class name should be TransferMatrix without the Function word.

@namannimmo10
Copy link
Member Author

Hi @ilayn! Thanks for this suggestion. I just got to know about the control systems toolbox you implemented over the years - harold. My first PR for adding SISO transfer function is merged, here's the documentation. The class was named as TransferFunction, so if we change TransferFunctionMatrix to TransferMatrix, then maybe we should also rename TransferFunction to Transfer (the name used in harold for both SISO and MIMO TF).

@ilayn
Copy link

ilayn commented Jul 18, 2020

It is wonderful that you are working on this and it is such a valuable addition. I am indeed using/developing harold but I might add a disclaimer that my wording in harold is highly unconventional so please take it with some salt. It is probably better that others chime in too.

My logic is that Transfer function name is like "he is such a gentleman man" that is Transfer function is already a function no need to emphasize it. Same with state space that actually means basically R^n so again redundant wording. Hence the naming Transfer and State models. But again this is not a very popular view so you have been warned :-)

Another option is to have a single object TransferFunction and let it handle MIMO systems too to reduce clutter.

@sylee957
Copy link
Member

I think that it's different matter for naming between TransferFunction vs Transfer and TransferFunctionMatrix vs TransferMatrix because both terms Transfer Function Matrix and Transfer Matrix are used academically
https://en.wikipedia.org/wiki/Transfer_function_matrix

When I search how many IEEE papers out there for Transfer Function Matrix and Transfer Matrix
https://ieeexplore.ieee.org/search/searchresult.jsp?newsearch=true&queryText=%22Transfer%20Function%20Matrix%22
https://ieeexplore.ieee.org/search/searchresult.jsp?newsearch=true&queryText=%22Transfer%20Matrix%22
I see there Transfer Matrix is searched more

But I don't think that Transfer Matrix is better to use because of the plain reason that there are more papers using them, because the term Transfer Matrix can confuse with Transfer Matrix Method while Transfer Function Matrix is more accurate that it is used only by a handful of control theory journals.

@namannimmo10
Copy link
Member Author

What should we name these two classes for series and parallel interconnection? What about TFMSeriesConnect and TFMParallelConnect?

@ilayn
Copy link

ilayn commented Jul 21, 2020

When I search how many IEEE papers out there for Transfer Function Matrix and Transfer Matrix
https://ieeexplore.ieee.org/search/searchresult.jsp?newsearch=true&queryText=%22Transfer%20Function%20Matrix%22
https://ieeexplore.ieee.org/search/searchresult.jsp?newsearch=true&queryText=%22Transfer%20Matrix%22
I see there Transfer Matrix is searched more

I don't think IEEE is doing a good job here by returning a couple of thousand results. This terminology should in either case return way more results. And the terminology clash is sufficiently far apart, for example, I was completely immersed in control theory and not once I heard this method. But anyways, my vote goes out for having a single class with SISO/MIMO as special cases.

@eric-wieser
Copy link
Member

@oscarbenjamin

The tfm3 case doesn't seem right to me. I don't think it should be possible to have a Matrix of non-Expr transfer function objects.

Why can't TransferFunction be an Expr? If the issue is Add and Mul, then it seems that Series is like MatMul and Parallel is like MatAdd. Out of scope for this patch, but if we did that we could avoid reimplementing matrix multiplication in this PR.

raise TypeError("Var must be a Symbol, not {}.".format(type(var)))

obj = super(TransferFunctionMatrix, cls).__new__(cls, arg)
if all(isinstance(i, (TransferFunction, Series, Parallel)) for i in arg):
Copy link
Member

Choose a reason for hiding this comment

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

This path needs to check that Series and Parallel are all is_SISO, right?

Copy link
Member

@eric-wieser eric-wieser left a comment

Choose a reason for hiding this comment

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

The special-casing of num_input and num_outputs being 1 scares me, and I think it will make this fail:

a1 = TransferFunctionMatrix([-tf4], (1, 1), p)
a11 = TransferFunctionMatrix([[-tf4]], (1, 1), p)
b2 = TransferFunctionMatrix([-tf4, tf4], (2, 1), p)
b21 = TransferFunctionMatrix([[-tf4, tf4]], (2, 1), p)
c = TransferFunctionMatrix([[-tf4], [tf4]], (1, 3), p)

# all of these products should be legal
for a in [a1, a11]:
    for b in [b1, b11]:
        b * a
    a * c

@Sc0rpi0n101
Copy link
Member

before the GSoC period ends.

When is that?

August 31

@Sc0rpi0n101
Copy link
Member

@namannimmo10 can you push the work you have done on the documentation?

Also, please respond to @eric-wieser's comments. We have less than 2 days till the deadline.

@namannimmo10
Copy link
Member Author

Some of the comments are still to be addressed, and class level docstring will be added soon. (Didn't add in this commit because some API design changes are to be made).

if not isinstance(var, Symbol):
raise TypeError("Var must be a Symbol, not {}.".format(type(var)))

obj = super(TransferFunctionMatrix, cls).__new__(cls, arg)
Copy link
Member

@eric-wieser eric-wieser Aug 31, 2020

Choose a reason for hiding this comment

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

I'm starting to think you really do want to reuse the matrix classes here. So I'd propose __new__ looks like:

def __new__(cls, arg, shape, var):
    if var is not None and not isinstance(var, Symbol):
        raise TypeError("Var must be a Symbol, not {}.".format(type(var)))
    if shape is not None and not isinstance(shape, tuple):
        raise TypeError("Shape must be a tuple, not {}.".format(type(shape)))

    # let matrix handle conversion from lists
    matrix = ImmutableDenseMatrix(arg)

    # args[2] is the flattened entries
    for entry in matrix.args[2]:
        if not isinstance(entry, (TransferFunction, Parallel, Series)):
            raise TypeError("Unsupported type for argument of TransferFunctionMatrix.")
        if var is not None and entry.var != var:
            raise ValueError("All transfer functions should use the same complex"
                        " variable (var) of the Laplace transform.")
    
    if shape is not None and matrix.shape != shape:
        raise ValueError("wrong shape")  # or a better message

    obj = super(TransferFunctionMatrix, cls).__new__(cls, matrix)
    obj._var = var
    # etc
    return obj

A big advantage of this approach is that TransferFunctionMatrix.subs(some_variable, 0) will just work.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Does subs not work with this class?

There is a basic problem in sympy with allowing non-Expr (even non-Basic) instances in places such as Matrix entries that are clearly written for Expr instances. I intend to eliminate all places where non-Expr instances are used in a Matrix from the codebase and deprecate having non-Expr in a Matrix.

I don't think that it is a big deal to implement something like matrix multiplication which is fairly trivial although perhaps it would be better if there was a reusable function somewhere for doing that calculation. It is still a work in progress and not at all documented but I have implemented various matrix routines in such a way that they can be applied to a standard Python list of lists based on only assuming that the elements support the field operations +, -, * and / so do not need to Expr or even Basic. My intention was that it should not be necessary to subclass Matrix and insert objects that don't belong there just to reuse the routines for standard matrix operations. Matrix multiplication is handled by this function:

def ddm_imatmul(a, b, c):
"""a += b @ c"""
cT = list(zip(*c))
for bi, ai in zip(b, a):
for j, cTj in enumerate(cT):
ai[j] = sum(map(mul, bi, cTj), ai[j])

That implementation is optimised for large matrices and cheap elementary arithmetic (e.g. int or mpz as the elements).

Copy link
Member

Choose a reason for hiding this comment

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

Does subs not work with this class?

My impression was that subs would not recurse into list arguments, but perhaps I missed a sympify somewhere.

At any rate, my suggestion above is more about letting the matrix constructor handle the normalization, and less about the multiplication itself. The thing I don't like about this PR is how the special cases for TFM([tf]) vs TFM([[tf]]) are in every single method, when they should just be in the constructor.

Copy link
Member

@eric-wieser eric-wieser Aug 31, 2020

Choose a reason for hiding this comment

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

I intend to eliminate all places where non-Expr instances are used in a Matrix from the codebase and deprecate having non-Expr in a Matrix.

If you do this, I'd argue that it might be worth introducing a base class that works over non-exprs too. In my mind, this control stuff is really trying to work with a new TransferFunctionExpr type, just like matrices work with a Matexpr type.

Copy link
Collaborator

Choose a reason for hiding this comment

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

I'd argue that it might be worth introducing a base class

The Matrix class is already arranged into a bunch of different base classes:

In [1]: Matrix.mro()                                                                                                                                          
Out[1]: 
[sympy.matrices.dense.MutableDenseMatrix,
 sympy.matrices.dense.DenseMatrix,
 sympy.matrices.matrices.MatrixBase,
 sympy.matrices.matrices.MatrixDeprecated,
 sympy.matrices.matrices.MatrixCalculus,
 sympy.matrices.matrices.MatrixEigen,
 sympy.matrices.matrices.MatrixSubspaces,
 sympy.matrices.matrices.MatrixReductions,
 sympy.matrices.matrices.MatrixDeterminant,
 sympy.matrices.common.MatrixCommon,
 sympy.matrices.common.MatrixArithmetic,
 sympy.matrices.common.MatrixOperations,
 sympy.matrices.common.MatrixProperties,
 sympy.matrices.common.MatrixSpecial,
 sympy.matrices.common.MatrixShaping,
 sympy.matrices.common.MatrixRequired,
 sympy.printing.defaults.Printable,
 object]

It's just not really clear how you could make use of any of those classes separately.

@namannimmo10 namannimmo10 added this to the SymPy 1.7 milestone Oct 2, 2020
@Sc0rpi0n101
Copy link
Member

Hey @namannimmo10,

@oscarbenjamin is planning to release SymPy 1.7 soon. If you are free and available now, can you finish this PR so that this work can also be included in the release?

@namannimmo10
Copy link
Member Author

Thanks! Yes, I am planning to complete this PR soon.

@Sc0rpi0n101
Copy link
Member

Any progress on this @namannimmo10?

@oscarbenjamin
Copy link
Collaborator

After a significant delay I'm getting ready for the 1.7 release. Is anything going to happen in this PR soon?

@oscarbenjamin
Copy link
Collaborator

I'm removing the 1.7 milestone. Let me know if there any changes come soon that should be considered for 1.7

@oscarbenjamin oscarbenjamin removed this from the SymPy 1.7 milestone Nov 19, 2020
@czgdp1807
Copy link
Member

@namannimmo10 Can this become a part of next GSoC?

@white-peed
Copy link

I would like to work on this issue as my GSoC project. I know basic Git/Github, Python and Control Systems(via NPTEL lectures). Currently I am reading the documentation and code of the the matrix. Please help me get started.

@namannimmo10
Copy link
Member Author

Sure, please feel free to take over.
Some comments need to be addressed first; let us know what you think about the existing implementation and how it can be made more robust.

@white-peed
Copy link

Sure, please feel free to take over.
Some comments need to be addressed first; let us know what you think about the existing implementation and how it can be made more robust.

I was going over the document http://www.cds.caltech.edu/~murray/amwiki/index.php/Second_Edition , and I am currently having the following ideas:-

  1. MIMO systems :- I think using a 2D array would work well in this case.
  2. State space systems :- I am currently studying this topic.
  3. Documentation :- Few more examples of first order systems and second order can be added.
  4. pole zero function :- This can probably be done by using the roots function, for plotting I am thinking of using the matplotlib.
  5. root locus :- I was thinking of using a large number of data points with the plot function to plot the data. Numpy preprocessed functions could not be used as we don't which function to apply.

@namannimmo10
Copy link
Member Author

@white-peed, I think some small steps can be taken to further develop sympy.physics.control. That way we can discuss in detail each step!

  1. One task is to complete this outstanding PR; it includes resolving the merge conflicts, addressing some comments, and making TransferFunctionMatrix more robust (after some discussion which is pending by one of the reviewers).
  2. Another important task is to add 3-4 working examples in the documentation showing the users how to exercise this package.
  3. After that, we can think of adding graphical analyses to it. pole_zero and root_locus will simply use .poles() and .zeros() methods in TransferFunction class. And SymPy's own plotting module will work fine. bode plot should also be added.
  4. Finally, once all the aforementioned tasks are handled, the StateSpace class can be introduced for creating State Space models. I would also refer to python-control and harold for doing this task.

All in all, I believe some examples are essential as the development proceeds.

@white-peed
Copy link

white-peed commented Mar 26, 2021 via email

@namannimmo10
Copy link
Member Author

Can you explain " making TransferFunctionMatrix more robust " ?

Please have a look at all the discussions in this PR. There were some suggestions given to improve the existing implementation.

@bugmaker2
Copy link
Contributor

Hi @namannimmo10 . I am also interested in taking Symbolic Control System as my GSoC project. I am still reviewing the knowledge of Control Theory. It takes a few more days before I can comment on the implementation.

Also hello @white-peed . I feel so unlucky as I come here late and you have already come up with some brilliant ideas. Hope there is a chance that we both can contribute to this project.

As I am browsing the comment history, I found @white-peed had mentioned using matplotlib and numpy

  1. pole zero function :- This can probably be done by using the roots function, for plotting I am thinking of using the matplotlib.

Then I found that @namannimmo10 was using matplotlib and numpy in control_plot as well. emm...

I think maybe we should take a look at the Why SymPy section of Introduction to SymPy. It says

An advantage of SymPy is that it is lightweight. In addition to being relatively small, it has no dependencies other than Python, so it can be used almost anywhere easily.

We should follow the philosophy of the design. Also the function would become buggy if the dependencies update in the future. The Sympy has already implemented plotting methods in plotting.pygletplot, we may utilize that.

The idea above is just my random thoughts. I haven't come up with other ideas. I have to read the reference book quickly. See you days later.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.