问题
I have the Curve Fitting toolbox installed and I'm trying to fit diffusion-data to a specific function.
The function is an error-function of the form:
y = 3500 - 2500 * erf( ( x-x0 ) / ( 2 * sqrt( D * t )) )
I want the app to give me reasonable values for D and x0, while t is a predefined constant. The data-points, which the fitting is based on include values for x and y.
I know that D should be around 1e-11 and x0 is around 0.0014 but the function won't find these solutions on its own. Matlab outputs the error "Input must be real and full." when trying with the default parameters of the curve fitting app. When I set the starting guess of x0 to 0.0014 or 0.0015, it will find the correct solution. But only with those two values. To find the correct solution for D I need to set a pre-factor in the equation, like in this example (1e-12):
y = erf( ( x-x0 ) / ( 2 * sqrt( 1e-12 * D * t )) )
In this way matlab finds the correct solution but only for pre-factors between 1e-10 to 1e-13.
This is highly problematic as the correct solutions for D will vary between 1e-3 and 1e-15, depending on the datasets I am going to use. Also the values for x0 will vary. So in this way I cannot implement a general solution.
Do you have suggestions how to handle this issue? I cannot believe that matlab isn't able to solve this, there must be a way. Is it because the values are so small?
Here is an example dataset I'm working with:
y = [6000 6000 6000 6000 6000 6000 6000 6000 6000 5750 5500 5250 5000 4500 4000 3250 2750 2250 1750 1500 1400 1250 1250 1150];
x = [0:0.0001:0.0023];
When using the following fixed parameters in the equation, the resulting line fits the data-points very good. But matlab won't find them.
D = 7.1e-11;
t = 900;
x0 = 0.0015;
(Keep in mind that these parameters are based on a larger and more accurate dataset than those I provided here)
Any help would be great! Thank you very much.
Here's a working example with all predefined parameters to get the fit working. Code was generated by the Curve Fitting tool (cftool):
%% Fit: 'untitled fit 1'.
% Input data
C = [6000 6000 6000 6000 6000 6000 6000 6000 6000 5750 5500 5250 5000 4500 4000 3250 2750 2250 1750 1500 1400 1250 1250 1150]';
x = [0:0.0001:0.0023]';
[xData, yData] = prepareCurveData( x, C );
% Set up fittype and options.
ft = fittype( '3500-2500*erf((x-x0)/(2*sqrt(1e-10*D*900)))', 'independent', 'x', 'dependent', 'y' );
opts = fitoptions( 'Method', 'NonlinearLeastSquares' );
opts.Display = 'Off';
opts.MaxIter = 4000;
opts.StartPoint = [0.5 0.0014];
opts.Upper = [1 Inf];
% Fit model to data.
[fitresult, gof] = fit( xData, yData, ft, opts )
% Plot fit with data.
figure( 'Name', 'untitled fit 1' );
h = plot( fitresult, xData, yData );
legend( h, 'C vs. x', 'untitled fit 1', 'Location', 'NorthEast' );
% Label axes
xlabel x
ylabel C
grid on
(Plot only for convenience)
Please note that I included the pre-factor 1e-10 in the sqrt()-term, and that I used 0.0014 as a starting guess for x0, otherwise the fit won't work.
回答1:
There seems to be 2 problems:
Your function is not defined for every real value. Assuming you are only interested in positive values for
D
andx0
, you can simply bound the search range to positive number by specifying the lower bounds as[0 0]
Matlab has problems to evaluate the derivative of your function numerically due to the very small values. Therefore, the best solution is to calculate the jacobian of your function symbolically. This can be done using the symbolic toolbox:
syms x x0 D t real; y = 3500 - 2500 * erf( ( x-x0 ) / ( 2 * sqrt( D * t )) ) J = jacobian(y, [D, x0]);
You can use matlabFunction to convert it to an ordinary matlab function or just copy and paste the result in your script, so that you do not have to calculate the jacobian symbolically in each iteration.
You should set the option 'SpecifyObjectiveGradient',true to tell matlab to use your exact jacobian.
Implementing both solutions results in the following code:
ydata = [6000 6000 6000 6000 6000 6000 6000 6000 6000 5750 5500 5250 5000 4500 4000 3250 2750 2250 1750 1500 1400 1250 1250 1150];
xdata = 0:0.0001:0.0023;
t = 900;
D = 0.1;
x0 = 0.1;
options = optimoptions('lsqcurvefit', 'SpecifyObjectiveGradient',true);
X = lsqcurvefit(@(x, xdata) y(x(1), x(2), t, xdata),[D, x0], xdata, ydata, [0 0], [], options);
D = X(1); % 6.4833e-11
x0 = X(2); % 0.0015
figure
hold on
plot(xdata, ydata);
plot(xdata, y(D, x0, t, xdata));
function [F, J] = y(D, x0, t, x)
F = 3500 - 2500 * erf( ( x-x0 ) / ( 2 * sqrt( D * t )) );
J = [(1250.*t.*exp(-(x - x0).^2/(4.*D.*t)).*(x - x0))/(pi^(1/2).*conj((D.*t).^(3/2))); ...
(2500.*exp(-(x - x0).^2/(4.*D.*t)))/(pi.^(1/2).*conj((D.*t).^(1/2)))]';
end
来源:https://stackoverflow.com/questions/46080580/matlab-curve-fitting-wont-work-for-small-values-1e-12-what-can-i-do