I am trying to implement a clamped cubic spline with a zero slope (flat extrapolation) at the boundary knots, but I am unable to obtain the desired results.
For instance setting:
x = [3 4 7 9];
y = [2 1 2 0.5];
I can use the CSAPE function to obtain the piecewise polynomial
pp = csape(x,y,'variational');
Next, evaluating the pp in the range [0-10] yields,
xx = 0:0.1:10;
yy =ppval(pp,xx);
plot(xx,yy)
However, this method do not achieve a flat extrapolation outside the [3-9] range (i.e for x<3 all values for y should be 2, and for x>9 all values for y should be 0.5)
Is there any way to achieve the desired result?
Edit: Continuity at the boundary knot should be preserved
I don't think there's any need to use csape, you can just use spline. From the documentation for spline:
If Y is a vector that contains two more values than x has entries, the
first and last value in Y are used as the endslopes for the cubic
spline.
Also, spline allows you to obtain the interpolated yy values directly, so:
x = [3 4 7 9];
y = [2 1 2 0.5];
xx = 0:0.1:10;
yy = spline(x,[0 y 0], xx);
plot(xx,yy)
This gives me the plot below.
Looking at this, the slope is zero at the boundaries (x=3 and x=9), which is what we are asking of a 'clamped' spline with zero gradient at the boundaries. If you wish to have zero gradient beyond the boundaries, I would recommend just doing the following:
yy(xx<x(1)) = y(1);
yy(xx>x(length(x))) = y(length(y));
Giving:
Edit
This gives a continuous y function at the end-knots, but y' is not smooth at the end-knots. If you would like y' to be smooth you could pad your input arrays and use this as the input to your spline function. This would give you some oscillations as shown below, though, which may or may not be what you want.
% Spline on padded input arrays
x = [0 1 2 3 4 7 9 10 11 12];
y = [2 2 2 2 1 2 0.5 0.5 0.5 0.5];
yy = spline(x,y, xx);
Related
I need to generate a curve between scatter points then identify the unit normal of the curve at each point. Here is an example of a point cloud
figure
x = [1 2 0 0 1 1 2 3 4 2];
y = [4 6 9 1 1 2 4 9 2 3];
scatter(x,y)
hold on
line(x,y)
xlim([0 4])
ylim([0 10])
NOTE: the 2 points along the y-axis are connected
Instead of a line between the points, I'd like to create a smooth curve. I'm not sure how to do this when points in x and y repeat. An attempt using spline fails. After I know the curve, I need to find the unit normals at each point. How do I go about this?
EDIT:
Basically I want to do what is show here for polyfit in the matlab docs. Assuming that x was unique in my case, this wouldn't be an issue. I could identify the polynomial and then, I believe, determine the unit normals from the polynomial function evaluated at that point. But in my case, the x and y data repeat so a straight forward application doesn't work.
One way to get a smooth path is to treat this as a parametric function and interpolate x and y separately.
x = [1 2 0 0 1 1 2 3 4 2];
y = [4 6 9 1 1 2 4 9 2 3];
t = 1:numel(x);
tq = 1:0.1:t(end);
xq = interp1(t,x,tq,'v5cubic');
yq = interp1(t,y,tq,'v5cubic');
plot(x,y,' ob',xq,yq,'-r');
To estimate the normals you can take the average normal of the two line segments around the sample points. This code is a bit ugly but it gets the job done.
n = zeros(2,numel(x));
for tidx = 1:numel(t)
tt = t(tidx);
idx1 = find(tq <= tt,1,'last');
idx0 = idx1 - 1;
idx2 = idx1 + 1;
if idx0 > 0
n1 = [yq(idx1) - yq(idx0); xq(idx0) - xq(idx1)];
n(:,tidx) = n(:,tidx) + n1/norm(n1);
end
if idx2 <= numel(tq)
n2 = [yq(idx2) - yq(idx1); xq(idx1) - xq(idx2)];
n(:,tidx) = n(:,tidx) + n2/norm(n2);
end
n(:,tidx) = n(:,tidx) / norm(n(:,tidx));
end
plot(x,y,' ob',xq,yq,'-r',[x.' x.'+n(1,:).'].', [y.' y.'+n(2,:).'].',' -k');
axis equal;
If you use pchip instead of v5cubic for the interpolation method then you get more symmetry around the sample points. However, it appears that any sharp turns (90 degrees or greater) are not smoothed.
I wanted to plot the above function on Matlab so I used the following code
ezplot('-log(x)-log(y)+x+y-2',[-10 10 -10 10]);
However I'm just getting a blank screen. But clearly there is at least the point (1,1) that satisfies the equation.
I don't think there is a problem with the plotter settings, as I'm getting graphs for functions like
ezplot('-log(y)+x+y-2',[-10 10 -10 10]);
I don't have enough rep to embed pictures :)
If we use solve on your function, we can see that there are two points where your function is equal to zero. These points are at (1, 1) and (0.3203 + 1.3354i, pi)
syms x y
result = solve(-log(x)-log(y)+x+y-2, x, y);
result.x
% -wrightOmega(log(1/pi) - 2 + pi*(1 - 1i))
% 1
result.y
% pi
% 1
If we look closely at your function, we can see that the values are actually complex
[x,y] = meshgrid(-10:0.01:10, -10:0.01:10);
values = -log(x)-log(y)+x+y-2;
whos values
% Name Size Bytes Class Attributes
% values 2001x2001 64064016 double complex
It seems as though in older versions of MATLAB, ezplot handled complex functions by only considering the real component of the data. As such, this would yield the following plot
However, newer versions consider the magnitude of the data and the zeros will only occur when both the real and imaginary components are zero. Of the two points where this is true, only one of these points is real and is able to be plotted; however, the relatively coarse sampling of ezplot isn't able to display that single point.
You could use contourc to determine the location of this point
imagesc(abs(values), 'XData', [-10 10], 'YData', [-10 10]);
axis equal
hold on
cmat = contourc(abs(values), [0 0]);
xvalues = xx(1, cmat(1,2:end));
yvalues = yy(cmat(2,2:end), 1);
plot(xvalues, yvalues, 'r*')
This is because x = y = 1 is the only solution to the given equation.
Note that the minimum value of x - log(x) is 1 and that happens when x = 1. Obviously, the same is true for y - log(y). So, -log(x)-log(y)+x+y is always greater than 2 except at x = y = 1, where it is exactly equal to 2.
As your equation has only one solution, there is no line on the plot.
To visualize this, let's plot the equation
ezplot('-log(x)-log(y)+x+y-C',[-10 10 -10 10]);
for various values of C.
% choose a set of values between 5 and 2
C = logspace(log10(5), log10(2), 20);
% plot the equation with various values of C
figure
for ic=1:length(C)
ezplot(sprintf('-log(x)-log(y)+x+y-%f', C(ic)),[0 10 0 10]);
hold on
end
title('-log(x)-log(y)+x+y-C = 0, for 5 < C < 2');
Note that the largest curve is obtained for C = 5. As the value of C is decreased, the curve also becomes smaller, until at C = 2 it completely vanishes.
I have two vectors x and y, and I fit them by a smoothing spline fit in matlab. I obtain this:
form: 'pp'
breaks: [15.5649 16.2041 17.0345 18.0489 20.1834 22.5540 24.5158 27.7881 32.5594 36.0827 40.5951]
coefs: [10x4 double]
pieces: 10
order: 4
dim: 1
I need to know the coefficients of the fit in order to reconstruct the fitted curve.
How can I get this information?
As mentioned by #A_C, you can obtain the coefficients from the coefs parameter. You should keep in mind that a spline fits a different polynomial to each region - in your case 10 regions.
As it is quite a lot of work to reconstruct the curve from the coefficients Matlab offers you the ppval function to do this:
x = [3 4 7 9];
y = [2 1 2 0.5];
xx = 0:0.1:10;
pp = spline(x,y);
yy = ppval(pp,xx);
plot(xx,yy);
Alternatively, if you only need to perform one interpolation, why not do it directly:
x = [3 4 7 9];
y = [2 1 2 0.5];
xx = 0:0.1:10;
yy = spline(x,y,xx);
plot(xx,yy);
I have my data in 2 vectors.
Then using [n,xout] = hist(x,y) returns vectors n and xout containing the frequency counts and the bin locations.
x are my real data and y the segment where I want to build my histogram.
Than I use bar(xout,n) to plot the histogram. In the end I am fitting this histogram with gaussian fit.
Now I would like to know where my real data (each point of vector x), are located in the histogram?
Can someone help me to figure out them?
[~, result] = min(abs(bsxfun(#minus, y(:), x(:).')));
This gives, for each value in x, the index of the closest element in y. So y(result) is the closest element in y for each x.
Example:
>> x = [0.4 1.6 5.3 4.2 3.1 7.8];
>> y = [0 2 4 6 8];
>> [~, result] = min(abs(bsxfun(#minus, y(:), x(:).')))
result =
1 2 4 3 3 5
>> y(result)
ans =
0 2 6 4 4 8
You want to use histc.
[binCounts, idx] = histc(x, y);
Then to find the bin in which a certain value of x is:
bin = idx(x == 0.4);
Just watch out since histc second input is not the centers like hist, but the end value of each bin. So you might need to change your y vector.
I have two arrays - X points and Y points. X array have some spaces (e. g. [0 1 2 6 7 8]), and Y array contains only values for that Xes. I've got that array as a local maxima from wavelet transform. I can plot it with plot(X,Y)
Now I want to get Y's on linspace - Y must contain values for any X from 0 to 8. I want to have the same plot plot(Y) as the previous plot(X, Y).
How can I do this?
It looks like you want to perform interpolation
xPts = [0 1 2 6 7 8];
yPts = ...
xPlot = 0:1:8;
yPlot = interp1(xPts,yPts,xPlot,'cubic')
plot(xPlot,yPlot)
Check the documentation for interp1 for the different interpolation schemes.
If there are repeated x-values, you can average the corresponding y-values
xPtsRep = [0 0 1 2 6 7 7 8]
yPtsRep = ...
[xPts,~,xIdx] = unique(xPtsRep);
yPts = accumarray(xIdx,yPtsRep,[],#mean);