How to have sequence in a function in MATLAB? - matlab

Below is a code, which works fine without function/function handles. However, I have to write a function so that I can use optimization function of "lsqcurvefit" in MATLAB and optimize parameters constant(1) and constant(2).
L=zeros(n,length(S)); % Height of elements
L(:,1)=Linitial; % Height of elements for day zero
for jj=1:length(S)
if jj==1
continue
end
for j=1:n % n is 101 (previously defined...)
L(j,jj) = #(constant, i_dt) L(j,jj-1) - (i_dt(j,jj).*constant(1).*((L(j,jj-1)./hs)-1)^constant(2)); %*****
end
end
height_pred = #(constant,i_dt) sum(L); %heigh_pred(1) is known and previously defined
options = optimoptions('lsqcurvefit','FinDiffType','central',...
'TolFun',1e-10);
constant = lsqcurvefit(height_pred,constant0,i_dt,height_meas,lb,ub,options);
When I run above code, I receive the error "Conversion to double from function_handle is not possible" for line with 5 stars (*****).
If I remove function handle and instead write a function at the end of my code like below:
L=zeros(n,length(S)); % Height of elements
L(:,1)=Linitial; % Height of elements for day zero
for jj=1:length(S)
for j=1:n % n is 101 (previously defined...)
L(j,jj) = Nelfun(j,jj);
end
end
height_pred = #(constant,i_dt) sum(L); %heigh_pred(1) is known and previously defined
options = optimoptions('lsqcurvefit','FinDiffType','central',...
'TolFun',1e-10);
constant = lsqcurvefit(height_pred,constant0,i_dt,height_meas,lb,ub,options);
function L = Nelfun(constant,i_dt)
for jj=1:145
if jj==1
L(:,1)=0.0187600000000000;
continue
end
for j=1:101
L(j,jj-1);
L(j,jj) = L(j,jj-1) - (i_dt(j,jj).*constant(1).*((L(j,jj-1)./hs)-1)^constant(2)); %*****
end
end
end
I receive the error
"Index exceeds matrix dimensions" for line with (*****).
Size of matrix i_dt is (101,145), L is (101,145), height_meas(1,145), and height_pred(1,145).
L(j,jj) for jj==1 is known but then should be calculated and optimized for L(:,2) to L(:,145).
***
Thanks to Praveen, "Index exceeds matrix dimensions" issue is resolved, but now I receive the error
"Error using snls (line 183)
Finite difference Jacobian at initial point contains Inf or NaN values. lsqcurvefit cannot continue."
for below code:
lb = [0.000000000001,0.05]; ub = [1,50]; constant0 = [1.06e-09,15.2];
height_pred = #(constant,i_dt) sum(Nelfun(constant,i_dt));
options = optimoptions('lsqcurvefit','FinDiffType','central',...
'TolFun',1e-10);
constant = lsqcurvefit(height_pred,constant0,i_dt,height_meas,lb,ub,options);
function L = Nelfun(constant,i_dt)
L=zeros(size(i_dt));
n=101; % number of nodes
ei=2.05613504572765;
e0=ei.*ones(n,1);
Hsolid=0.613847219422639;
hs = Hsolid./(n-1);
for JJ=1:size(i_dt,2)
if JJ==1
for j=1:n
Linitial0=hs.*(1+e0);
Linitial0(1)=0;
end
L(:,JJ) = Linitial0;
continue
end
for J=1:size(i_dt,1)
L(J,JJ) = L(J,JJ-1) - (i_dt(J,JJ).*constant(1).*((L(J,JJ-1)./hs)-1)^constant(2));
end
end
end

In the first case, you can't make an array of function handles. you can use cell arrays but I can't understand why you need it.
Let's work your second case.
%I don't know what is hs, I assume it a scalar
hs=myhs;
%call the Nelfun in your function handle
height_pred = #(constant,i_dt) Nelfun(constant,i_dt,hs);
options = optimoptions('lsqcurvefit','FinDiffType','central',...
'TolFun',1e-10);
% Constant0 should be 2 element array
constant0=[0,0];
constant = lsqcurvefit(height_pred,constant0,i_dt,height_meas,lb,ub,options);
function L = Nelfun(constant,i_dt,hs)% why hs is not passed
L=zeros(size(i_dt)); % Always initialize before filling it up
for jj=1:size(i_dt,2) %use adaptive indexing
if jj==1
L(:,jj)=0.0187600000000000;
continue
end
for j=1:size(i_dt,1) %use adaptive indexing
L(j,jj) = L(j,jj-1) - (i_dt(j,jj).*constant(1).*((L(j,jj-1)./hs)-1)^constant(2));
end
end

Related

Error showing "The variable 'uLS1' appears to change size on every loop iteration. consider preallocating for speed."

% 3. Calculation of strain energy density
% CALCULATION OF STRAIN-ENERGY-DENSITY FOR EACH LOAD CASE
% u=1/2*sigma*epsilon
for p = 1:N_ele
uLS1(p) = 1/2*(sigma_1(p,2:7)*epsilon_1(p,2:7)');
uLS2(p) = 1/2*(sigma_2(p,2:7)*epsilon_2(p,2:7)');
uLS3(p) = 1/2*(sigma_3(p,2:7)*epsilon_3(p,2:7)');
end
% AVERAGE OF ALL LOAD CASES
sed(:,a) = (uLS1' + uLS2' + uLS3')/3; %11 ... line
Error on command window:
"Unrecognized function or variable 'uLS1'."
Error in main_file (line 86)
sed(:,a) = (uLS1' + uLS2' + uLS3')/3;
Regarding the error: The variable sed must have N_ele rows such that size(sed,1) = N_ele. If the number N_ele changes with every iteration a, then you can use a cell instead an array, i.e., sed{a} = (uLS1' + uLS2' + uLS3')/3;.
Regarding the warning: Preallocate the arrays uLS1, uLS2, and uLS3 before the for-loop when you know the size they will have, i.e.,
uLS1 = zeros(1, N_ele);
uLS2 = zeros(1, N_ele);
uLS3 = zeros(1, N_ele);
If you don't know their sizes in advance, you have the choice to ignore Matlab's warning and proceed as is.

MATLAB - plot an iteration

The code so far:
function [fr]=frictionFactorFn(rho,mu,e,D,L,Q,f0,tol,imax)
format long
CS=(pi*D^(2))/4;%Cross sectional area of pipe
v=Q/CS;%velocity
Re=(rho*v*L)/mu;
iter=1;i=1;fr(1)=f0;
while 1
fr(i+1)=(-1.74*log((1.254/(Re*sqrt(fr(i))))+((e/D)/3.708)))^-2;%substitution for root finding
iter=iter+1;
if abs(fr(i+1)-fr(i))<tol || iter>=imax
break;
end
i=i+1;
end
fprintf('\n The Reynolds number is %f\n',Re);
plot(0:iter-1,fr);
xlabel('Number of iteration'),ylabel('friction factor');
end
It gave me the right converged value of f=0.005408015, but I would like to plot the iteration
Possibly by storing the values of f upon each iteration in an array. In this example the array is called Store_f and is plotted after the while-loop is completed. The variable Index below is used to indicate which cell of array Store_f the value should be saved to.
function [f_vals] = frictionfactorfn()
Index = 1;
while (Condition)
%Calculation code%
Store_f(Index) = f;
Index = Index + 1;
end
disp(num2str(f))
plot(Store_f,'Marker','.');
xlabel('Iteration'); ylabel('Value');
end

Fast way to get mean values of rows accordingly to subscripts

I have a data, which may be simulated in the following way:
N = 10^6;%10^8;
K = 10^4;%10^6;
subs = randi([1 K],N,1);
M = [randn(N,5) subs];
M(M<-1.2) = nan;
In other words, it is a matrix, where the last row is subscripts.
Now I want to calculate nanmean() for each subscript. Also I want to save number of rows for each subscript. I have a 'dummy' code for this:
uniqueSubs = unique(M(:,6));
avM = nan(numel(uniqueSubs),6);
for iSub = 1:numel(uniqueSubs)
tmpM = M(M(:,6)==uniqueSubs(iSub),1:5);
avM(iSub,:) = [nanmean(tmpM,1) size(tmpM,1)];
end
The problem is, that it is too slow. I want it to work for N = 10^8 and K = 10^6 (see commented part in the definition of these variables.
How can I find the mean of the data in a faster way?
This sounds like a perfect job for findgroups and splitapply.
% Find groups in the final column
G = findgroups(M(:,6));
% function to apply per group
fcn = #(group) [mean(group, 1, 'omitnan'), size(group, 1)];
% Use splitapply to apply fcn to each group in M(:,1:5)
result = splitapply(fcn, M(:, 1:5), G);
% Check
assert(isequaln(result, avM));
M = sortrows(M,6); % sort the data per subscript
IDX = diff(M(:,6)); % find where the subscript changes
tmp = find(IDX);
tmp = [0 ;tmp;size(M,1)]; % add start and end of data
for iSub= 2:numel(tmp)
% Calculate the mean over just a single subscript, store in iSub-1
avM2(iSub-1,:) = [nanmean(M(tmp(iSub-1)+1:tmp(iSub),1:5),1) tmp(iSub)-tmp(iSub-1)];tmp(iSub-1)];
end
This is some 60 times faster than your original code on my computer. The speed-up mainly comes from presorting the data and then finding all locations where the subscript changes. That way you do not have to traverse the full array each time to find the correct subscripts, but rather you only check what's necessary each iteration. You thus calculate the mean over ~100 rows, instead of first having to check in 1,000,000 rows whether each row is needed that iteration or not.
Thus: in the original you check numel(uniqueSubs), 10,000 in this case, whether all N, 1,000,000 here, numbers belong to a certain category, which results in 10^12 checks. The proposed code sorts the rows (sorting is NlogN, thus 6,000,000 here), and then loop once over the full array without additional checks.
For completion, here is the original code, along with my version, and it shows the two are the same:
N = 10^6;%10^8;
K = 10^4;%10^6;
subs = randi([1 K],N,1);
M = [randn(N,5) subs];
M(M<-1.2) = nan;
uniqueSubs = unique(M(:,6));
%% zlon's original code
avM = nan(numel(uniqueSubs),7); % add the subscript for comparison later
tic
uniqueSubs = unique(M(:,6));
for iSub = 1:numel(uniqueSubs)
tmpM = M(M(:,6)==uniqueSubs(iSub),1:5);
avM(iSub,:) = [nanmean(tmpM,1) size(tmpM,1) uniqueSubs(iSub)];
end
toc
%%%%% End of zlon's code
avM = sortrows(avM,7); % Sort for comparison
%% Start of Adriaan's code
avM2 = nan(numel(uniqueSubs),6);
tic
M = sortrows(M,6);
IDX = diff(M(:,6));
tmp = find(IDX);
tmp = [0 ;tmp;size(M,1)];
for iSub = 2:numel(tmp)
avM2(iSub-1,:) = [nanmean(M(tmp(iSub-1)+1:tmp(iSub),1:5),1) tmp(iSub)-tmp(iSub-1)];
end
toc %tic/toc should not be used for accurate timing, this is just for order of magnitude
%%%% End of Adriaan's code
all(avM(:,1:6) == avM2) % Do the comparison
% End of script
% Output
Elapsed time is 58.561347 seconds.
Elapsed time is 0.843124 seconds. % ~70 times faster
ans =
1×6 logical array
1 1 1 1 1 1 % i.e. the matrices are equal to one another

Averaging over adjacency groups in a loop

I have the following code which eventually outputs a graph and a 'groups' value. The results are dependent on a random function so can provide different results every times.
function [t seqBeliefs] = extendedHK(n, tol, adj)
%extendedHK Summary of function goes here
%Detailed explanation goes here
beliefs = rand(n,1);
seqBeliefs = beliefs; %NxT matrix
converge = 0;
step = 0
t = step
while converge ~= 1
step = step+1;
t = [t step];
A = zeros (n,n);
for i=1:1:n
for j=i:1:n
if abs(beliefs(i) - beliefs(j)) < tol && adj(i,j)==1
A(j,i)=1;
A(i,j)=1;
end
end
end
beliefs = A*beliefs./ sum(A,2);
seqBeliefs = [seqBeliefs beliefs];
if sum(abs(beliefs - seqBeliefs(:,step)))<1e-12
converge = 1;
end
end
groups = length(uniquetol(seqBeliefs(:,step), 1e-10))
plot(t,seqBeliefs)
end
In command window type
adj=random_graph(n)
I usually use n as 100 then call extendedHK function with same n then tol value (I usually choose between 0.1 and 0.4) and 'adj'
e.g. adj = random_graph(100)
extendedHK(100, 0.2, adj)
What I now need help with is running this function say 100 times, and taking an average of how many 'groups' are formed.
First, include the parameter "groups" in your function output. to do so, try this instead of the first line of your code:
function [t seqBeliefs groups] = extendedHK(n, tol, adj)
then save this function in a extendedHK.m file.
open another .m file, say console.m and write this:
results = zeros(1,100);
for i = 1:100
% set n, tol and adj inputs here <=
[~,~,out] = extendedHK(n, tol, adj);
results(1,i) = out;
end
avg = mean(results)
don't forget to define "results" out of the loop. since parameters that change size every loop, will make your code slow
(suppressing unnecessary outputs with ~ added later to this reply)
It is not clear if you need the current outputs of extendedHK:[t seqBeliefs]
Anyhow, you can add "groups" to the output and then from the console
N=100;
groups_vec = zeros(1,N);
for i=1:N
adj = random_graph(100);
[~,~,groups_vec(i)] = extendedHK(100, 0.2, adj);
end
groups_avr = mean(groups_vec);
note that if you use this code you won't be able to "see" the graphs as they will be cleared every loop iteration. you can do one of the following (1) add "figure;" before the plot command and then you will have 100 figures. (2) add "pause" to wait for key press between each graph. (3) add "hold on" to print all graphs on the same figure.

In Matlab I keep getting undefined variable or function error despite clearly defining the variable

I have this piece of code in Matlab which should take an Airfoil profile and increase the number of points so that when I plot the profile in another programme I will get a smoother curve.
clear
%reading an external data file
fid = fopen('NACA0015.txt');
a = fscanf(fid,'%g %g',[2 inf]); % It has two rows now.
a = a'; % matrix transpose
n = input('200') %e.g., n=35
for i=1:n
for j=1:2
fprintf('%12.7f',a(i,j)); %a(i,1) is first column, a(i,2) is 2nd col
end
fprintf('\n');
end
fclose(fid);
for i=1:n
x(i)=a(i,1); %x , y vectors
y(i)=a(i,2);
end
% use spline to create more points
xx=0:0.01:1 % e.g., step =0.01 (number of points = (1-0)/0.01=100)
yy = spline(x,y,xx); % xx and yy are new generated values of Naca0012
fprintf('\n print spline values \n');
plot(xx,yy,'ro')
hold on
plot(x,y,'*')
When I run this I get the error
Undefined function or variable 'x'.
Error in reading_external_data_and_spline (line 26)
yy = spline(x,y,xx); % xx and yy are new generated values of Naca0012
I am at a complete loss as to why this is not working when the x variable is clearly defined in the code, please could someone help me with this
It's how you're using input. The argument in input isn't the default value, it's the prompt text. If you type the command into the console and hit enter, you get this:
>> n = input('200')
200
n =
[]
>>
Input doesn't accept a default. If you really want to have an interactive prompt with a default answer, you want inputdlg:
answer = inputdlg('Enter a number of lines to parse', 'n', 1, '200');
n = str2double(answer);
note that inputdlg returns text always, so you need to convert to a number.