MATLAB Answers

Listen for Changes to Property Values

27 views (last 30 days)
Nycholas Maia
Nycholas Maia on 22 Feb 2018
Answered: Jack Du Ho on 16 Aug 2019
I would like to update the RMS object property always when the signal object property has change:
classdef track < handle
properties
rms = 0;
end
properties (SetObservable)
signal = [1 2 3 4 5 6 7 8 9 10];
end
methods
function obj = update_rms(obj)
obj.rms = rms(obj.signal);
end
end
end
EXAMPLE:
% Init object:
myObj = track();
% Signal property manual update:
myObj.signal = [1 1 1 1];
% After this, I would like that myObj update automatically the RMS property calling the update_rms method.
Could you help me?

  4 Comments

Show 1 older comment
Steven Lord
Steven Lord on 23 Feb 2018
I assume this comment is related to my Answer. For that you might want to have your Dependent property's get.[property name] method use memoization via the memoize function.
Nycholas Maia
Nycholas Maia on 23 Feb 2018
Thanks for this optimization method. I was unaware of this possibility in MATLAB. It will be very useful in other sessions of the software.
In this specific case, I really would not like to cache in memory for some architectural reasons of the software in question.
Could you tell me how to automatically update the 'RMS' property value using the 'update_rms' function whenever the value of the 'Signal' property changes?
I need the RMS value to be actually stored inside the RMS property. It can not be recalculated or calculated at the moment it is called.
How could I do that?
Thank you very much
Walter Roberson
Walter Roberson on 23 Feb 2018
You can use memoize and set the cache size to 1.

Sign in to comment.

Accepted Answer

Guillaume
Guillaume on 23 Feb 2018
Edited: Guillaume on 23 Feb 2018
If I understood correctly, wouldn't the following be the simplest way to implement what you want:
classdef (ConstructOnLoad) track < handle
properties (SetAccess = private) %I assume that rms cannot be set by the user
rms;
end
properties
signal = 1:10;
end
methods
function this = track()
this.UpdateRms();
end
function set.signal(this, value)
this.signal = value;
this.UpdateRms();
end
end
methods (Access = private)
function UpdateRms(this)
this.rms = rms(this.signal);
end
end
end
No need for dependent properties or listener. Just recalculate rms whenever signal is updated.
The ConstructOnLoad is there to ensure that rms is recalculated when a class object is reloaded from disk. It's not strictly necessary since rms and signal should never be out of sync even when read from disk.

  5 Comments

Show 2 older comments
Nycholas Maia
Nycholas Maia on 23 Feb 2018
Sorry to ask me one last detail:
Is it possible to pass input arguments to the method call when I'm using addlistener?
Example:
addlistener(this, 'signal', 'PostSet', @this.UpdateRms (arg1, arg2, arg3));
Guillaume
Guillaume on 23 Feb 2018
I wouldn't say that the two methods work the same way. But they do produce the same result. My personal preference would be for the first method as it's clearer as to what happens when signal change since the update_rms call is right there in the signal set function rather than defined (and forgotten) ahead of time with a listener. For me, it's a bit unusual to have a class listening to its own events since it can just tell itself when something happens. Listeners are usually for others to listen on your class.
In term of performance, I've no idea if there's a difference between the two.
As for your question:
addlistener(this, 'signal', 'PostSet', @(~, ~) this.UpdateRms (arg1, arg2, arg3)); %the ~, ~ to capture (and discard) the standard event arguments.
and the function prototype to match:
function UpdateRms(this, arg1, arg2, arg3)
Be aware that arg1, arg2 and arg3 will be fixed to the value they had when addlistener was called.
Nycholas Maia
Nycholas Maia on 23 Feb 2018
Fantastic! You all helped me a lot!
Thank you all!

Sign in to comment.

More Answers (3)

Steven Lord
Steven Lord on 22 Feb 2018
So you want the rms property to always be the result of calling the rms function on the signal property of your object? In that case I don't think you need property listeners. I would instead make rms a Dependent property. The "Calculate Data on Demand" section of this example in the documentation shows how to do this, where the Modulus property is calculated on demand from the Stress and Strain properties of that object.

  1 Comment

Steven Lord
Steven Lord on 23 Feb 2018
If a Dependent property doesn't satisfy your needs, you could give the Signal property a property set method that computes and sets the RMS property. If you do that you probably want to restrict SetAccess to the RMS property so that only methods inside the class (private) or perhaps methods in that class and its subclasses (protected) can set it. If you left it with public SetAccess, anyone could change its value thus putting it out of sync with the Signal property's value.

Sign in to comment.


Gabriele Bunkheila
Gabriele Bunkheila on 23 Feb 2018
Hi Nycholas,
I agree with Steven's suggestion. Please find below a possible implementation of what I understand you are trying to achieve:
classdef track < handle
properties
signal = [1 2 3 4 5 6 7 8 9 10];
end
properties (Dependent)
RMS = 0;
end
properties (Access = private)
pRMS
end
methods
function obj = track()
update_rms(obj)
end
function set.signal(obj, value)
obj.signal = value;
update_rms(obj)
end
function set.RMS(obj,~)
fprintf('%s%g\n','RMS is: ',obj.RMS)
error('You cannot set the RMS property');
end
function value = get.RMS(obj)
value = obj.pRMS;
end
function update_rms(obj)
obj.pRMS = rms(obj.signal);
end
end
end
This would give you the following behavior:
>> t = track
t =
track with properties:
signal: [1 2 3 4 5 6 7 8 9 10]
RMS: 6.2048
>> t.signal = 0.1*t.signal
t =
track with properties:
signal: [0.1000 0.2000 0.3000 0.4000 0.5000 0.6000 0.7000 0.8000 0.9000 1]
RMS: 0.6205
>> t.RMS = 2
RMS is: 0.620484
Error using track/set.RMS (line 21)
You cannot set the RMS property
>>
You can simplify this to avoid storing anything, at the expense of triggering a computation every time you need to use or display the RMS property.
I hope this helps.
Gabriele.

  4 Comments

Show 1 older comment
Adam
Adam on 23 Feb 2018
The private property is there precisely to store the values, the public dependent property does not store any data, which is always the case with a dependent property.
It simply asks the private property for the value when you ask for the public RMS value.
In this setup the paired property that actually stores the value should always be private because you access it via the public RMS property.
In this particular usage I would agree with Guillaume though that it seems un-necessary.
I use this kind of setup generally when I want to be able to publicly set the RMS value and in that set function to also refer to some other class property (e.g. for validation).
Gabriele Bunkheila
Gabriele Bunkheila on 23 Feb 2018
Hi Nycholas,
Dependent properties do not store data but compute it based on other values (e.g. stored in other properties). Because they don't store data, when queried (i.e. in their get method) they need to either compute it or copy from somewhere else. The role of pRMS is to store the value so you don't need to re-compute it every time that you need it.
That said, Guillaume's take below without using Dependent attributes is equally valid (and shorter) in this simple case.
On the other hand, the SetObservable qualifier allows you to create listeners for property-set events - it is a more complicated tool. I agree it doesn't seem justified for this simple example, but it may be useful in larger-scale problems. One of the benefits that I found in using listeners in the past is the effectiveness in decoupling triggering events (in this case the change of the value of a property) from the resulting effect - the property change does not need to know what needs to happen next.
Good luck with your project,
Gabriele.
Nycholas Maia
Nycholas Maia on 23 Feb 2018
Perfect Gabriele. As my project is large and I can not always re-calculate the value of object properties I believe the MATLAB SetObservable feature can be very useful to me.
The problem is that I am not able to run these features in this simple example I posted here.
I want whenever the 'signal' property changes, then trigger the 'update_rms' function, and update the value of the 'RMS' property.
Could you help me in the code from this example above posted using the SetObservable feature?

Sign in to comment.


Jack Du Ho
Jack Du Ho on 16 Aug 2019
One solution is to define an event "signalChanged" and create a listener when you initialize the object
classdef track < handle
properties
rms = 0;
end
properties
signal = [1 2 3 4 5 6 7 8 9 10]
end
events
signalChanged
end
methods
function obj = track()
obj.update_rms;
addlistener(obj,'signalChanged',@(~,~) obj.update_rms);
end
function obj = disp(obj)
disp('--------------------------')
disp(obj.rms);
disp('--------------------------')
end
function set.signal(obj,value)
obj.signal = value;
notify(obj,'signalChanged')
end
function update_rms(obj)
obj.rms = rms(obj.signal);
end
end
end

  0 Comments

Sign in to comment.

Tags

Products