MATLAB Answers

0

OOP: How to avoid recalculation on dependent properties (I hope a MathWork Developer could give me an answer too)

Asked by Ricardo Prada on 12 Nov 2012
Latest activity Commented on by Fred
on 13 Feb 2017
Dear MATLAB Community,
I am studying for the first time OOP on Matlab and have a question regarding how dependent properties can be calculated without falling on internal recalculations over and over again. For instance, consider the following class definition (taken from the MATLAB documentation):
classdef TensileData
properties
Material='';
SampleNumber=0;
Stress
Strain
end
properties (Dependent=true, SetAccess=private)
Modulus
end
methods
function obj=TensileData(material, samplenum, stress, strain)
if nargin>0
obj.Material=material;
obj.SampleNumber=samplenum;
obj.Stress=stress;
obj.Strain=strain;
end
end
function obj=set.Material(obj,material)
if ~(strcmpi(material,'aluminum') || ...
strcmpi(material,'stainless steel') || ...
strcmpi(material,'carbon steel'))
error('Material must be aluminum, stainless steel, or carbon steel')
end
obj.Material=material;
end
function modulus=get.Modulus(obj)
fprintf('!!!!! Running get.Modulus method...\n\n')
ind=find(obj.Strain>0);
modulus=mean(obj.Stress(ind)./obj.Strain(ind));
end
function disp(obj)
fprintf(1,'Material: %s\nSample Number: %g\nModulus: %1.5g\n',...
obj.Material, obj.SampleNumber, obj.Modulus);
end
function plot(obj,varargin)
plot(obj.Strain, obj.Stress, varargin{:})
title(['Stress/Strain plot for Sample',...
num2str(obj.SampleNumber)])
ylabel('Stress (psi)')
xlabel('Strain %')
end
end
end
... and at the command window, we type:
>> td=TensileData('carbon steel',001,[2e4 4e4 6e4 8e4],[.12 .20 .31 .40]);
>> td.Modulus
!!!!! Running get.Modulus method...
ans =
1.9005e+05
>> td.Modulus
!!!!! Running get.Modulus method...
ans =
1.9005e+05
As you can see, when I executed for the second time td.Modulus, MATLAB calculated the modulus value once more. My question is: if properties did not change, why does MATLAB need to calculate again the modulus value? Of course, this is not good programming practice (suppose td.Modulus is a time consuming method) and I was wondering whether we can avoid recalculation in this particular example. If so, how?
Your help will be greatly appreciated.
Thank you!

  0 Comments

Sign in to comment.

2 Answers

Answer by Honglei Chen
on 12 Nov 2012
 Accepted Answer

Hi Ricardo,
By definition, the value of dependent property depends on the values of object's other properties and probably the internal state of the object too. Therefore, every time you try to get the property, it needs to be recalculated. I think for your problem, the best solution is to define an extra private property, say pModulus, which is set internally whenever necessary. Then in your get.Modulus method, you simply write
function modulus = get.Modulus(obj)
modulus = obj.pModulus;
end
This way, it only needs to be recalculated when necessary and still preserve the behavior of a dependent property.
HTH

  6 Comments

For example, you can write the following code for the Material property
function obj=set.Material(obj,material)
if ~(strcmpi(material,'aluminum') || ...
strcmpi(material,'stainless steel') || ...
strcmpi(material,'carbon steel'))
error('...')
end
obj.Material=material;
computeModulus(obj);
end
where computeModulus is your private method to compute the modulus.
Dear Honglei Chen,
Thanks a lot for all your support in this regard.
This is what I did but it is not working.
classdef TensileData
properties
Material='';
SampleNumber=0;
Stress
Strain
end
properties (Dependent=true, SetAccess=private)
Modulus
end
properties (SetAccess=private, GetAccess=private, Hidden)
pModulus
end
methods
function obj=TensileData(material, samplenum, stress, strain)
if nargin>0
obj.Material=material;
obj.SampleNumber=samplenum;
obj.Stress=stress;
obj.Strain=strain;
end
end
function obj=set.Material(obj,material)
if ~(strcmpi(material,'aluminum') || ...
strcmpi(material,'stainless steel') || ...
strcmpi(material,'carbon steel'))
error('Material must be aluminum, stainless steel, or carbon steel')
end
obj.Material=material;
computeModulus(obj);
end
function obj=computeModulus(obj)
fprintf('!!!!! Running computeModulus method...\n\n')
ind=find(obj.Strain>0);
obj.pModulus=mean(obj.Stress(ind)./obj.Strain(ind));
end
function modulus=get.Modulus(obj)
modulus=obj.pModulus;
end
end
end
When I type this command-line on the command window
>> td=TensileData('carbon steel',001,[2e4 4e4 6e4 8e4],[.12 .20 .31 .40]);
!!!!! Running computeModulus method...
I get:
>> td.Modulus
ans =
[]
I was also thinking of creating events, but I have not been able to make it work yet.
Thanks.
This is because when the method is invoked, you don't have value for parameters, such as stress yet. For your case, there are two solutions. Either set some default values for propeties, or set default values in the constructor and then at the end of constructor, call the method too to update the value.
HTH

Sign in to comment.


Answer by Ricardo Prada on 13 Nov 2012
Edited by Ricardo Prada on 14 Nov 2012

Dear Honglei Chen,
Thank you very much for staying tuned in this regard.
I will take your last reply as an acceptable solution. I am still thinking this is not good programming practice and that the MathWorks Team should solve this problem from the root by recalculating dependent properties only when independent properties changes of state. I have read some examples in which into a method dependent properties are recalculated more than once, such as in the following script (taken from the MATLAB Documentation as such):
classdef topo < handle
properties
FigHandle
FofXY
Lm=[-2*pi 2*pi];
end
properties (Dependent=true, SetAccess=private)
Data
end
methods
function obj=topo(fnc,limits)
obj.FofXY=fnc;
obj.Lm=limits;
end
function set.Lm(obj, lim)
if ~(lim(1)<lim(2))
error('Limits must be monotically increasing')
else
obj.Lm=lim;
end
end
function data=get.Data(obj)
fprintf('!!!! Running get.Data method...\n\n');
[x,y]=topo.grid(obj.Lm);
matrix=obj.FofXY(x,y);
data.X=x;
data.Y=y;
data.Matrix=matrix;
end
function surflight(obj)
obj.FigHandle=figure;
surfc(obj.Data.X,obj.Data.Y,obj.Data.Matrix,...
'FaceColor',[.8 .8 0], 'EdgeColor',[0 .2 0],...
'FaceLighting','phong');
camlight('left'); material('shiny'); grid('off');
colormap('copper');
end
function delete(obj)
h=obj.FigHandle;
if ishandle(h)
delete(h);
else
return
end
end
end
methods (Static=true)
function [x,y]=grid(lim)
inc=(lim(2)-lim(1))/35;
[x,y]=meshgrid(lim(1):inc:lim(2));
end
end
end
When we type at the command window:
>> t=topo(@(x,y) x.*exp(-x.^2-y.^2),[-2,2]);
We get:
>> t.surflight
!!!! Running get.Data method...
!!!! Running get.Data method...
!!!! Running get.Data method...
As you can see, get.Data method was invoked three times into the surflight method. Are MathWorks Team aware of this? If so, I hope that a MathWorks Developer could give me an explanation for this particular behaviour.
Thank you!

  3 Comments

Also, what is the different between creating a method that calculates Data and the one that is provided by the MATLAB language with get.Data?
I mean, why not delete Data property and replace get.Data with data method?
function data=data(obj)
fprintf('!!!! Running data method...\n\n');
[x,y]=topo.grid(obj.Lm);
matrix=obj.FofXY(x,y);
data.X=x;
data.Y=y;
data.Matrix=matrix;
end
and surflight with this function:
function surflight(obj)
obj.FigHandle=figure;
data=obj.data;
surfc(data.X,data.Y,data.Matrix,...
'FaceColor',[.8 .8 0], 'EdgeColor',[0 .2 0],...
'FaceLighting','phong');
camlight('left'); material('shiny'); grid('off');
colormap('copper');
end
... which of course if a more efficient script than the one proposed by the MathWorks Team. I really do not understand.
Hope you could help me to understand. Thank you.
Note: I'm sorry for bumping such an old thread. I'm just getting into OOP and came across this thread. I decided to check out the original poster's class.
First, with regard to your comment that running the method t.surflight calls your get.Data function 3 times. Yes, it will call your get.Data function 3 times because you have 3 calls within this line (of the surflight method):
surfc(obj.Data.X,obj.Data.Y,obj.Data.Matrix,...
Once for obj.Data.X, once for obj.Data.Y, and once for obj.Data.Matrix. It would probably be better for Matlab to recognize that it only needs to get the obj.Data structure once, but I can definitely understand how that may be difficult to code on their end. There is a much simpler way for you to fix your code:
function surflight(obj)
obj.FigHandle=figure;
datatmp=obj.Data;
surfc(datatmp.X,datatmp.Y,datatmp.Matrix,...
'FaceColor',[.8 .8 0], 'EdgeColor',[0 .2 0],...
'FaceLighting','phong');
camlight('left'); material('shiny'); grid('off');
colormap('copper');
end
By making a temporary data variable, datatmp, and assigning it to obj.Data you will only get 1 call to the get.Data method. Then you can use that temporary variable all you want within the surflight method.
Second, with regard to your comment that Mathworks should change how dependent properties work so that they only change when independent properties change; I think that maybe it would be nice for that to be an option, but I wouldn't want all dependent properties to act that way. Having dependent properties which calculate on call is useful for certain circumstances, just as having dependent properties which only update when certain (or all) independent properties change is useful. Honglei Chen's solution above should allow you to implement the functionality you want.
Finally, with regard to your final comment on why use dependent properties at all. Yes, the code you proposed there is completely sufficient, you can cut out the dependent property entirely. Now, consider that the data that you're plotting has units associated with them, lets say length. Before you plot something you might not be sure if you want it to show up in mm, m, or km. You can store the data raw like you have above in one unit system, then use a dependent property to store the scaled data. I find this to be very useful for my work for two reasons:
  1. I live in the USA, but work in SI units. This means I occasionally convert plots to US Customary units =( .
  2. Occasionally I will be analyzing oscilloscope data. The data comes in raw, and I may need to apply offsets scale factors. By using dependent properties I can keep a copy of unscaled data, choose my scale factors, and when I plot it it comes out correctly.
Now I'm sure there are other ways to do what I just described but hopefully you can understand that this is at least a legitimate use for the way dependent properties work. Finally, I completely understand your confusion around why something works the way it does. I wrote some code recently where I was trying to learn to use Events. The first one or two Events I created made total sense (and worked well), but third and fourth Events I realized could be implemented cleaner by using plain old methods. I think the lesson is that there are always multiple ways (paradigms) to code.

Sign in to comment.