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

16 views (last 30 days)
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!

Accepted Answer

Honglei Chen
Honglei Chen on 12 Nov 2012
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
Ricardo Prada
Ricardo Prada on 13 Nov 2012
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.
Honglei Chen
Honglei Chen on 13 Nov 2012
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.

More Answers (1)

Ricardo Prada
Ricardo Prada on 13 Nov 2012
Edited: 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
Nate H
Nate H on 30 Mar 2015
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.

Categories

Find more on Software Development Tools in Help Center and File Exchange

Products

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!