Adding constraints to lsq fitting
Show older comments
I have a code that is of a function g(X,Y). The below code fits my data very well. However, I want to add some constraints at the boundaries where my data is not. For instance, how can I set the coefficient such that when Y=0, then the value of my function is 1?
Can anyone help with this?
clear
clc
close all
num=xlsread('C:data.xlsx',1,'A2:F18110');
eta = num(:,3);
r = num(:,4);
g = num(:,6);
%Do the surface fit
options=optimoptions(@lsqcurvefit,'SpecifyObjectiveGradient',false,'MaxFunctionEvaluations',50000,'MaxIterations',1000);
xdata={r,eta};
params0=linspace(0.1, 0.1, 50);
[params, resnorm]=lsqcurvefit(@modelfun,params0,xdata,g,[],[],options)
for i=1:50
C(i)=params(i);
end
xmin = 1; xmax = 2; dx = 0.01;
etamin=0; etamax=0.55; deta=0.01;
[Xgrid,etagrid]=ndgrid(xmin:dx:xmax,etamin:deta:etamax);
surf(Xgrid,etagrid,modelfun(params,{Xgrid,etagrid}))
zlim([0 8]);
hold on;
scatter3(r(:),eta(:),g(:),'MarkerEdgeColor','none',...
'MarkerFaceColor','b','MarkerFaceAlpha',.5); hold off
xlabel 'x', ylabel '\eta', zlabel 'g(x,\eta)'
function [out,Jacobian]=modelfun(params,xdata)
X=xdata{1};
Y=xdata{2};
for i=1:50
C(i)=params(i);
end
A1 = -0.4194;
A2 = 0.5812;
A3 = 0.6439;
A4 = 0.4730;
eta_c = 0.6452;
PV = 1 + 3*Y./(eta_c-Y)+ A1*(Y./eta_c) + 2*A2*(Y./eta_c).^2 + 3*A3*(Y./eta_c).^3 + 4*A4*(Y./eta_c).^4;
rdf_contact = (PV - 1)./(4*Y);
poly_guess = polyVal2D(C,X-1,Y/eta_c,4,4);
out = (poly_guess.*rdf_contact);
if nargout>1
%Jacobian=[X(:), Y(:), ones(size(X(:)))];
end
end
Answers (3)
Adam Danz
on 20 Mar 2019
2 votes
"...I want to add some constraints at the boundaries... "
In your call to lsqcurvefit(), you're not using the upper and lower bounds options (5th and 6th inputs are empty). I'd start out by imposing boundaries.
"For instance, how can I set the coefficient such that when Y=0, then the value of my function is 1"
I don't follow this part. The variable "Y" doesn't exist in your sample code. Also, what do you mean "the value of my function"? Do you mean the output of your nonlinear function or do you mean the coefficient estimates produced by the fit?
13 Comments
Also, in the bounds part, how do I specify which variable is being bounded?
In the syntax,
x = lsqcurvefit(fun,x0,xdata,ydata,LB,UB,options)
the inputs LB and UB are lower and upper bounds on your parameters. If you set LB(i)=UB(i)=somevalue, then x(i) will be constrained to equal somevalue. So if, for example, you want to force your first coefficient to be zero and all the rest unconstrained, you could do
LB=-inf(size(x0));
UB=+inf(size(x0));
LB(1)=0; UB(1)=0 % forces first unknown parameter to be zero
x = lsqcurvefit(fun,x0,xdata,ydata,LB,UB,options)
I'm having trouble following the thread - mostly because I don't know which function you're refering to ("...the whole function needs to equal 1").
The upper/lower bound inputs to lsqcurvefit() will restrain the coeficient estimate outputs. Are you saying that you'd like a set of coeficients that, when used as inputs to your modelfun() function, will result in an output of 1?
[UPDATED] in response to your updated comment above.
LB(1)=1; UB(1)=1; forces the bound to be 1 (your inline comments says 0) on the first coefficient. This coefficient is corresponds to the first parameter of your nonlinear function.
Adam Danz
on 20 Mar 2019
Couldn't you just restrain that as well?
LB(2)=0; UB(2)=0; % forces 2nd parameter to be zero
Matt J
on 20 Mar 2019
What order are they in?
I believe we covered that in a previous thread. The help doc for polyFit2D explains the ordering of the coefficients.
Benjamin
on 20 Mar 2019
Adam Danz
on 20 Mar 2019
% Polynomial coefficients are in the following order.
%
% F(X,Y) = P_1 * X^N * Y^M + P_2 * X^{N-1} * Y^M + ... + P_{N+1} * Y^M + ...
% P_{N+2} * X^N * Y^{M-1} + P_{N+3} * X^{N-1} * Y^{M-1} + ... + P_{2*(N+1)} * Y^{M-1} + ...
% ...
% P_{M*(N+1)+1} * X^N + P_{M*(N+1)+2} * X^{N-1} + ... + P_{(N+1)*(M+1)}
Benjamin
on 20 Mar 2019
Here is an adaptation of the algebraic solution that I presented in your previous thread. I don't bother with the lsqcurvefit alternative, since as I showed you, it is both slower and less accurate. Below are also surface plots of the fitted surface, both in the neighborhood of the data points and near eta=0 where you can see that everything converges to the target value parameter, g0, as eta-->0. In this example, I have set g0=1.3.


clear
clc
close all
%% Data Set-up
num=xlsread('example.xlsx',1,'A2:F18110');
Np=4; %polynomial order
g0=1.3; %target value at eta=0
g = num(:,6);
r = num(:,4);
eta = num(:,3);
otherStuff.r = r;
otherStuff.eta = eta;
otherStuff.g = g;
otherStuff.eta_c = 0.6452;
otherStuff.Np=Np;
otherStuff.A1 = -0.4194;
otherStuff.A2 = 0.5812;
otherStuff.A3 = 0.6439;
otherStuff.A4 = 0.4730;
%% Fit using matrix algebra
A0=func2mat(@(p) modelfun(p,otherStuff), ones(Np+1,Np+1));
q=4*g0./((3+otherStuff.A1)/otherStuff.eta_c); %weight on limiting target value as eta-->0
b=g(:)-q*A0(:,1);
A=A0;
A(:,1:Np+1)=[];
coeffs =[zeros(Np+1,1); A\b]; %fitted coefficients
coeffs(1)=q;
figure(1); showFit(coeffs,otherStuff);
%% Also check surface near eta=0
[Rg,Eg]=ndgrid(unique(r),linspace(1e-8,0.01,100));
differentStuff=otherStuff;
differentStuff.r = Rg(:);
differentStuff.eta = Eg(:);
differentStuff.g=nan([numel(Rg),1]);
figure(2); showFit(coeffs,differentStuff);
function ghat=modelfun(C,otherStuff)
r = otherStuff.r;
eta = otherStuff.eta;
eta_c = otherStuff.eta_c;
Np = otherStuff.Np;
A1 = otherStuff.A1;
A2 = otherStuff.A2;
A3 = otherStuff.A3;
A4 = otherStuff.A4;
PV = 1 + 3*eta./(eta_c-eta)+ A1*(eta./eta_c) + 2*A2*(eta./eta_c).^2 +...
3*A3*(eta./eta_c).^3 + 4*A4*(eta./eta_c).^4;
rdf_contact = (PV - 1)./(4*eta);
Cflip=rot90(reshape(C,Np+1,Np+1),2);
poly_guess = polyVal2D(Cflip,r-1,eta/eta_c,Np,Np);
ghat = (poly_guess.*rdf_contact);
end
function showFit(coeffs,otherStuff)
r = otherStuff.r(:);
eta = otherStuff.eta(:);
g = otherStuff.g(:);
ghat=modelfun(coeffs,otherStuff);
tri = delaunay(r,eta);
%% Plot it with TRISURF
h=trisurf(tri, r, eta, ghat);
h.EdgeColor='b';
h.FaceColor='b';
axis vis3d
hold on;
scatter3(r,eta,g,'MarkerEdgeColor','none',...
'MarkerFaceColor','r','MarkerFaceAlpha',.05);
xlabel 'r', ylabel '\eta', zlabel 'g(r,\eta)'
hold off
end
7 Comments
Benjamin Cowen
on 21 Mar 2019
Wow this is impressive! I’m not on my computer now so it’s hard to test it out myself, but it looks good! I’ll have to go through line by line to see what you did because it looks pretty different than what I had before. Could you explain why this method is better than lsq method? How is it even solving for the coefficients here if it is not iterative?
Matt J
on 21 Mar 2019
Your model function is linear (in the coefficients). Therefore, there exists a matrix A and vector b such that the coefficients c is the solution to,

This has an analytic solution that is computed in Matlab via c=A\b. Note that this only works because your model function is linear. If you ever tweak the model making it non-linear you would have to go back to lsqcurvefit.
If you could tell me explictly, what term in the C matrix is C00, C11, C12, etc, that would super helpfu. As it stands, I have no idea which is which.
In modelfun, I have flipped the input to polyVal2D to make the ordering of the coefficients is more intuitive. In my implementation C(i,j) corresponds to the mononomial term r.^(i-1).*eta.^(j-1), for i,j=1,...,Np+1. So, it is esentially set up now as you've suggested. By making C(1,1)=constant and C(2:Np+1,1)=0, the surface is made to converge to a common value as eta-->0 for all values of r.
1) What exactly is the g0=1.3? I need my function to equal 1 at eta = 0.
g0 is an adjustable parameter. If you want the function to approach 1 as eta-->0, then set g0=1. Note that your surface cannot actually be evaluated at eta=0 (as currently coded) because the weighting term rdf_contact is infinite at eta=0. I think that you can fix that by re-implenting rdf_contact as below. This gets rid of all divisions by eta.
rdf_contact = 3./(eta_c-eta)+ A1*(1./eta_c) + 2*A2*eta./eta_c.^2 +...
3*A3*eta.^2./eta_c.^3 + 4*A4*eta.^3/eta_c.^4;
Cflip=rot90(reshape(C,Np+1,Np+1),2);
poly_guess = polyVal2D(Cflip,r-1,eta/eta_c,Np,Np);
ghat = (poly_guess.*rdf_contact);
Can't we just set the C00 coefficeint to 1 and the Ci0 coefficients to 0?
The first coefficient cannot be set to 1 because it must account for additional weighting contributed by rdf_contact.
2) How do I add more constraints...So to be clear. I need (where x = i, eta = j)
Didn't follow that. The coefficients themselves are not functions of r and eta. They are coefficients of things that are functions of r and theta.
3) isn't the last term supposed to be this coefficient?
As I mentioned, I flipped the coefficients. That's what this line does
Cflip=rot90(reshape(C,Np+1,Np+1),2);
4) In the plotted figure 1, how can I make the surface extend to eta = 0? Currently, it stops where the data does.
This should do it. Now you can pass additional points [R,Eta] to showFit, which you would like added to the surface:
function showFit(coeffs,otherStuff, R,Eta)
r = otherStuff.r(:);
eta = otherStuff.eta(:);
g = otherStuff.g(:);
otherStuff.r=[r;R(:)];
otherStuff.eta=[eta;Eta(:)];
ghat=modelfun(coeffs,otherStuff);
tri = delaunay(otherStuff.r,otherStuff.eta);
%% Plot it with TRISURF
h=trisurf(tri, otherStuff.r, otherStuff.eta, ghat);
h.EdgeColor='b';
h.FaceColor='b';
axis vis3d
hold on;
scatter3(r,eta,g,'MarkerEdgeColor','none',...
'MarkerFaceColor','r','MarkerFaceAlpha',.05);
xlabel 'r', ylabel '\eta', zlabel 'g(r,\eta)'
hold off
end
Matt J
on 21 Mar 2019
Can you remove the weighting function, and then I can just set my coeffs to what they need to be?
It's your model. I assume rdf_contact has a reason to be there. As you'll recall, that was the whole reason you couldn't simply do this with straight up polynomial fitting with the Curve Fitting Toolbox.
Do I add like r = [1 2] and eta=[0 1]?
You add whatever additional points you want included in the surface plot.
I have no idea where this came from:
By setting C(1,1) to some constant q, as you've been proposing, and C(2:Np+1,1)=0 and letting eta-->0 you will find that the surface approaches the limiting value
LimitingValue=(3+otherStuff.A1)/otherStuff.eta_c/4 * q;
Therefore, setting
q=4.*g0./((3+otherStuff.A1)/otherStuff.eta_c));
leads to
LimitingValue = g0
Matt J
on 21 Mar 2019
Any chance you could modify the code without the weighting function and with the new data? Note that this means rdf_contact and all the coeffs (A1, A2, ..) with it can be removed.
It's virtually trivial now, with the Curve Fitting Toolbox:
%% Load Data
num=xlsread('example2.xlsx',1,'A2:F18110');
eta_c= 0.6452;
r = num(:,4);
eta = num(:,3);
H = num(:,5);
%% Set-up for fit
[I,J]=ndgrid(0:4);
Terms= compose('C%u%u*r^%u*eta^%u', I(:),J(:),I(:),J(:)) ;
Terms=strjoin(Terms,' + ');
independent={'r','eta'};
dependent='H';
knownCoeffs= {'C00','C10','C20','C30','C40', 'C01','C11','C21','C31','C41'};
knownVals=num2cell([ [1 , 0 , 0 , 0 , 0 ], [ 8 , -6 , 0 , 0.5 , 0 ]/eta_c ]);
allCoeffs=compose('C%u%u',I(:),J(:));
[unknownCoeffs,include]=setdiff(allCoeffs,knownCoeffs);
ft=fittype(Terms,'independent',independent, 'dependent', dependent,...
'coefficients', unknownCoeffs,'problem', knownCoeffs);
%% Fit and display
fobj = fit([r,eta],H,ft,'problem',knownVals) ;
hp=plot(fobj,[r,eta],H);
set(hp(1),'FaceAlpha',0.5);
set(hp(2),'MarkerFaceColor','r');
xlabel 'r',
ylabel '\eta'
zlabel 'H(r,\eta)'

4 Comments
Benjamin Cowen
on 22 Mar 2019
Edited: Benjamin Cowen
on 22 Mar 2019
I still cannot fully get this figured out, but I feel like I am close. During the fit, is there a way to fit the function to x and eta/eta_c. Currently it is fit to x and eta. I changed in my code a few things. For instance, the coefficients that you have divided by eta_c, should actually be multiplied. Also, you have H equal to the 5th column, but it should be the 8th. When I make all these changes, the surface fit is pretty far off, but maybe I'm doing something incorrectly. Any chance you could take a look? I edited the code to fix the coefficients and change H to the 8th column. The updated code is below. How can I make the fit be to x and eta/eta_c? Mabye the curve looked off because the eta axis needs to be changed to eta/eta_c? Lastly, it would be nice if the code could be updated so that I can see the surface as eta->0.
Edit: I can't actually test it right now since I'm not on computer. I think maybe I realized that the axes was wrong. Since I fit to eta/eta_c, then the y-axis should be eta/eta_c I would think. Can you test this? Does the curve fit well with the code below?
I changed the things I mentioned and these 2 lines (changed eta to eta/eta_c).
fobj = fit([r,eta/eta_c],H,ft,'problem',knownVals) ;
hp=plot(fobj,[r,eta/eta_c],H);
here is the whole code.
%% Load Data
num=xlsread('example.xlsx',1,'A2:H18110');
eta_c= 0.6452;
r = num(:,4);
eta = num(:,3);
H = num(:,8);
%% Set-up for fit
[I,J]=ndgrid(0:4);
Terms= compose('C%u%u*r^%u*eta^%u', I(:),J(:),I(:),J(:)) ;
Terms=strjoin(Terms,' + ');
independent={'r','eta'};
dependent='H';
knownCoeffs= {'C00','C10','C20','C30','C40', 'C01','C11','C21','C31','C41'};
knownVals=num2cell([ [1 , 0 , 0 , 0 , 0 ], [ 8 , -6 , 0 , 0.5 , 0 ]*eta_c ]);
allCoeffs=compose('C%u%u',I(:),J(:));
[unknownCoeffs,include]=setdiff(allCoeffs,knownCoeffs);
ft=fittype(Terms,'independent',independent, 'dependent', dependent,...
'coefficients', unknownCoeffs,'problem', knownCoeffs);
%% Fit and display
fobj = fit([r,eta/eta_c],H,ft,'problem',knownVals) ;
hp=plot(fobj,[r,eta/eta_c],H);
set(hp(1),'FaceAlpha',0.5);
set(hp(2),'MarkerFaceColor','r');
xlabel 'r',
ylabel '\eta'
zlabel 'H(r,\eta)'
Matt J
on 22 Mar 2019
The fit that I get looks pretty good to me...

Categories
Find more on Get Started with Curve Fitting Toolbox 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!