When I write some complex code with MATLAB, I want to prohibit modifying the fields of the structure after it is initialized.
For example, I initialize the following struct
s = struct('vec', zeros(3, 1), 'val', 1.0)
In the following process, I ALLOW modifying the value of fields:
s.vec(1) = 1;
s.val = 2;
I want to PROHIBIT modifying the size of the existing field and PROHIBIT adding a new field to the struct.
If I run the following codes, I want it to return error information.
s.vec = zeros(4, 1);
s.mat = zeros(3, 3);
How to realize the above functions?
Thank you!
You need to write your own class, struct does not have this functionality.
Below is an example, read the comments for more info.
In particular I'm creating a class with the two properties of your example, and adding input validation with validateattributes and setter functions. The validateattributes function issues descriptive errors without you having to write them.
With the below class saved on your path, you can then run
s = myObject('vec', zeros(3, 1), 'val', 1.0);
Where vec and val both default to NaN (of the correct size) if not given as inputs. Then you can index like a struct to set the values, e.g.
s.vec(1) = 1; % Allowed, s.vec = [1 0 0] now
s.val = 2; % Allowed
s.vec = zeros(4,1); % Error: Expected input to be of size 3x1, but it is of size 4x1.
s.mat = zeros(3, 3); % Error: Unrecognized property 'mat' for class 'myObject'.
Full class example:
classdef myObject < matlab.mixin.SetGet
% We have to inherit from the SetGet superclass (could use a "handle"
% too) to get the setter functions
properties
% Object properties here which can be accessed using dot syntax
vec
val
end
methods
function obj = myObject( varargin )
% Constructor: called when creating the object
% Optional inputs for the properties to use name-value pairs
% similar to struct construction, with default values
p = inputParser;
p.addOptional( 'vec', NaN(3,1) );
p.addOptional( 'val', NaN(1,1) );
p.parse( varargin{:} );
% Assign values from inputs (or defaults)
obj.vec = p.Results.vec;
obj.val = p.Results.val;
end
% Use set functions. These are called when you try to update the
% corresponding property but allow for input validation before it
% is stored in the property.
function set.vec( obj, v )
validateattributes( v, {'numeric'}, {'size', [3,1]} );
obj.vec = v;
end
function set.val( obj, v )
validateattributes( v, {'numeric'}, {'size', [1,1]} );
obj.val = v;
end
end
end
Related
This is a follow-up of the question All possible combinations of many parameters MATLAB
In addition to all possible combinations of my parameter set, I also have a conditional parameter. For example, I need to include the parameter named 'lambda' only when the parameter 'corrAs' is set to 'objective'.
Do achieve this, right now I am doing the following
%% All posible parameters
params.corrAs = {'objective', 'constraint'};
params.size = {'small', 'medium', 'large'};
params.density = {'uniform', 'non-uniform'};
params.k = {3,4,5,6};
params.constraintP = {'identity', 'none'};
params.Npoints_perJ = {2, 3};
params.sampling = {'hks', 'fps'};
% If corrAs is 'objective', then also set lambda
params.lambda = {0.01, 0.1, 1, 10, 100};
%%%%%%%%%%%%% The solution posted on the link %%%%%%%%%%%
%% Get current parameter and evaluate
fields = fieldnames(params);
nFields = numel(fields);
sz = NaN(nFields, 1);
% Loop over all parameters to get sizes
for jj = 1:nFields
sz(jj) = numel( params.(fields{jj}) );
end
% Loop for every combination of parameters
idx = cell(1,nFields);
for ii = 1:prod(sz)
% Use ind2sub to switch from a linear index to the combination set
[idx{:}] = ind2sub( sz, ii );
% Create currentParam from the combination indices
currentParam = struct();
for jj = 1:nFields
%%%%%%%%%%% My addition for conditional parameter %%%%%%%%%%%
% lambda is valid only when corrAs is 'objective'
if isfield(currentParam, 'corrAs') && strcmp(fields{jj}, 'lambda') && ~strcmp(currentParam.corrAs, 'objective')
continue;
end
currentParam.(fields{jj}) = params.(fields{jj}){idx{jj}};
end
%% Do something with currentParam
end
It works but, the number of iterations for the main for loop also includes the lambda parameter even when corrAs is not 'objective'. So, I end up evaluating with the same currentParam many times than I am supposed to.
How can I do it more efficiently?
An easy way to think about this is by breaking the code up to be more function-based
In the below code, I've simply put the combination processing code into a function paramProcessing. This function is called twice -
When params.corrAs is 'constraint' only, all combinations will be processed, with no lambda field.
When params.corrAs is 'objective' only, all combinations will be processed with the additional lambda field.
You can have an output for the paramProcessing function if there is one from the looping.
This means you're only doing the combinations you want. From your question, it seems like each combination is independent, so it should be irrelevant that you're covering the combinations in separate loops. The function usage means you don't have to have the new condition in the loop, and the distinct possible values for params.corrAs each time ensure no overlap.
The paramProcessing function can be a local function in a main function file, as shown, local in a script (for newer MATLAB versions), or in its own .m file on your path.
Code:
function main()
%% All posible parameters, corrA is 'constraint' only.
params.corrAs = {'constraint'};
params.size = {'small', 'medium', 'large'};
params.density = {'uniform', 'non-uniform'};
params.k = {3,4,5,6};
params.constraintP = {'identity', 'none'};
params.Npoints_perJ = {2, 3};
params.sampling = {'hks', 'fps'};
% First processing call, no 'lambda' field exists in 'params'
paramProcessing( params );
% Cover the cases where corrAs is 'objective', with 'lambda' field
params.corrAs = {'objective'};
params.lambda = {0.01, 0.1, 1, 10, 100};
% Second processing call, with new settings
paramsProcessing( params );
end
function paramProcessing( params )
%% Get current parameter and evaluate
fields = fieldnames(params);
nFields = numel(fields);
sz = NaN(nFields, 1);
% Loop over all parameters to get sizes
for jj = 1:nFields
sz(jj) = numel( params.(fields{jj}) );
end
% Loop for every combination of parameters
idx = cell(1,nFields);
for ii = 1:prod(sz)
% Use ind2sub to switch from a linear index to the combination set
[idx{:}] = ind2sub( sz, ii );
% Create currentParam from the combination indices
currentParam = struct();
for jj = 1:nFields
currentParam.(fields{jj}) = params.(fields{jj}){idx{jj}};
end
%% Do something with currentParam
end
end
Minimalistic Example:
classdef MyClass
properties
arr
handArr
end
properties(Dependent)
rowAcc
colAcc
end
methods
function obj = MyClass(arr, handRow, handCol)
obj.arr = arr;
obj.handArr{1} = handRow;
if ~isequal(handRow, handCol)
obj.handArr{2} = handCol;
end
end
function r = get.rowAcc(obj)
r = obj.handArr{1}(obj.arr);
end
function c = get.colAcc(obj)
c = obj.handArr{end}(obj.arr);
end
end
end
Now assume I pass equal functions to the constructor, I want the row and col access would also be the same:
f=#(x)#(y) y;
x=MyClass(1, f, f);
isequal(x.rowAcc, x.colAcc) //should be 1
Is this possible?
I have a good reason for this 'insane' requirement:
I have several algorithms which run with 100+ MBs of input and takes those two functions as input, and when they are equal they can be optimized very efficiently; to call the algorithms I need to make transformations to the input functions which are encapsulated inside this class. I can't change the algorithms (not my code) and they use isequal on they're own functions to dispatch.
Two variables pointing to the same anonymous function are considered to be equal
f = #(x)x;
g = f;
isequal(f, g)
% 1
However, if you define the anonymous functions at different times, then they are not considered to be equal because the internal workspaces of the two functions could differ.
f = #(x)x;
g = #(x)x;
isequal(f, g)
% 0
In order to have your property return equal handles, you could have some "shadow" property (accessors_) which caches the accessors and you update these cached values whenever the arr property is changed.
classdef MyClass
properties
arr
handArr
end
properties (Access = 'protected')
accessors_ % An array of accessor functions for rows & columns
end
properties (Dependent)
rowAcc
colAcc
end
methods
function set.arr(obj, value)
% Set the value
obj.arr = value;
% Update the accessors_
self.accessors_ = obj.handArr{1}(obj.arr);
% Only assign another accessor if we have a second one
if numel(obj.handArr) > 1
self.accessors_(2) = obj.handArr{2}(obj.arr);
end
end
function res = get.rowAcc(obj)
res = obj.accessors_(1);
end
function res = get.colAcc(obj)
% If only one was stored, this will return a duplicate of it
res = obj.accessors_(end);
end
end
end
This also has the added benefit that you aren't creating function handles every time that colAcc and rowAcc are retrieved.
I have created the following class, which inherits both from handle and enum.
classdef ShiftType < handle
%SHIFTTYPE Defines shift per frame
properties
shift = 0
num_shifts = 0
end
enumeration
LateralCst %in meters
RadialCst % in radians
RadialVar % in beam distance ratio
LateralVar % Same. Lateral shift calculated at focus range.
end
end
If I create an instance of ShiftType and use it within a script, everything goes well. But I realized that, if I save this instance into a .mat file and then load it, its properies are set to their default value (0). Here is an example to illustrate the problem:
>> shift_type = ShiftType.RadialVar;
>> shift_type.shift = 0.5;
>> shift_type.num_shifts = 4;
>> shift_type
shift_type =
RadialVar
>> shift_type.shift
ans =
0.5000
>> save test.mat shift_type
>> clear all
>> load test.mat
>> shift_type
shift_type =
RadialVar
>> shift_type.shift
ans =
0
How can I have the properties saved in the .mat files along with the ShiftType instance?
Note that those properties are independant of the Enum type, so I don't want to just have a ShiftType(val) function and default values for each enum (such as LateralCst (1, 4)).
Thanks ahead!
It is most probably a bug. The documentation says that the property set methods should be called when the object is loaded however if you add this method to your class:
function set.shift(obj, shift)
obj.shift = shift;
disp('Set method called!');
end
You'll see that it is not being called. If you remove the enumeration part of the class, it works fine. Appears that loading of enumeration types have their own specific handling which doesn't take care of other properties.
Thanks for your answers. I have reported the bug to Matlab.
In the meantime I have fixed the issue by dividing ShiftType into two classes. The class Shift is a generic class with editable properties, one of which is an instance of the enum type of before (ShiftType).
classdef Shift
%SHIFT Defines shift per frame
properties
type = ShiftType.RadialVar;
val = 0; % Ref ShiftType
num_shifts = 0;
end
methods
function obj = Shift(type, val, num_shift)
obj.type = type;
obj.val = val;
obj.num_shifts = num_shift;
end
end
end
And:
classdef ShiftType
%SHIFTTYPE Defines shift type
enumeration
LateralCst %in meters
RadialCst % in radians
RadialVar % in beam distance ratio(1 = moves from one beam to another).
LateralVar % Same. Lateral shift calculated at focus range.
end
end
I think it is the most straightforward approach until the bug is fixed.
Thanks again for your answers :)
Here is something I wrote. I won't say its perfect. But gets the job done for now. There is no change required in the class file (Same class file). Just run this code
%% Initialise data
shift_type = ShiftType.RadialVar;
shift_type.shift = 0.5;
shift_type.num_shifts = 4;
%% save file
file_name='test11.mat'; % write filename
class_object='shift_type';
class_name=class(eval(class_object));
propNames=properties(class_name);
data=cell(1,size(propNames,1)+1);
data{1}=class_object;
for index=2:size(propNames,1)+1
propertyName=strcat(class_object,'.',propNames{index-1});
data{index}=[propNames(index-1) eval(propertyName)];
end
save(file_name,class_object,'data');
%% clear workspace
clear all;
%% load file
file_name='test11.mat'; %read filename
load(file_name);
for index=2:size(data,2)
property=data{index};
propertyName=strcat(data{1},'.',property{1});
expresn=strcat(propertyName,'=',num2str(property{2}),';');
eval(expresn);
end
%% Display data
shift_type.shift
shift_type.num_shifts
suppose we have following class,i want to declare rational number class in matlab,i am beginner of object oriented programming in matlab languages and i want to learn basics
classdef ratnum %rational number class
properties (Access=protected)
n %numerator
d %denomerator
end
methods
function r=ratnum(numerator,denomenator)
r.n=numerator;
r.d=denomenator;
end
end
end
how can i create constructor with specific values in matlab main part?should i use name of class ?thanks in advance
To instantiate an object of this class you can use: num1 = ratnum(2,3)
Since MATLAB doesn't have method overloading that is based on the amount of passed inputs, you could use nargin to select the correct scenario, as follows:
classdef ratnum %rational number class
properties (Access=protected)
n %//numerator
d %//denominator
end
methods
function r = ratnum(numerator,denominator)
switch nargin
case 2
r.n=numerator;
r.d=denominator;
case 0
%//whatever you want the defaults to be
end
end
end
end
A simple debug trick is to do num1_str=struct(num1) which allows you to see the contents of the object. However, you should create some public methods to get the values (instead of turning the object to a struct every time).
To overload the default summation of MATLAB you need to understand that whenever you write a+b it is automatically translated into plus(a,b). When defining custom classes with custom summation you should make a folder that has the name #classname (in your case #ratnum) and in it:
ratnum.m: the class definition file (which is the code you wrote)
a file named plus.m which looks something like:
.
function sum = plus(ratnum1,ratnum2)
ratnum1 = ratnum(ratnum1);
ratnum2 = ratnum(ratnum2);
sum = (...
ratnum1.r*ratnum2.d + ...
ratnum2.r*ratnum1.d )/ ...
(ratnum1.d * ratnum2.d);
end
Then, when you use + to add ratnums it will use the correct plus function.
Here's some helpful reading: MATLAB Documntation: Implementing Operators for Your Class
To call class methods, even from within other class methods, you must always write the class name first: ratnum.sum(ratnum1). Here's an example:
classdef ratnum %rational number class
properties (Access=public)
n %//numerator
d %//denominator
end
methods (Access = public)
function r = ratnum(numerator,denominator)
switch nargin
case 2
r.n=numerator;
r.d=denominator;
case 0
%whatever you want the defaults to be
end
end
end
methods (Access = public, Static)
function out = sum(ratnum)
out = ratnum.n + ratnum.d;
end
end
end
then:
>> a = ratnum(1,1)
a =
ratnum with properties:
n: 1
d: 1
>> ratnum.sum(a)
ans =
2
If you want to overload constructor with many different forms you'll have to initialize missing parameters with default value (or use varargin for more complex overloads):
function [r] = ratnum(num, den, varargin)
% Showing initializing missing parameters
if (nargin < 2), den = 42; end
if (nargin < 1), num = 7; end
% Showing working with varargin
if (nargin == 3)
...
elseif((nargin > 1) && (ischar(varargin{1}))
...
else
...
end
end
If you want to create named initializers for clarification of their meaning, you'll have to do it with Static methods:
methods (Static)
function [r] = TwoThird()
r = ratnum(2, 3);
end
function [r] = Half()
r = ratnum(1, 2);
end
end
Which can be used like this:
dummy = ratnum.Half(); % Ratio is 1/2
dummy = ratnum.TwoThird(); % Ratio is 2/3
dummy = ratnum(42, 666); % Ratio can be any custom one
I am having some difficulty designing a subclass of dataset in Matlab (R2010b). I am experienced programming Matlab, but new to using its OOP features. My constructor seems to be fine, but when I try to build some methods that access data in the dataset, I can't seem to get it to work. Here is an example:
classdef mydataset < dataset
properties
end
methods
function [obj] = mydataset(obs,array)
% obs is N x 1 cell array of strings
% array is N x 2 double array
obj = obj#dataset({array,'Field1','Field2'},'ObsNames',obs)
end
function [val] = computeValue(obj)
col = obj.Field1;
% I get an error above regardless of how I try to access the dataset.
% e.g. col = double(obj(obs,'Field1')) also does not work.
% Some more code using col to determine val
end
end
end
In my method computeValue, I am trying to access the data in the dataset using dataset syntax, i.e. on the command line I could access Field1 using ".". It complains there is no method, property, or field Field1 for class mydataset. If I try the alternate syntax
col = double(obj(:,'Field1'));
it complains about the size of obj, e.g. "Index exceeds matrix dimensions".
I found a workaround using subsref:
methods
function [val] = computeValue(obj)
s.type = '.';
s.subs = 'Field1';
col = subsref(obj,s);
% Some more code using col to determine val
end
end
Although the workaround works, it is not very convenient and largely defeats the purpose of wanting a subclass of dataset. Is there some attribute or something simple I am missing?
Thank you very much.
Eric
Can you post the complete code of your class. I suppose you didn't declare your property "factor" and try to access it.
Your class should look like this :
classdef MyClass
properties
factor;
end
methods
function obj = MyClass(factor)
% The constructor set the property factor
obj.factor = factor;
end
function val = computeValue(obj)
col = obj.factor;
% ...
end
end
end