How to fit an elliptic cone to a set of data? - matlab

I have a set of 3d data (300 points) that create a surface which looks like two cones or ellipsoids connected to each other. I want a way to find the equation of a best fit ellipsoid or cone to this dataset. The regression method is not important, the easier it is the better. I basically need a way, a code or a matlab function to calculate the constants of the elliptic equation for these data.

You can also try with fminsearch, but to avoid falling on local minima you will need a good starting point given the amount of coefficients (try to eliminate some of them).
Here is an example with a 2D ellipse:
% implicit equation
fxyc = #(x, y, c_) c_(1)*x.^2 + c_(2).*y.^2 + c_(3)*x.*y + c_(4)*x + c_(5).*y - 1; % free term locked to -1
% solution (ellipse)
c_ = [1, 2, 1, 0, 0]; % x^2, y^2, x*y, x, y (free term is locked to -1)
[X,Y] = meshgrid(-2:0.01:2);
figure(1);
fxy = #(x, y) fxyc(x, y, c_);
c = contour(X, Y, fxy(X, Y), [0, 0], 'b');
axis equal;
grid on;
xlabel('x');
ylabel('y');
title('solution');
% we sample the solution to have some data to fit
N = 100; % samples
sample = unique(2 + floor((length(c) - 2)*rand(1, N)));
x = c(1, sample).';
y = c(2, sample).';
x = x + 5e-2*rand(size(x)); % add some noise
y = y + 5e-2*rand(size(y));
fc = #(c_) fxyc(x, y, c_); % function in terms of the coefficients
e = #(c) fc(c).' * fc(c); % squared error function
% we start with a circle
c0 = [1, 1, 0, 0, 0];
copt = fminsearch(e, c0)
figure(2);
plot(x, y, 'rx');
hold on
fxy = #(x, y) fxyc(x, y, copt);
contour(X, Y, fxy(X, Y), [0, 0], 'b');
hold off;
axis equal;
grid on;
legend('data', 'fit');
xlabel('x'); %# Add an x label
ylabel('y');
title('fitted solution');

The matlab function fit can take arbitrary fit expressions. It takes a bit of figuring out the parameters but it can be done.
You would first create a fittype object that has a string representing your expected form. You'll need to work out the expression yourself that best fits what you're expecting, I'm going to take a cone expression from the Mathworld site for an example and rearrange it for z
ft = fittype('sqrt((x^2 + y^2)/c^2) + z_0', ...
'independent', {'x', 'y'}, 'coeff', {'c', 'z_0'});
If it's a simple form matlab can work out which are the variables and which the coefficients but with something more complex like this you'd want to give it a hand.
The 'fitoptions' object holds the configuration for the methods: depending on your dataset you might have to spend some time specifying upper and lower bounds, starting values etc.
fo = fitoptions('Upper', [one, for, each, of, your, coeffs, in, the, order, they, appear, in, the, string], ...
'Lower', [...], `StartPoint', [...]);
then get the output
[fitted, gof] = fit([xvals, yvals], zvals, ft, fo);
Caveat: I've done this plenty with 2D datasets and the docs state it works for three but I haven't done that myself so the above code might not work, check the docs to make sure you've got your syntax right.
It might be worth starting with a simple fit expression, something linear, so that you can get your code working. Then swap the expression out for the cone and play around until you get something that looks like what you're expecting.
After you've got your fit a good trick is that you can use the eval function on the string expression you used in your fit to evaluate the contents of the string as if it was a matlab expression. This means you need to have workspace variables with the same names as the variables and coefficients in your string expression.

Related

Plot a surface only for coordinates that satisfy a specific equation in MATLAB

I have two grid coordinates matrices, X and Y, created by calling [X, Y] = meshgrid(x, y), so their elements represent coordinates. How can I plot a surface on the xy-plane, using heights from matrix V, only for coordinates that satisfy a specific equation? For example, my plot extends up to radius a, but I dont want to plot any data to the set of points that satisfy the equation sqrt(x^2 + (y-c)^2) < b, where b, c (a>b) are given constants and x=X(i,j), y=Y(i,j). Is there an easy way to do this, other than creating the two grid coordinates matrices (up to radius a) and then manually removing elements from X, Y, V, using nested for loops? I have not found any way to limit the plotting area I am interested in by changing x, y.
Using Logical Indexing
Just in case you're still looking for any implementation details. Referencing the comment by #Ander Biguri. I have to add that it might be easier to use mesh parameters X and Y directly in the logical indexing. Here is a little playground script that might help future readers. Below Region_Array is a logical array that specifies where the condition in this case sqrt(X.^2 + (Y-c).^2) < b is true. When true Region_Array is indexed with the value "1" and elsewhere with "0". I've split this into two steps just in case the complementary region is quickly wanted. The images/plots below show the resulting surf() and masks/regions. MATLAB has some thorough documentation and examples overviewing logical indexing: Find Array Elements That Meet a Condition
Trivial Surface Plot:
Masks/Regions Not to be Plotted:
Playground Script:
%Random test axes%
x = linspace(0,100,50);
y = linspace(0,100,50);
[X,Y] = meshgrid(x,y);
%Trivial plot of ones%
V = ones(length(x),length(y));
%Constant parameters%
b = 20;
c = 10;
%Eliminating within the curved region%
figure(1)
Region_Array = sqrt(X.^2 + (Y-c).^2) < b;
V(Region_Array) = NaN;
subplot(1,2,1); surf(X,Y,V);
axis([0 100 0 100]);
title("Eliminating Within the Curved Region");
%Eliminating outside the curved region%
V = ones(length(x),length(y));
V(~Region_Array) = NaN;
subplot(1,2,2); surf(X,Y,V);
axis([0 100 0 100]);
title("Eliminating Outside the Curved Region");
figure(2)
subplot(1,2,1); imshow(~Region_Array,'InitialMagnification',200);
title("Region Array Mask/Map (Inside)")
subplot(1,2,2); imshow(Region_Array,'InitialMagnification',200);
title("Region Array Mask/Map (Outside)")
Ran using MATLAB R2019b

Why can't I use 'scatter3' here?

[X,Y] = meshgrid(-8:.5:8);
R = sqrt(X.^2 + Y.^2) + eps;
Z = sin(R)./R;
scatter3(X,Y,Z)
Error using scatter3 (line 64)
X, Y and Z must be vectors of the same length.
Matlab R2018b windows x64
As shown in the documentation, X, Y, Z must be vectors. (When you enter an article on mathworks from Googling, say, "matlab scatter3", you will first see the syntax for the function. Blue text means hyperlink. All the inputs are linked to the bottom of the page where their exact typing is defined.)
The reason is (probably) as follows.
As stated in the documentation, scatter3 puts circles (or other symbols of your choice if you modify the graphic object) on 3D coordinates of your choice. The coordinates are the ith element of X, Y, Z respectively. For example, the x-coordinate of the 10th point you wish to plot in 3D is X(10).
Thus it is not natural to input matrices into scatter3. If you know X(i), Y(i), Z(i) are indeed the coordinates you want to plot for all i, even though your X, Y, Z are not vectors for some reason, you need to reshape X, Y, Z.
In order to reshape, you can simply do scatter3(X(:), Y(:), Z(:)) which tells Matlab to read your arrays as a vectors. (You should look up in what order this is done. But it is in the intuitive way.) Or you can use reshape. Chances are: reshape is faster for large data set. But ofc (:) is more convenient.
The following should work:
[X,Y] = meshgrid(-8:.5:8);
R = sqrt(X.^2 + Y.^2) + eps;
Z = sin(R)./R;
X = X(:);
Y = Y(:);
Z = Z(:);
scatter3(X,Y,Z)
scatter3 needs vectors, not matrices as far as I can see here
this is my result:
If you want to use meshgrid without reshaping the matrices you have to use plot3 and the 'o' symbol. So you can get a similar result with:
plot3(X,Y,Z,'o')
EDIT:
A question that arose in association with this post was, which of the following methods is more efficient in terms of computation speed: The function reshape(X,[],1), suggested by me, or the simpler colon version X(:), suggested by #Argyll.
After timing the reshape function versus the : method, I have to admit that the latter is more efficient.
I added my results and the code I used to time both functions:
sizes = linspace(100,10000,100);
time_reshape = [];
time_col = [];
for i=1:length(sizes)
X = rand(sizes(i)); % Create random squared matrix
r = #() ResFcn(X);
c = #() ColFcn(X);
time_reshape = [time_reshape timeit(r)/1000] % Take average of 1000 measurements
time_col = [time_col timeit(c)/1000] % Take average of 1000 measurements
end
figure()
hold on
grid on
plot(sizes(2:end), time_col(2:end))
plot(sizes(2:end), time_reshape(2:end))
legend("Colon","Reshape","Location","northwest")
title("Comparison: Reshape vs. Colon Method")
xlabel("Length of squared matrix")
ylabel("Average execution time [s]")
hold off
function res = ResFcn(X)
for i = 1:1000 % Repeat 1000 times
res = reshape(X,[],1);
end
end
function res = ColFcn(X)
for i = 1:1000 % Repeat 1000 times
res = X(:);
end
end

Polynomial fit matlab with some constraints on the coefficients

I have data that I should interpolate with a function which must be of the following kind:
f(x) = ax4 + bx2 + c
with a > 0 and b ≤ 0. Unfortunately, MATLAB's polyfit does not allow any constraints on the coefficients of the polynomial. Does anybody know if there is a MATLAB function to do this? Otherwise, how can I implement it?
Thank you very much in advance,
Elisabetta
You can try using fminsearch, fminunc defining your objective function manually.
Alternatively, you can define your problem slightly different:
f(x) = a2x4 - b2x2 + c
Now, the new a and b can be optimized for without constraints, while ensuring that the final a and b you are looking for are positive (negative resp.).
Without constraints, the problem can be written and solved as a simple linear system:
% Your design matrix ([4 2 0] are the powers of the polynomial)
A = bsxfun(#power, your_X_data(:), [4 2 0]);
% Best estimate for the coefficients, [a b c], found by
% solving A*[a b c]' = y in a least-squares sense
abc = A\your_Y_data(:)
Those constraints will of course automatically be satisfied iff that constrained model indeed underlies your data. For example,
% some example factors
a = +23.9;
b = -15.75;
c = 4;
% Your model
f = #(x, F) F(1)*x.^4 + F(2)*x.^2 + F(3);
% generate some noisy XY data
x = -1:0.01:1;
y = f(x, [a b c]) + randn(size(x));
% Best unconstrained estimate a, b and c from the data
A = bsxfun(#power, x(:), [4 2 0]);
abc = A\y(:);
% Plot results
plot(x,y, 'b'), hold on
plot(x, f(x, abc), 'r')
xlabel('x (nodes)'), ylabel('y (data)')
However, if you impose constraints on data that are not accurately described by that constrained model, things might go wrong:
% Note: same data, but flipped signs
a = -23.9;
b = +15.75;
c = 4;
f = #(x, F) F(1)*x.^4 + F(2)*x.^2 + F(3);
% generate some noisy XY data
x = -1:0.01:1;
y = f(x, [a b c]) + randn(size(x));
% Estimate a, b and c from the data, Forcing a>0 and b<0
abc = fmincon(#(Y) sum((f(x,Y)-y).^2), [0 0 0], [-1 0 0; 0 +1 0; 0 0 0], zeros(3,1));
% Plot results
plot(x,y, 'b'), hold on
plot(x, f(x, abc), 'r')
xlabel('x (nodes)'), ylabel('y (data)')
(this solution has a == 0, indicative of an incorrect model choice).
If the exact equality of a == 0 is a problem: there is of course no difference if you set a == eps(0). Numerically, this will not be noticeable for real-world data, but it's nonzero nonetheless.
Anyway, I have a suspicion that your model is not well chosen and the constraints are a "fix" to get everything to work, or your data should actually be unbiased/rescaled before trying to make any fit, or that some similar preconditions apply (I've often seen people do this sort of thing, so yes, I'm a bit biased in this respect :).
So...what are the real reasons behind those constraints?
If you have the curve fitting toolbox then fit does allow for setting constraints using the 'upper' and 'lower' options. You would want something like.
M=fit(x, f, 'poly4', 'upper', [-inf, 0, -inf, 0, -inf], 'lower', [0, 0, 0, 0, -inf]);
Note use -inf to set a particular coefficient to be unconstrained.
This will give a cfit object with the relevant coefficients. You can access these using for example M.p1 for the x^4 term. Alternatively you can evaluate the function at whatever points you want using feval.
I think you can do a similar thing using lsqcurvefit in the optimization toolbox as well.

matlab determine curve

Does anyone know how to obtain a mean curve having a matrix with the correspondent x,y points from the original plot? I mean, I pretend a medium single curve.
Any code or just ideas would be very very helpful for me since I am new with matlab.
Thank you very much!
Well, one thing you can do is fit a parametric curve. Here's an example on how to do this for a figure-8 with noise on it:
function findParamFit
clc, clf, hold on
%# some sample data
noise = #(t) 0.25*rand(size(t))-0.125;
x = #(t) cos(t) + noise(t);
y = #(t) sin(2*t) + noise(t);
t = linspace(-100*rand, +100*rand, 1e4);
%# initial data
plot(x(t), y(t), 'b.')
%# find fits
options = optimset(...
'tolfun', 1e-12,...
'tolx', 1e-12);
a = lsqcurvefit(#myFun_x, [1 1], t, x(t), -10,10, options);
b = lsqcurvefit(#myFun_y, [1 2], t, y(t), -10,10, options);
%# fitted curve
xx = myFun_x(a,t);
yy = myFun_y(b,t);
plot(xx, yy, 'r.')
end
function F = myFun_x(a, tt)
F = a(1)*cos(a(2)*tt);
end
function F = myFun_y(b, tt)
F = b(1)*sin(b(2)*tt);
end
Note that this is a particularly bad way to fit parametric curves, as is apparent here by the extreme sensitivity of the solution to the quality of the initial values to lsqcurvefit. Nevertheless, fitting a parametric curve will be the way to go.
There's your google query :)

quiver not drawing arrows just lots of blue, matlab

Can somebody tell me what I am doing wrong with the quiver plotting function when I don't really get arrows, it just fills the empty space with lots of blue.Look at the image below and then look at my code.
This is just a part of my contour since this eats up proccessing power if I try to draw it larger. But my function, the contours and everything else works, it's just the quiver I'm having trouble with.
interval = -100:100;
[X Y] = meshgrid(interval, interval);
h = figure;
contour(X, Y, Z);
hold on;
[FX,FY] = gradient(-Z);
quiver(X, Y, FX, FY);
hold off;
If I make my matrix more sparse, e.g. with "interval = linspace(-800, 1600, 1200);" the result will look like this:
EDIT:
What I need are contour lines like that, but the arrows should flow with them. Right now these just look like dots, even if I zoom in further. If I zoom out the entire window will be blue.
Here is the script in its entirety if anyone wants to play with it to figure this out.
m1 = 1;
m2 = 0.4;
r1 = [1167 0 0];
r2 = [-467 0 0];
G = 9.82;
w = sqrt( G*(m1+m2) / norm(r1-r2)^3 );
interval = linspace(-800, 1600, 1200);
% Element-wise 2-norm
ewnorm = #(x,y) ( x.^2 + y.^2 ).^(1/2);
% Element-wise cross squared
ewcross2 = #(w,x,y) w^2.*( x.*x + y.*y );
[X Y] = meshgrid(interval, interval);
Z = - G*m1 ./ ewnorm( X-r1(1), Y-r1(2) ) - G*m2 ./ ewnorm( X-r2(1), Y-r2(2) ) - 1/2*ewcross2(w,X,Y);
h = figure;
contour(Z);
daspect([1 1 1]);
saveas(h, 'star1', 'eps');
hold on;
[FX,FY] = gradient(-Z);
quiver(X, Y, FX,FY);
hold off;
The problem is that the mesh is too dense. You only want to have as few elements as necessary to generate a useful mesh. As such, try reducing the density of the mesh:
interval = -100:2:100
If you're going to be changing the limits often, you probably want to avoid using the X:Y:Z formulation. Use the linspace function instead:
interval = linspace(-100,100,10);
This will ensure that no matter what your limits, your mesh will be 10x10. In the comment below, you mention that the arrows are appearing as dots when you use a very large mesh. This is to be expected. The arrows reflect "velocity" at a given point. When your plot is scaled out to a very large degree, then the velocity at any given point on the plot will be almost 0, hence the very small arrows. Check out the quiver plot documentation, as well as the quivergroup properties, to see more details.
If you absolutely must see arrows at a large scale, you can try setting the AutoScale property to off, or increasing the AutoScaleFactor:
quiver(X, Y, FX, FY, 'AutoScale', 'off');
quiver(X, Y, FX, FY, 'AutoScaleFactor', 10);
You may also want to play with the MarkerSize and MaxHeadSize properties. I really just suggest looking at all the QuiverGroup properties and trying things out.
You could use a threshold
interval = -100:100;
[X Y] = meshgrid(interval, interval);
h = figure;
contour(X, Y, Z);
hold on;
[FX,FY] = gradient(-Z);
GM = sqrt(FX.^2 + FY.^2);
threshold = 0.1;
mask = GM > threshold;
quiver(X(mask), Y(mask), FX(mask), FY(mask));
hold off;
This will show only vectors with a magnitude > 0.1;