You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Decide on how to handle (to be deprecated?) np.matrix class #233
The latest version of numpy is listing the matrix class as something that may be deprecated in the future (see numpy-discussion thread). This is currently generating warning messages in our unit tests (see issue #232) but otherwise not causing any harm.
At some point we have to decide whether to switch out internal operations to use the array class, as recommended in the numpy documentation.
Updated code posted in PR #292. One question is whether we should incorporate this in version 0.8.2 or kick up the revision number and make the version be 0.9.0 when the PR is merged. All of my testing shows that everything in PR #292 is backward compatible, so I think it is OK to leave the update at the patch level (versus minor level).
@don4get We'll have to resolve the conflicts in the two PRs at some point. I changed lots of code in the unit tests, so it will probably generate conflicts with several of your (and other PRs). Sorry about that, but didn't see any other way to proceed since it made sense to update things all at once in PR #292.
Related to this issue, I've noticed that we are often importing both Numpy and Scipy but then accessing numpy objects like array and matrix using the sp reference.
import scipy as sp
import numpy as np
...
Q = sp.mat([ [ 1, 2], [2, -1] ])
In many cases we don't need to import scipy at all since many of these objects are actually from numpy originally:
In [1]: import numpy as np
In [2]: import scipy
In [3]: scipy.array is np.array
Out[3]: True
In [4]: scipy.mat is np.mat
Out[4]: True
In [5]: scipy.zeros is np.zeros
Out[5]: True
In [6]: scipy.transpose is np.transpose
Out[6]: True
I haven't seen import scipy as sp before so not sure it's a common convention. Scipy is a big library so usually you just import specific things from one of the sub-modules, like so:
from scipy.integrate import odeint
Unless there is a special reason to use import scipy as sp I propose eliminating these wherever possible as part of this np.matrix removal project.
B.t.w. I also saw a couple of from scipy import * which is definitely not a good idea (inserts over 600 names into the global namespace).
I'm happy to have a go at this, and realise it may not be a top priority.
As a general principle, I think we should use numpy whenever possible. Definitely any references to sp.array or sp.matrix should convert to numpy.
I don't think we have converged yet on a good approach to removing the numpy.matrix dependence. PR #292 is a first attempt, but it has lots of problem. I think we should first sort out what we want to accomplish and then one of us can put together a PR addressing this issue (probably replacing #292).
Some things I think we should tackle:
We need to decide whether we want the control package or the control.matlab subpackage to allow MATLAB-style matrix operations (*, ^, etc). I can see two arguments: (1) we should by pythonic and phase out all MATLAB-style operations or (2) controls analysis does a lot to of matrix multiplication and inversion => we should support these as fundamental operations.
I think I am leaning toward just doing everything with arrays (so staying "pythonic" or perhaps "numpythonic" about things). For anyone using Python3.5 and up, you get matrix multiplication with @ and the only thing you lose is matrix powers.
We need to decide on some guidelines for return values from control package functions. Right now we return a combinations of lists, arrays, and matrices, depending on who wrote the code. I think it would be useful to standardize things a bit, so that you don't have to constantly be looking at the documentation to figure out what type of object you are getting.
We need to have some means of backward compatibility and decide at what point we are willing to break running code. What I think I like best of the ideas we have played with so far is the notion of including a command use_numpy_matrix() that would set configuration variables so that you stay compatible with current practtice. We can then decide at what point we make use_numpy_matrix(False) the default and what we return instead.
We should clean up our scipy/numpy usage so that we only use scipy when something isn't in numpy and we don't import entire namespaces when we don't need them.
I agree we need to think through what we are trying to achieve. I don't think we should be trying to replicate Matlab in Python. There are fundamental differences that we wouldn't want to replicate (such as zero-based indexing and semi-colons as line-terminators).
On the other hand, it seems a shame that Numpy is deprecating the matrix class as it seems like a legitimate use-case - not everyone works in n dimensions all the time and the mental overhead of keeping track of the dimension and shape of your array object is quite large.
To me, the differences are few but so fundamental (e.g. what * operator does) that we can't have ambiguity about whether an object is a matrix or an ndarray. So a transition to ndarrays with a temporary 'global switch' that turns everything into matrix objects (inputs and outputs) seems reasonable to me.
I think it will mean a lot of .reshape() methods appearing in the code though which is unfortunate and users will have to get their heads around ndarrays and the new operators.
@billtubbs The reasons are similar to what you are trying to do here; trying to get rid of a thin-matlab user pleasing compatibility layer. It was historically similar but it turned out to be very difficult to keep track of "is it a matrix or array" question. Note that once it is in, it is very difficult to take something out from NumPy/SciPy codebase.
If it wasn't for the scipy.sparse module it would already be deprecated however it turned out to be quite challenging to get rid of it so things are a bit stalled now. Also subclassing is another detail against matrix. Hence it is just not removing it independent from other objects in the library. Finally it is really a very limited design as a generic object for subclassing as you are experiencing now.
One thing we could do is maintain a matrix-like class in the control.matlab module (which attempts to provide a more MATLAB-like environment for easier transition). But I still feel like pushing users to Python 3.5+ (where @ becomes available) and then just using ndarray's is the best bet.
@murrayrm I was thinking about that option too. But we would in effect be trying to fix or replace np.matrix class which would be going against the grain with the rest of the community and presumably we would face all the issues currently dogging np.matrix.
Back to your original question, are we trying to make a package geared towards Matlab users requiring minimal re-learning or are we trying to make a Python control package that python users can adopt with minimal re-learning? The ndarray vs matrix question is just an example that provokes that bigger question. I think we need to go one way or the other and not hover somewhere in-between.
We take as our eventual goal the use of numpy.ndarray as the standard class for all state space objects in python-control. This will be consistent with where the community is heading. By default, all arrays related to state space functions would be 2D objects (but see items below regarding allowable input forms).
As a bridge to keep everyone's code from breaking, we implement a use_numpy_matrix function in config.py that sets the internal type of the StateSpace class to numpy.matrix and output type for all python-control functions that return a matrix to numpy.matrix. The default for the next release would be use_numpy_matrix(True) so that nobody's code breaks, but at a future date we make use_numpy_matrix(False) as the default.
For all python-control functions that take row or column vectors as inputs, we allow these to be specified either as a 0D object, 1D object, or 2D object (scalar, list, list of lists, ndarray, etc) as long as it can be converted to the right shape. So all of the following would work and give you the same result:
(the last one matches the shape of what would be stored internally).
In addition, if you are using numpy.matrix as the default (via use_numpy_matrix(True)) then you could also pass MATLAB-style strings as inputs (this is probably the biggest place where we will break things when we move away from numpy.matrix to numpy.ndarray).
For all python-control functions that return matrices as outputs (including column and row vectors), you get a 2D ndarray of the appropriate shape. For row or column vectors, the users can use squeeze if they want to turn the 2D object back into a 1D object. (Alternatively, we could add a squeeze keyword, as we have done in forced_response().)
Note that shifting to a consistent output for all functions could "break" some current user code since current functions vary in whether they return 2D numpy.ndarray objects or a numpy.matrix objects. Now all functions would return one or the other (depending on use_numpy_matrix config setting).
For the python.matrix subpackage, we follow the conventions of the control package (eventual conversion to ndarray) and put information in the documentation that lets people know they can use use_numpy_matrix if they want something more MATLAB-like (for as long as the numpy.matrix class is supported).
We convert unit tests to move away from the use of numpy.matrix objects, though with some explicit unit tests to make sure numpy.matrix objects are allowed for as long as they are supported. Warning messages should be turned off when using numpy.matrix on purpose, to get rid of extraneous warning messages.
Note that compared with PR #292, this would get rid of the StateSpaceMatrix class and the problems associated with that approach (described above).
I think the ultimate goal is the right one and the transition approach makes sense to me unless someone comes up with a better method.
I don't think we can ever overcome the fact that Python is a general-purpose language and so it will never be as perfect as Matlab for control tasks. I.m.h.o. our goal is to give the Python community a good control toolbox that makes sense to Python programmers not try to replicate Matlab language features.
I was wondering if we could make use of function decorators during this transition. I.e. design a function decorator to handle all the conversion of arguments and return values according to the use_numpy_matrix setting. This would be nice because then we could focus all our work on making the code np.array compliant and then just remove all the decorators one day if/when we no longer support np.matrix.
I'm not an expert on decorators so this could probably be written better but it seems to work:
# Problem: function expects np.arrays but arguments could be np.matrix
def some_func(A, B):
return A*B
# Works with np.arrays
A = np.array([[1, 2], [3, 4]])
B = np.array([[1, 0], [0, 1]])
result1 = some_func(A, B)
print(result1, type(result1))
# But result is different with np.matrix
A = np.matrix([[1, 2], [3, 4]])
B = np.matrix([[1, 0], [0, 1]])
result2 = some_func(A, B)
print(result2, type(result2))
(result1 == result2).all()
# Solution: define a decorator
import functools
def convert_arguments(func):
@functools.wraps(func)
def wrapper_decorator(*args, **kwargs):
# Convert arguments
converted_args = []
for arg in args:
if isinstance(arg, np.matrix):
print("Matrix arg converted")
arg = np.array(arg)
converted_args.append(arg)
args = tuple(converted_args)
# Execute function
value = func(*args, **kwargs)
# Convert return values
if use_numpy_matrix:
value = np.matrix(value)
print("Matrix return value converted")
return value
return wrapper_decorator
# Then use it on every function:
@convert_arguments
def some_func(A, B):
"""Some function that expects np.arrays."""
return A*B
# Test with np.arrays
use_numpy_matrix = False
A = np.array([[1, 2], [3, 4]])
B = np.array([[1, 0], [0, 1]])
result1 = some_func(A, B)
print(result1, type(result1))
# Test with np.matrix
use_numpy_matrix = True
A = np.matrix([[1, 2], [3, 4]])
B = np.matrix([[1, 0], [0, 1]])
result2 = some_func(A, B)
print(result2, type(result2))
(result1 == result2).all()
* We take as our eventual goal the use of `numpy.ndarray` as the standard class for all state space objects in `python-control`. This will be consistent with where the community is heading. By default, all arrays related to state space functions would be 2D objects (but see items below regarding allowable input forms).
:
:
Thoughts?
I think the overall goal of this proposal is good. In general, I'm of the opinion that it's better to follow the conventions of the scientific python community than to try and emulate matlab, and I think this proposal moves us in that direction.
What's the latest on this? I might have some more time in coming weeks but I would need to be assigned some tasks that are within my skills and abilities.
B.t.w. I came across this project which was written to replace the original PILCO algorithm implementation written in MATLAB. Maybe we can learn something from the approach? I notice they do use the @ operator wherever possible but they are also using the matrix methods in TensorFlow a lot. Example:
Activity
murrayrm commentedon Apr 12, 2019
It would be nice to get this cleaned up. Flagging for release in 0.8.2.
murrayrm commentedon Apr 13, 2019
FYI, I've started working on cleaning this up. It involves substantial changes to test cases and the
StateSpaceclass.don4get commentedon Apr 13, 2019
Hello,
maybe I should let you know that I already performed part of this cleaning in currently opened PRs, like here:

https://github.com/python-control/python-control/pull/268/files
murrayrm commentedon Apr 16, 2019
Updated code posted in PR #292. One question is whether we should incorporate this in version 0.8.2 or kick up the revision number and make the version be 0.9.0 when the PR is merged. All of my testing shows that everything in PR #292 is backward compatible, so I think it is OK to leave the update at the patch level (versus minor level).
@don4get We'll have to resolve the conflicts in the two PRs at some point. I changed lots of code in the unit tests, so it will probably generate conflicts with several of your (and other PRs). Sorry about that, but didn't see any other way to proceed since it made sense to update things all at once in PR #292.
billtubbs commentedon Jun 10, 2019
Related to this issue, I've noticed that we are often importing both Numpy and Scipy but then accessing numpy objects like
arrayandmatrixusing thespreference.For example, in control/bdalg.py:
In many cases we don't need to import scipy at all since many of these objects are actually from numpy originally:
I haven't seen
import scipy as spbefore so not sure it's a common convention. Scipy is a big library so usually you just import specific things from one of the sub-modules, like so:Unless there is a special reason to use
import scipy as spI propose eliminating these wherever possible as part of thisnp.matrixremoval project.B.t.w. I also saw a couple of
from scipy import *which is definitely not a good idea (inserts over 600 names into the global namespace).I'm happy to have a go at this, and realise it may not be a top priority.
murrayrm commentedon Jun 10, 2019
As a general principle, I think we should use numpy whenever possible. Definitely any references to
sp.arrayorsp.matrixshould convert to numpy.I don't think we have converged yet on a good approach to removing the
numpy.matrixdependence. PR #292 is a first attempt, but it has lots of problem. I think we should first sort out what we want to accomplish and then one of us can put together a PR addressing this issue (probably replacing #292).Some things I think we should tackle:
We need to decide whether we want the
controlpackage or thecontrol.matlabsubpackage to allow MATLAB-style matrix operations (*, ^, etc). I can see two arguments: (1) we should by pythonic and phase out all MATLAB-style operations or (2) controls analysis does a lot to of matrix multiplication and inversion => we should support these as fundamental operations.I think I am leaning toward just doing everything with arrays (so staying "pythonic" or perhaps "numpythonic" about things). For anyone using Python3.5 and up, you get matrix multiplication with
@and the only thing you lose is matrix powers.We need to decide on some guidelines for return values from
controlpackage functions. Right now we return a combinations of lists, arrays, and matrices, depending on who wrote the code. I think it would be useful to standardize things a bit, so that you don't have to constantly be looking at the documentation to figure out what type of object you are getting.We need to have some means of backward compatibility and decide at what point we are willing to break running code. What I think I like best of the ideas we have played with so far is the notion of including a command
use_numpy_matrix()that would set configuration variables so that you stay compatible with current practtice. We can then decide at what point we makeuse_numpy_matrix(False)the default and what we return instead.We should clean up our scipy/numpy usage so that we only use scipy when something isn't in numpy and we don't import entire namespaces when we don't need them.
billtubbs commentedon Jun 10, 2019
I agree we need to think through what we are trying to achieve. I don't think we should be trying to replicate Matlab in Python. There are fundamental differences that we wouldn't want to replicate (such as zero-based indexing and semi-colons as line-terminators).
On the other hand, it seems a shame that Numpy is deprecating the
matrixclass as it seems like a legitimate use-case - not everyone works in n dimensions all the time and the mental overhead of keeping track of the dimension and shape of your array object is quite large.I've been doing some playing around to check out the differences between numpy ndarray and matrix classes. Then I found this more thorough compare-and-contrast article in the numpy documentation (if you haven't seen it). B.t.w. There's also a reddit discussion.
To me, the differences are few but so fundamental (e.g. what
*operator does) that we can't have ambiguity about whether an object is a matrix or an ndarray. So a transition to ndarrays with a temporary 'global switch' that turns everything into matrix objects (inputs and outputs) seems reasonable to me.I think it will mean a lot of
.reshape()methods appearing in the code though which is unfortunate and users will have to get their heads around ndarrays and the new operators.ilayn commentedon Jun 10, 2019
@billtubbs The reasons are similar to what you are trying to do here; trying to get rid of a thin-matlab user pleasing compatibility layer. It was historically similar but it turned out to be very difficult to keep track of "is it a matrix or array" question. Note that once it is in, it is very difficult to take something out from NumPy/SciPy codebase.
If it wasn't for the
scipy.sparsemodule it would already be deprecated however it turned out to be quite challenging to get rid of it so things are a bit stalled now. Also subclassing is another detail against matrix. Hence it is just not removing it independent from other objects in the library. Finally it is really a very limited design as a generic object for subclassing as you are experiencing now.murrayrm commentedon Jun 10, 2019
One thing we could do is maintain a matrix-like class in the
control.matlabmodule (which attempts to provide a more MATLAB-like environment for easier transition). But I still feel like pushing users to Python 3.5+ (where@becomes available) and then just using ndarray's is the best bet.billtubbs commentedon Jun 10, 2019
Thanks @ilayn . Yes, I saw a similar explanation on Reddit by someone from the Scipy team.
@murrayrm I was thinking about that option too. But we would in effect be trying to fix or replace np.matrix class which would be going against the grain with the rest of the community and presumably we would face all the issues currently dogging
np.matrix.Back to your original question, are we trying to make a package geared towards Matlab users requiring minimal re-learning or are we trying to make a Python control package that python users can adopt with minimal re-learning? The ndarray vs matrix question is just an example that provokes that bigger question. I think we need to go one way or the other and not hover somewhere in-between.
murrayrm commentedon Jun 11, 2019
A proposal for how to move forward:
We take as our eventual goal the use of
numpy.ndarrayas the standard class for all state space objects inpython-control. This will be consistent with where the community is heading. By default, all arrays related to state space functions would be 2D objects (but see items below regarding allowable input forms).As a bridge to keep everyone's code from breaking, we implement a
use_numpy_matrixfunction inconfig.pythat sets the internal type of theStateSpaceclass tonumpy.matrixand output type for allpython-controlfunctions that return a matrix tonumpy.matrix. The default for the next release would beuse_numpy_matrix(True)so that nobody's code breaks, but at a future date we makeuse_numpy_matrix(False)as the default.For all
python-controlfunctions that take row or column vectors as inputs, we allow these to be specified either as a 0D object, 1D object, or 2D object (scalar, list, list of lists, ndarray, etc) as long as it can be converted to the right shape. So all of the following would work and give you the same result:(the last one matches the shape of what would be stored internally).
In addition, if you are using
numpy.matrixas the default (viause_numpy_matrix(True)) then you could also pass MATLAB-style strings as inputs (this is probably the biggest place where we will break things when we move away fromnumpy.matrixtonumpy.ndarray).For all
python-controlfunctions that return matrices as outputs (including column and row vectors), you get a 2Dndarrayof the appropriate shape. For row or column vectors, the users can usesqueezeif they want to turn the 2D object back into a 1D object. (Alternatively, we could add asqueezekeyword, as we have done inforced_response().)Note that shifting to a consistent output for all functions could "break" some current user code since current functions vary in whether they return 2D
numpy.ndarrayobjects or anumpy.matrixobjects. Now all functions would return one or the other (depending onuse_numpy_matrixconfig setting).For the
python.matrixsubpackage, we follow the conventions of thecontrolpackage (eventual conversion tondarray) and put information in the documentation that lets people know they can useuse_numpy_matrixif they want something more MATLAB-like (for as long as thenumpy.matrixclass is supported).We convert unit tests to move away from the use of
numpy.matrixobjects, though with some explicit unit tests to make surenumpy.matrixobjects are allowed for as long as they are supported. Warning messages should be turned off when usingnumpy.matrixon purpose, to get rid of extraneous warning messages.Note that compared with PR #292, this would get rid of the
StateSpaceMatrixclass and the problems associated with that approach (described above).Thoughts?
billtubbs commentedon Jun 12, 2019
I think the ultimate goal is the right one and the transition approach makes sense to me unless someone comes up with a better method.
I don't think we can ever overcome the fact that Python is a general-purpose language and so it will never be as perfect as Matlab for control tasks. I.m.h.o. our goal is to give the Python community a good control toolbox that makes sense to Python programmers not try to replicate Matlab language features.
billtubbs commentedon Jun 12, 2019
Implementation Idea
I was wondering if we could make use of function decorators during this transition. I.e. design a function decorator to handle all the conversion of arguments and return values according to the
use_numpy_matrixsetting. This would be nice because then we could focus all our work on making the codenp.arraycompliant and then just remove all the decorators one day if/when we no longer supportnp.matrix.I'm not an expert on decorators so this could probably be written better but it seems to work:
Output:
Output:
rabraker commentedon Jun 12, 2019
I think the overall goal of this proposal is good. In general, I'm of the opinion that it's better to follow the conventions of the scientific python community than to try and emulate matlab, and I think this proposal moves us in that direction.
billtubbs commentedon Jun 30, 2019
What's the latest on this? I might have some more time in coming weeks but I would need to be assigned some tasks that are within my skills and abilities.
B.t.w. I came across this project which was written to replace the original PILCO algorithm implementation written in MATLAB. Maybe we can learn something from the approach? I notice they do use the
@operator wherever possible but they are also using the matrix methods in TensorFlow a lot. Example:They are also using decorators for converting input arguments it seems:
murrayrm commentedon Jun 30, 2019
PR #314 resolves this. I'll merge it soon and close this issue out.
billtubbs commentedon Jun 30, 2019
Ah sorry, I wasn't paying attention. So it's all done then?
Let me know if there is something else I can help with.
murrayrm commentedon Jun 30, 2019
Yes, effectively done. When we decide we want to get rid of the matrix class, we just change the default setting in
config.py.There are lots of other places to pitch in (43 other open issues... -:).
murrayrm commentedon Jun 30, 2019
Resolved in PR #314.