Explorations in overriding MATLAB functions
In a recent blog post, I demonstrated how to use the MATLAB 2012a Symbolic Toolbox to perform Variable precision QR decomposition in MATLAB. The result was a function called vpa_qr which did the necessary work.
>> a=vpa([2 1 3;-1 0 7; 0 -1 -1]); >> [Q R]=vpa_qr(a);
I’ve suppressed the output because it’s so large but it definitely works. When I triumphantly presented this function to the user who requested it he was almost completely happy. What he really wanted, however, was for this to work:
>> a=vpa([2 1 3;-1 0 7; 0 -1 -1]); >> [Q R]=qr(a);
In other words he wants to override the qr function such that it accepts variable precision types. MATLAB 2012a does not allow this:
>> a=vpa([2 1 3;-1 0 7; 0 -1 -1]); >> [Q R]=qr(a) Undefined function 'qr' for input arguments of type 'sym'.
I put something together that did the job for him but felt that it was unsatisfactory. So, I sent my code to The MathWorks and asked them if what I had done was sensible and if there were any better options. A MathWorks engineer called Hugo Carr sent me such a great, detailed reply that I asked if I could write it up as a blog post. Here is the result:
Approach 1: Define a new qr function, with a different name (such as vpa_qr). This is probably the safest and simplest option and was the method I used in the original blog post.
- Pros: The new function will not interfere with your MATLAB namespace
- Cons: MATLAB will only use this function if you explicitly define that you wish to use it in a given function. You would have to find all prior references to the qr algorithm and make a decision about which to use.
Approach 2: Define a new qr function and use the ‘isa’ function to catch instances of ‘sym’. This is the approach I took in the code I sent to The MathWorks.
function varargout = qr( varargin ) if nargin == 1 && isa( varargin{1}, 'sym' ) [varargout{1:nargout}] = vpa_qr( varargin{:} ); else [varargout{1:nargout}] = builtin( 'qr', varargin{:} ); end
- Pros: qr will always select the correct code when executed on sym objects
- Cons: This code only works for shadowing built-ins and will produce a warning reminding you of this fact. If you wish to extend this pattern for other class types, you’ll require a switch statement (or nested if-then-else block), which could lead to a complex comparison each time qr is invoked (and subsequent performance hit). Note that switch statements in conjunction with calls to ‘isa’ are usually indicators that an object oriented approach is a better way forward.
Approach 3: The MathWorks do not recommend that you modify your MATLAB install. However for completeness, it is possible to add a new ‘method’ to the sym class by dropping your function into the sym class folder. For MATLAB 2012a on Windows, this folder is at
C:\Program Files\MATLAB\R2012a\toolbox\symbolic\symbolic\@sym
For the sake of illustration, here is a simplified implementation. Call it qr.m
function result = qr( this ) result = feval(symengine,'linalg::factorQR', this); end
Pros: Functions saved to a class folder take precedence over built in functionality, which means that MATLAB will always use your qr method for sym objects.
Cons: If you share code which uses this functionality, it won’t run on someone’s computer unless they update their sym class folder with your qr code. Additionally, if a new method is added to a class it may shadow the behaviour of other MATLAB functionality and lead to unexpected behaviour in Symbolic Toolbox.
Approach 4: For more of an object-oriented approach it is possible to sub-class the sym class, and add a new qr method.
classdef mySym < sym methods function this = mySym(arg) this = this@sym(arg); end function result = qr( this ) result = feval(symengine,'linalg::factorQR', this); end end end
Pros: Your change can be shipped with your code and it will work on a client’s computer without having to change the sym class.
Cons: When calling superclass methods on your mySym objects (such as sin(mySym1)), the result will be returned as the superclass unless you explicitly redefine the method to return the subclass.
N.B. There is a lot of literature which discusses why inheritance (subclassing) to augment a class’s behaviour is a bad idea. For example, if Symbolic Toolbox developers decide to add their own qr method to the sym API, overriding that function with your own code could break the system. You would need to update your subclass every time the superclass is updated. This violates encapsulation, as the subclass implementation depends on the superclass. You can avoid problems like these by using composition instead of inheritance.
Approach 5: You can create a new sym class by using composition, but it takes a little longer than the other approaches. Essentially, this involves creating a wrapper which provides the functionality of the original class, as well as any new functions you are interested in.
classdef mySymComp properties SymProp end methods function this = mySymComp(symInput) this.SymProp = symInput; end function result = qr( this ) result = feval(symengine,'linalg::factorQR', this.SymProp); end end end
Note that in this example we did not add any of the original sym functions to the mySymComp class, however this can be done for as many as you like. For example, I might like to use the sin method from the original sym class, so I can just delegate to the methods of the sym object that I passed in during construction:
classdef mySymComp properties SymProp end methods function this = mySymComp(symInput) this.SymProp = symInput; end function result = qr( this ) result = feval(symengine,'linalg::factorQR', this.SymProp); end function G = sin(this) G = mySymComp(sin(this.SymProp)); end end end
Pros: The change is totally encapsulated, and cannot be broken save for a significant change to the sym api (for example, the MathWorks adding a qr method to sym would not break your code).
Cons: The wrapper can be time consuming to write, and the resulting object is not a ‘sym’, meaning that if you pass a mySymComp object ‘a’ into the following code:
isa(a, 'sym')
MATLAB will return ‘false’ by default.
Note that you can do your approach 3 without modifying the matlab installation, and I think that is the best approach. If you add the qr method to *any* @sym folder on the path it will get dispatched to when you call qr on an instance of sym. It can even be in your own working folder.
If you are concerned that sym may implement their own qr method then you can always look at the meta class information for the sym class (look at what you get with cls = ?sym in MATLAB) and look at its MethodList to see what it defines and you should be able to assert that you are not overriding sym’s native implementation.
@Andy
Andy – good idea. We had a similar thought. The ability to extend a class with methods in another directory is a feature of MATLAB’s classic OO system (struct-based), but is not supported in the modern OO system introduced in R2008a (with classdef files). Since syms are implemented with this newer object system, this technique won’t work.