Using Matlab's lsqcurvefit to Calculate Infinite Relaxation Spectrum - matlab

I am trying to replicate an analysis performed in a publication and am having trouble. This is about trying to calculate the infinite relaxation spectrum from rheological data.
Given an experimental data series (above), the authors extract the relaxation spectrum (H(tau)) by solving for the difference of the sum of squares between experimental (above) and model data (below).
This relaxation follows a distribution described by the following relationship.
This calculation was performed with matlabs lsqcurefit function. However, lsqcurvefit does not accept a function as a solvable parameter. I would like to know how this extraction can be accomplished using this matlab function.
EDIT: Following Bentoys comments, here is a clarification of my question.
I will detail the following just for G'' (Gdp) to save space.
I have a experimental data in the following form: a vector of frequency values (omega), xdata, and a vector of Gdp response values (Gdp).
I would like to calculate H(tau), and to do so I will need the parameters contained within that function. This gives the following expression that I need to resolve:
Gdp is a function of omega, and my xdata is a vector of omega values, but I am integrating with respect to ln(tau). This seems like it could be a possible problem?
Also, I do not have a clear ideal of expected initial values for the 6 variables, only for the resulting H(tau), so have chosen arbitrary values to begin with. I can optimise their relative values if calculated values can be obtained.
From your suggestions my matlab code is as follows:
w = numdata(:,1); %w is omega (experimental xdata)
GdpExp = numdata(:,3); % response values (ydata)
x0 = [10,10,0.1,0.1,1,1]; % arbitrary intial values
H = #(x, xdata) x(1)*exp(-(xdata-log(x(3))).^2/x(5)^2/2)...
+ x(2)*exp(-(xdata-log(x(4))).^2/x(6)^2/2);
Gdp = #(A_1, A_2, tau_1, tau_2, sigma_1, sigma_2, w) ...
integral(H([A_1, A_2, tau_1, tau_2, sigma_1, sigma_2], ...
u).* w.*exp(u)./(1+w.^2.*exp(2*u)), -Inf, Inf);
lsqcurvefit(Gdp, x0, w, GdpExp);
This currenlty gives the following error:
>> lsqcurvefit(Gdp, x0, w, GdpExp);
Not enough input arguments.
Error in
Inf_Spec_Test>#(A_1,A_2,tau_1,tau_2,sigma_1,sigma_2,w)integral
(H([A_1,A_2,tau_1,tau_2,sigma_1,sigma_2],u).*w.*exp(u)./(1+w.^2.*exp(2*u)),
-Inf,Inf)
Error in lsqcurvefit (line 202)
initVals.F = feval(funfcn_x_xdata{3},xCurrent,XDATA,varargin{:});
Caused by:
Failure in initial objective function evaluation. LSQCURVEFIT cannot
continue.
Am I correct in thinking that each function is self-contained or should the naming for A_1, A_2 etc. be the same as x(1), x(2), or are the A_1 etc. simply labels to refer to the calculated values?
The experimental data and calculated H(tau) should resemble the following figures.
I also found the following relationship, which could be used to transform the omega vector into a tau vector, which might be helpful for overcoming the discrepancy between the current xdata and the integral.

Assuming you have experimental data of H(tau) in the form of a vector Y, you want to fit your model H:
H = #(A_1, A_2, tau_1, tau_2, sigma_1, sigma_2, tau) ...
A1*exp(-(log(tau)-log(tau_1)).^2/sigma_1^2/2) + A2*exp(-(log(tau)-log(tau_2)).^2/sigma_2^2/2);
lsqcurvefit expects a function in the form f(x, xdata). Here xdata is the "time data", naming tau, and x all coefficients of your function. So, rewriting function H gives:
H = #(x, xdata) x(1)*exp(-(log(xdata)-log(x(3))).^2/x(5)^2/2) + x(2)*exp(-(log(xdata)-log(x(4))).^2/x(6)^2/2);
Then I asusme that you have some initial guess for the values of all six coefficients. If not, the fitting may not converge to the appropriate solution. I call this initial guess x0 which is a vector of length 6. To get the fit, just call:
lsqcurvefit(H, x0, tau, Y)
You may want to add more options (such as parameter bounds), see documentation of lsqcurvefit for more details.
EDIT
Following details in comments below, you have a vector of values G+G' for several values of omega (in a vector W).
So the function you want to fit is a little more complex:
H = #(x, xdata) x(1)*exp(-(xdata-log(x(3))).^2/x(5)^2/2) + x(2)*exp(-(xdata-log(x(4))).^2/x(6)^2/2);
GGp = #(A_1, A_2, tau_1, tau_2, sigma_1, sigma_2, w) ...
integral(H([A_1, A_2, tau_1, tau_2, sigma_1, sigma_2], u).* w.*exp(u).*(1+w.*exp(u))./(1+w.^2.*exp(2*u)), -Inf, Inf);
after a little change of variable, u = ln(tau).
Then it is exactly the same thing, except you have now a different function to fit:
lsqcurvefit(GGp, x0, W, Y)

Related

Plot log(n over k)

I've never used Matlab before and I really don't know how to fix the code. I need to plot log(1000 over k) with k going from 1 to 1000.
y = #(x) log(nchoosek(1000,x));
fplot(y,[1 1000]);
Error:
Warning: Function behaves unexpectedly on array inputs. To improve performance, properly
vectorize your function to return an output with the same size and shape as the input
arguments.
In matlab.graphics.function.FunctionLine>getFunction
In matlab.graphics.function.FunctionLine/updateFunction
In matlab.graphics.function.FunctionLine/set.Function_I
In matlab.graphics.function.FunctionLine/set.Function
In matlab.graphics.function.FunctionLine
In fplot>singleFplot (line 241)
In fplot>#(f)singleFplot(cax,{f},limits,extraOpts,args) (line 196)
In fplot>vectorizeFplot (line 196)
In fplot (line 166)
In P1 (line 5)
There are several problems with the code:
nchoosek does not vectorize on the second input, that is, it does not accept an array as input. fplot works faster for vectorized functions. Otherwise it can be used, but it issues a warning.
The result of nchoosek is close to overflowing for such large values of the first input. For example, nchoosek(1000,500) gives 2.702882409454366e+299, and issues a warning.
nchoosek expects integer inputs. fplot uses in general non-integer values within the specified limits, and so nchoosek issues an error.
You can solve these three issues exploiting the relationship between the factorial and the gamma function and the fact that Matlab has gammaln, which directly computes the logarithm of the gamma function:
n = 1000;
y = #(x) gammaln(n+1)-gammaln(x+1)-gammaln(n-x+1);
fplot(y,[1 1000]);
Note that you get a plot with y values for all x in the specified range, but actually the binomial coefficient is only defined for non-negative integers.
OK, since you've gotten spoilers for your homework exercise anyway now, I'll post an answer that I think is easier to understand.
The multiplicative formula for the binomial coefficient says that
n over k = producti=1 to k( (n+1-i)/i )
(sorry, no way to write proper formulas on SO, see the Wikipedia link if that was not clear).
To compute the logarithm of a product, we can compute the sum of the logarithms:
log(product(xi)) = sum(log(xi))
Thus, we can compute the values of (n+1-i)/i for all i, take the logarithm, and then sum up the first k values to get the result for a given k.
This code accomplishes that using cumsum, the cumulative sum. Its output at array element k is the sum over all input array elements from 1 to k.
n = 1000;
i = 1:1000;
f = (n+1-i)./i;
f = cumsum(log(f));
plot(i,f)
Note also ./, the element-wise division. / performs a matrix division in MATLAB, and is not what you need here.
syms function type reproduces exactly what you want
syms x
y = log(nchoosek(1000,x));
fplot(y,[1 1000]);
This solution uses arrayfun to deal with the fact that nchoosek(n,k) requires k to be a scalar. This approach requires no toolboxes.
Also, this uses plot instead of fplot since this clever answer already addresses how to do with fplot.
% MATLAB R2017a
n = 1000;
fh=#(k) log(nchoosek(n,k));
K = 1:1000;
V = arrayfun(fh,K); % calls fh on each element of K and return all results in vector V
plot(K,V)
Note that for some values of k greater than or equal to 500, you will receive the warning
Warning: Result may not be exact. Coefficient is greater than 9.007199e+15 and is only accurate to 15 digits
because nchoosek(1000,500) = 2.7029e+299. As pointed out by #Luis Mendo, this is due to realmax = 1.7977e+308 which is the largest real floating-point supported. See here for more info.

How to use lsqcurvefit to fit a rational function?

I want to fit a rational function using the curve fitting technique in MATLAB.
I am trying to use lsqcurvefit to reproduce a rational function M, which takes 5 inputs, with the data outputted from the exact function C. I think I am close to getting the plots; however, when I use lsqcurvefit, I keep getting an error saying:
LSQCURVEFIT requires the following inputs to be of data type double: 'YDATA'.
Below is my code:
% Define range for k
k= linspace(1E-10,1.5,100);
% Exact Function C(k)
C= #(k)(0.5*((1i*k+0.135).* (1i*k+0.651)))./((1i*k+0.0965).* (1i*k+0.4555));
% Cget function used to extract real and imaginary numbers and stacks the result
Cget= #(k)[real(C(k)); imag(C(k))];
%Call function Cget(k) to get stacked real and imaginary values
realimag =Cget(k);
% Create an initial guess
x0=[1,1];
% Define parameters
a1=0.2; a2=0.7; b1=0.1; b2=0.5;
% Define approximated function
M= #(a1,a2,b1,b2,k)(0.5*((1i*k+a1).* (1i*k+a2)))./((1i*k+b1).* (1i*k+b2));
Mget1= #(a1,a2,b1,b2,k)[real(M(a1,a2,b1,b2,k)); imag(M(a1,a2,b1,b2,k))];
T=Mget1(a1,a2,b1,b2,k);
%Find best fit curve
x=lsqcurvefit(M,x0,k,C)
How can I fit reproduce M, given the exact function C?
The variable C should be entered as a double array so that the function should get the input range and use it in the calculation.
% Exact Function C(k)- Bessel Function
C0 = (0.5*((1i*k+0.135).* (1i*k+0.651)))./((1i*k+0.0965).* (1i*k+0.4555));
and parameters of x could be entered as an array with 4 values.
M = #(x,k)(0.5*((1i*k+x(1)).* (1i*k+x(2))))./((1i*k+x(3)).* (1i*k+x(4)));
and x0 should have 4 values.
x0 = [1,1,1,1];
%Find best fit curve
x = lsqcurvefit(M,x0,k,C0)

Using fzero in Matlab or Octave, avoiding for loop and complex solutions

I'm using fzero function to solve a non-linear equation depending on one parameter
and I'm not satisfied with my method. I have these issues:
1) Can for loop for the parameter be avoided ?
2) In order to avoid complex solutions I first have to pre-compute valid interval for fzero.
Is there is a better solution here ?
If I reduce the parameter step size the execution time becomes slow. If I don’t pre-compute
the interval I get an error "Function values at interval endpoints must be finite and real."
in Matlab and "fzero: not a valid initial bracketing" in Octave.
Here is the code
% solve y = 90-asind(n*(sind(90-asind(sind(a0)/n)-y)))
% set the equation paramaters
n=1.48; a0=0:0.1:60;
% loop over a0
for i = 1:size(a0,2)
% for each a0 find where the argument of outer asind()
% will not give complex solutions, i.e. argument is between 1 and -1
fun1 = #(y) n*(sind(90-asind(sind(a0(i))/n)-y))-0.999;
y1 = fzero(fun1,[0 90]);
fun2 = #(y) n*(sind(90-asind(sind(a0(i))/n)-y))+0.999;
y2 = fzero(fun2,[0 90]);
% use y1, y2 as limits in fzero interval
fun3 = #(y) 90-asind(n*(sind(90-asind(sind(a0(i))/n)-y)))-y;
y(i) = fzero(fun3, [y1 y2]);
end
% plot the result
figure; plot(y); grid minor;
xlabel('Incident ray angle [Deg]');
ylabel('Lens surface tangent angle');
With Matlab, I obtained the plot below with the following simplified loop.
for i = 1:size(a0,2)
fun3 = #(y) sind(90-y) - n*(sind(90-asind(sind(a0(i))/n)-y));
y(i) = fzero(fun3, [0,90]);
end
The difference is in the form of equation: I replaced 90-y = asind(something) with sin(90-y) = something. When "something" is greater than 1 in absolute value, the former version throws an error due to complex value of asind. The latter proceeds normally, recognizing that this is not a solution (sin(90-y) can't be equal to something that is greater than 1).
No precomputing of the bracket was necessary, [0,90] simply worked. Another change I made was in the plot: plot(a0,y) instead of plot(y), to get the correct horizontal axis.
And you can't avoid for loop here, nor should you worry about it. Vectorization means eliminating loops where the content is a low-level operation that can be done en masse by operating on some C array. But fzero is totally not that. If the code takes long to run, it's because solving a bunch of equations takes long, not because there's a for loop.

How to perform convolution using Fourier Series

OK let me cut to the chase.
I am trying to use MATLAB to
(i)generate the fourier series based on known coefficients and thereafter
(ii) determine the output function when the impulse is known.
So far I used this code to obtain the fourier series:
clear all
syms x k L n
evalin(symengine,'assume(k,Type::Integer)');
a = #(f,x,k,L) (2/(pi*k))* sin((pi*k)/(2 * L));
fs = #(f,x,n,L) (1/2*L) + symsum(a(f,x,k,L)*cos(k*2*pi*x/L),k,1,n);
f = x;
pretty(fs(f,x,11,1))
This works as desired. Now the impulse response is as follows:
h = heaviside(x) * exp(-5*x);
Now, in order to obtain the function, we need to perform the convolution with the respective functions.But when I input the following, I get the error:
x1 = fs(f,x,1,1);
conv(h,x1)
Undefined function 'conv2' for input arguments of type 'sym'.
Error in conv (line 38)
c = conv2(a(:),b(:),shape);
Any help would be appreciated
That is because conv is only defined for numeric inputs. If you want to find the convolution symbolically, you'll have to input the equation yourself symbolically using integration.
If you recall, the convolution integral is defined as:
Source: Wikipedia
Therefore, you would do this:
syms x tau;
F = int(h(tau)*x1(x-tau),'tau',-inf,+inf);
int is a function in MATLAB that does symbolic integration for you. Also note that the convolution integral is commutative, and so this also works:
Source: Wikipedia
Therefore, you should also get the same answer if you did:
syms x tau;
F = int(h(x-tau)*x1(tau),'tau',-inf,+inf);
Hope this helps!

Fitting data in least square sense to nonlinear equation

I need help fitting data in a least square sense to a nonlinear function. Given data, how do I proceed when I have following equation?
f(x) = 20 + ax + b*e^(c*2x)
So I want to find a,b and c. If it was products, I would linearize the function by takin the natural logaritm all over the function but I can not seem to do that in this case.
Thanks
You can use the nlinfit tool, which doesn't require the Curve Fitting Toolbox (I don't think...)
Something like
f = #(b,x)(20 + b(1)*x + b(2)*exp(b(3)*2*x));
beta0 = [1, 1, 1];
beta = nlinfit(x, Y, f, beta0);
When MATLAB solves this least-squares problem, it passes the coefficients into the anonymous function f in the vector b. nlinfit returns the final values of these coefficients in the beta vector. beta0 is an initial guess of the values of b(1), b(2), and b(3). x and Y are the vectors with the data that you want to fit.
Alternatively, you can define the function in its own file, if it is a little more complicated. For this case, you would have something like (in the file my_function.m)
function y = my_function(b,x)
y = 20 + b(1)*x + b(2)*exp(b(3)*2*x);
end
and the rest of the code would look like
beta0 = [1, 1, 1];
beta = nlinfit(x, Y, #my_function, beta0);
See also: Using nlinfit in Matlab?
You can try the cftool which is an interactive tool for fitting data. The second part I don't quite understand. It may help if you describe it in more detail.