R: Simulating line segment conditional - r

For a perceptual task, I wish to simulate multiple items, each consisting of a plotted single line with two 'breaking points' where the line abruptly changes direction. So in essence the line consists of three connected line segments (AB, BC, and CD), connecting four coordinates (Axy, Bxy, Cxy, Dyx), each with a different slope.
The line must agree with the following three conditions:
1) The total length of the line (L), which is the sum of the length of the three line segments (AB, BC, and CD) should vary between items, but always fall within the range of l1 and l2.
2) The line should fit within and take up an X*Y sized rectangle. That is, at least one x-coordinate (Ax, Bx, Cx, or Dx) should equal 0, at least one x-coordinate (Ax, Bx, Cx, or Dx) should equal X, at least one y-coordinate (Ay, By, Cy, or Dy) should be 0, at least one y-coordinate (Ay, By, Cy, or Dy) should equal Y; none of the x-coordinates should be lower than 0 or higher than X, none of the y-coordinates should be lower than 0 or higher than Y.
3) The line segments may not cross. That is, line segment AB and CD may not cross (as line BC is connected at one end to both other line segments, it cannot cross them).
I wish to do this in R. So far I've only managed a code wherein a random line is created and the code then checks if it meets all three conditions. If not, it starts anew. This method takes way too long!
Does anyone have an idea how I could make this code more efficient? Current R-code provided below.
#START WHILE LOOP
STOP = FALSE
CONDITION_COUNTER <- c(0,0,0)
while(STOP==FALSE){ #start condition checking loop
#SETTINGS:
l1 = 8 #minimum length L
l2 = 12 #maximum length L
L = runif(1,l1,l2) #length L
X = 5 #width square for length L
Y = 7 #heigth square for length L
#CREATE LINE SEGMENT:
Ax <- runif(1,0,X) #x-coordinate point A
Ay <- runif(1,0,Y) #y-coordinate point A
Bx <- runif(1,0,X) #x-coordinate point B
By <- runif(1,0,Y) #y-coordinate point B
Cx <- runif(1,0,X) #x-coordinate point C
Cy <- runif(1,0,Y) #y-coordinate point C
Dx <- runif(1,0,X) #x-coordinate point D
Dy <- runif(1,0,Y) #y-coordinate point D
#CHECK CONDITION 01 (line has to equal length L)
AB = sqrt((Ax-Bx)^2 + (Ay-By)^2) #length line segment AB
BC = sqrt((Bx-Cx)^2 + (By-Cy)^2) #length line segment BC
CD = sqrt((Cx-Dx)^2 + (Cy-Dy)^2) #length line segment CD
CONDITION_COUNTER[1] <- L == AB + BC + CD #Condition 1 satisfied (1) or not (0)?
#CHECK CONDITION 02 (line has to fill the square)
c1 = sum(c(Ax, Bx, Cx, Dx) == 0) > 0 #does one point have x-coordinate 0?
c2 = sum(c(Ax, Bx, Cx, Dx) == X) > 0 #does one point have x-coordinate X?
c3 = sum(c(Ay, By, Cy, Dy) == 0) > 0 #does one point have y-coordinate 0?
c4 = sum(c(Ay, By, Cy, Dy) == Y) > 0 #does one point have y-coordinate Y?
CONDITION_COUNTER[2] <- sum(c(c1,c2,c3,c4)) == 4 #Condition 2 satisfied (1) or not (0)?
#CHECK CONDITION 03 (line segments may not cross)
a <- max(c(Ax,Bx)); b <- min(c(Ax,Bx)); x <- a-b; x
a <- c(Ay,By)[which.max(c(Ax,Bx))]; b <- c(Ay,By)[which.min(c(Ax,Bx))]; y <- a-b; y
slopeAB <- y/x
InterceptAB <- Ay - slopeAB * Ax
c <- max(c(Cx,Dx)); d <- min(c(Cx,Dx)); x <- c-d; x
c <- c(Cy,Dy)[which.max(c(Cx,Dx))]; d <- c(Cy,Dy)[which.min(c(Cx,Dx))]; y <- c-d; y
slopeCD <- y/x
InterceptCD <- Cy - slopeCD * Cx
intersection <- (InterceptAB - InterceptCD)/(slopeCD - slopeAB) #what is the hypothetical x-coordinate of intersection?
c1 <- min(c(Ax,Bx)) <= intersection & intersection <= max(c(Ax,Bx)) #does AB contain that x-coordinate? (TRUE=yes, FALSE=no)
c1 <- (c1 -1)*-1
CONDITION_COUNTER[3] <- c1
CHECK <- (sum(CONDITION_COUNTER) == 3) #check if all conditions are met
if(CHECK == TRUE){STOP <- TRUE} #if all conditions are met, stop loop
} #END WHILE LOOP
#Plot:
plot(-1:10, -1:10, xaxt='n',yaxt='n',bty='n',pch='',ylab='',xlab='', col="white")
segments(Ax,Ay,Bx,By, lwd=2) #segment AB
segments(Bx,By,Cx,Cy, lwd=2) #segment BC
segments(Cx,Cy,Dx,Dy, lwd=2) #segment CD
#Add square that it has to fill
segments(0,0,X,0, col="red")
segments(0,0,0,Y, col="red")
segments(X,0,X,Y, col="red")
segments(0,Y,X,Y, col="red")

Since your constraints force the picture to look like your image (or perhaps a rotated copy) you can think of the problem as one of picking 4 numbers (a location on each edge) rather than 8. Intersections would be impossible, so no need to check. Pick the first three points, and then pause to check if it is
possible to extend it to the fourth (given the length constraints). As a safety valve, put a bound on the number of attempts to find a feasible solution:
dis <- function(x0,y0,x1,y1){
sqrt(sum((c(x1,y1)-c(x0,y0))^2))
}
broken.line <- function(X,Y,l1,l2,attempts = 1000){
Ax <- 0
By <- 0
Cx <- X
Dy <- Y
for(i in 1:attempts){
Ay <- runif(1,0,Y)
Bx <- runif(1,0,X)
Cy <- runif(1,0,Y)
L <- dis(Ax,Ay,Bx,By) + dis(Bx,By,Cx,Cy)
d.min <- Y - Cy #min dist to top edge
if(l1 < L + d.min && L + d.min < l2){
#it is feasible to complete this
#configuration -- calulate how much
#of top edge is a valid choice
#d.max is farthest that last point
#can be from the upper right corner:
d.max <- sqrt((l2 - L)^2 - d.min^2)
Dx <- runif(1,max(0,X-d.max),X)
points <- c(Ax,Bx,Cx,Dx,Ay,By,Cy,Dy)
return(matrix(points,ncol = 2))
}
}
NULL #can't find a feasible solution
}
It is fairly quick. With your parameters it can generate tens of thousands of solutions per second. For a quick test:
> m <- broken.line(5,7,8,12)
> m
[,1] [,2]
[1,] 0.000000 1.613904
[2,] 1.008444 0.000000
[3,] 5.000000 3.627471
[4,] 3.145380 7.000000
> plot(m,type = 'l')
Graph:

Related

How do I partition a circle into equal MULTIPOLYGON "slices" with sf and R?

I have this circle:
library(sf)
p <- st_sfc(st_point(c(0, 1)))
circle <- st_buffer(p, dist = 1)
plot(circle)
How do I partition this circle into 4 equal "slices"? 6 equal slices? 8 equal slices? Etc. The object I need returned would be a MULTIPOLYGON.
Take these two functions, one to create a single wedge given a centre, radius, start angle, width, and number of sections in the arc part, and another to create a number of those with different start angles:
st_wedge <- function(x,y,r,start,width,n=20){
theta = seq(start, start+width, length=n)
xarc = x + r*sin(theta)
yarc = y + r*cos(theta)
xc = c(x, xarc, x)
yc = c(y, yarc, y)
st_polygon(list(cbind(xc,yc)))
}
st_wedges <- function(x, y, r, nsegs){
width = (2*pi)/nsegs
starts = (1:nsegs)*width
polys = lapply(starts, function(s){st_wedge(x,y,r,s,width)})
mpoly = st_cast(do.call(st_sfc, polys), "MULTIPOLYGON")
mpoly
}
Then do something like this to get five wedges centred at 5,1 of radius 10:
> w5 = st_wedges(5,1,10,5)
> plot(w5)
> class(w5)
[1] "sfc_MULTIPOLYGON" "sfc"
> axis(1)
> axis(2)

Perpendicular Lines at Regular Intervals along Lines with Multiple Nodes

I am attempting to sample along multiple lines (roads) at regular intervals and am struggling to obtain exact perpendicular angles for each road segment. I have split each road into points giving the node at which each line changes orientation and what I have so far creates a point within a straight segment of each road and appears to be working fine.
This is the code I am using to produce perpendicular angles for each node segment.
# X and Y for 3 points along a line
road_node <- matrix(
c(
381103, 381112, 381117,
370373, 370301, 370290
),
ncol = 2,
)
road_node <- as.data.frame(road_node)
angle_inv <- c()
for (i in 2:nrow(road_node) - 1) {
n1 <- road_node[i, ]
n2 <- road_node[i + 1, ]
x <- as.numeric(n1[1] - n2[1])
y <- as.numeric(n1[2] - n2[2])
ang <- atan2(y, x) + 1 / 2 * pi
if (!is.na(ang) && ang < 0) {
ang <- 2 + ang
}
angle_inv <- rbind(angle_inv, ang)
}
Where road_node gives the coordinates of each node.
From this I take the mid points and the inverse angles to create two points either side of the mid points, to produce a line segment.
# X Y and Angles (angles for one segment are the same
mids <- matrix(
c(
381374.5, 381351.0, 381320.5,
371590.5,371560.0, 371533.590,
2.3, 2.3, 2.3
),
nrow = 3,
)
mids <- as.data.frame(mids)
pts <- c()
for (i in 1:nrow(mids)) {
x1 <- mids[i, 1] + 10 * cos(mids[i, 3])
y1 <- mids[i, 2] + 10 * sin(mids[i, 3])
x2 <- mids[i, 1] - 10 * cos(mids[i, 3])
y2 <- mids[i, 2] - 10 * sin(mids[i, 3])
p1 <- cbind(x1, y1)
p2 <- cbind(x2, y2)
pair <- rbind(p1, p2)
pts <- rbind(pts, pair)
}
Some line segments appear to be correctly perpendicular to the node they are associate with, however some are not. Each appear to correctly share the same length.
I believe the problem lies with either how I am selecting my angles using atan2, or with how I am selecting my points either side of the node segment.
Firstly, there's no need to use trigonometry to solve this. Instead you can use the inverse reciprocal of the slope intercept form of the line segment equation, then calculate points on a perpendicular line passing through a give point.
See Equation from 2 points using Slope Intercept Form
Also your mid points appear incorrect and there are only 2 mid points as 3 points = 2 line segments.
This code appears to work fine
# Function to calculate mid points
mid_point <- function(p1,p2) {
return(c(p1[1] + (p2[1] - p1[1]) / 2,p1[2] + (p2[2] - p1[2]) / 2))
}
# Function to calculate slope of line between 2 points
slope <- function(p1,p2) {
return((p2[2] - p1[2]) / (p2[1] - p1[1]))
}
# Function to calculate intercept of line passing through given point wiht slope m
calc_intercept <- function(p,m) {
return(p[2] - m * p[1])
}
# Function to calculate y for a given x, slope m and intercept b
calc_y <- function(x,m,b) {
return(c(x, m * x + b))
}
# X and Y for 3 points along a line
road_node <- matrix(
c(
381103, 381112, 381117,
370373, 370301, 370290
),
ncol = 2,
)
road_node <- as.data.frame(road_node)
perp_segments <- c()
for (i in 2:nrow(road_node) - 1) {
n1 <- road_node[i, ]
n2 <- road_node[i + 1, ]
# Calculate mid point
mp <- mid_point(n1,n2)
# Calculate slope
m <- slope(n1,n2)
# Calculate intercept subsituting n1
b <- calc_intercept(n1,m)
# Calculate inverse reciprocal of slope
new_m <- -1.0 / m
# Calculate intercept of perpendicular line through mid point
new_b <- calc_intercept(mp,new_m)
# Calculate points 10 units away in x direction at mid_point
p1 <- rbind(calc_y(as.numeric(mp[1])-10,new_m,new_b))
p2 <- rbind(calc_y(as.numeric(mp[1])+10,new_m,new_b))
# Add point pair to output vector
pair <- rbind(p1,p2)
perp_segments <- rbind(perp_segments,pair)
}
This is how it looks geometrically (image)
I hope this helps.
Edit 1:
I thought about this more and came up with this simplified function. If you tink of the problem as a right isosceles triangle (45,45,90), then all you need to do is find the point which is the required distance from the reference point interpolated along the line segment, then invert its x and y distances from the reference points, then add and subtract these from the reference point.
Function calc_perp
Arguments:
p1, p2 - two point vectors defining the end points of the line segment
n - the distance from the line segment
interval - the interval along the line segment of the reference point from the start (default 0.5)
proportion - Boolean defining whether the interval is a proportion of the length or a constant (default TRUE)
# Function to calculate Euclidean distance between 2 points
euclidean_distance <-function(p1,p2) {
return(sqrt((p2[1] - p1[1])**2 + (p2[2] - p1[2])**2))
}
# Function to calculate 2 points on a line perpendicular to another defined by 2 points p,p2
# For point at interval, which can be a proportion of the segment length, or a constant
# At distance n from the source line
calc_perp <-function(p1,p2,n,interval=0.5,proportion=TRUE) {
# Calculate x and y distances
x_len <- p2[1] - p1[1]
y_len <- p2[2] - p1[2]
# If proportion calculate reference point from tot_length
if (proportion) {
point <- c(p1[1]+x_len*interval,p1[2]+y_len*interval)
}
# Else use the constant value
else {
tot_len <- euclidean_distance(p1,p2)
point <- c(p1[1]+x_len/tot_len*interval,p1[2]+y_len/tot_len*interval)
}
# Calculate the x and y distances from reference point to point on line n distance away
ref_len <- euclidean_distance(point,p2)
xn_len <- (n / ref_len) * (p2[1] - point[1])
yn_len <- (n / ref_len) * (p2[2] - point[2])
# Invert the x and y lengths and add/subtract from the refrence point
ref_points <- rbind(point,c(point[1] + yn_len,point[2] - xn_len),c(point[1] - yn_len,point[2] + xn_len))
# Return the reference points
return(ref_points)
}
Examples
> calc_perp(c(0,0),c(1,1),1)
[,1] [,2]
point 0.5000000 0.5000000
1.2071068 -0.2071068
-0.2071068 1.2071068
> calc_perp(c(0,0),c(1,1),sqrt(2)/2,0,proportion=FALSE)
[,1] [,2]
point 0.0 0.0
0.5 -0.5
-0.5 0.5
This is how the revised function looks geometrically with your example and n = 10 for distance from line:

How to clip an isosurface to a ball?

Consider the Togliatti implicit surface. I want to clip it to the ball centered at the origin with radius 4.8. A solution, with the misc3d package, consists in using the mask argument of the computeContour3d function, which allows to use only the points satisfying x^2+y^2+z^2 < 4.8^2:
library(misc3d)
# Togliatti surface equation: f(x,y,z) = 0
f <- function(x,y,z){
w <- 1
64*(x-w)*
(x^4-4*x^3*w-10*x^2*y^2-4*x^2*w^2+16*x*w^3-20*x*y^2*w+5*y^4+16*w^4-20*y^2*w^2) -
5*sqrt(5-sqrt(5))*(2*z-sqrt(5-sqrt(5))*w)*(4*(x^2+y^2-z^2)+(1+3*sqrt(5))*w^2)^2
}
# make grid
nx <- 220; ny <- 220; nz <- 220
x <- seq(-5, 5, length=nx)
y <- seq(-5, 5, length=ny)
z <- seq(-4, 4, length=nz)
g <- expand.grid(x=x, y=y, z=z)
# calculate voxel
voxel <- array(with(g, f(x,y,z)), dim = c(nx,ny,nz))
# mask: keep points satisfying x^2+y^2+z^2 < 4.8^2, in order to
# clip the surface to the ball of radius 4.8
mask <- array(with(g, x^2+y^2+z^2 < 4.8^2), dim = c(nx,ny,nz))
# compute isosurface
surf <- computeContour3d(voxel, maxvol=max(voxel), level=0, mask=mask, x=x, y=y, z=z)
# draw isosurface
drawScene.rgl(makeTriangles(surf, smooth=TRUE))
But the borders of the resulting surface are irregular:
How to get regular, smooth borders?
The solution I found resorts to spherical coordinates. It consists in defining the function f in terms of spherical coordinates (ρ, θ, ϕ), then to compute the isosurface with ρ running from 0 to the desired radius, and then to transform the result to Cartesian coordinates:
# Togliatti surface equation with spherical coordinates
f <- function(ρ, θ, ϕ){
w <- 1
x <- ρ*cos(θ)*sin(ϕ)
y <- ρ*sin(θ)*sin(ϕ)
z <- ρ*cos(ϕ)
64*(x-w)*
(x^4-4*x^3*w-10*x^2*y^2-4*x^2*w^2+16*x*w^3-20*x*y^2*w+5*y^4+16*w^4-20*y^2*w^2) -
5*sqrt(5-sqrt(5))*(2*z-sqrt(5-sqrt(5))*w)*(4*(x^2+y^2-z^2)+(1+3*sqrt(5))*w^2)^2
}
# make grid
nρ <- 300; nθ <- 400; nϕ <- 300
ρ <- seq(0, 4.8, length = nρ) # ρ runs from 0 to the desired radius
θ <- seq(0, 2*pi, length = nθ)
ϕ <- seq(0, pi, length = nϕ)
g <- expand.grid(ρ=ρ, θ=θ, ϕ=ϕ)
# calculate voxel
voxel <- array(with(g, f(ρ,θ,ϕ)), dim = c(nρ,nθ,nϕ))
# calculate isosurface
surf <- computeContour3d(voxel, maxvol=max(voxel), level=0, x=ρ, y=θ, z=ϕ)
# transform to Cartesian coordinates
surf <- t(apply(surf, 1, function(rtp){
ρ <- rtp[1]; θ <- rtp[2]; ϕ <- rtp[3]
c(
ρ*cos(θ)*sin(ϕ),
ρ*sin(θ)*sin(ϕ),
ρ*cos(ϕ)
)
}))
# draw isosurface
drawScene.rgl(makeTriangles(surf, smooth=TRUE, color = "violetred"))
Now the resulting surface has regular, smooth borders:
Your solution is excellent for the problem you stated, because spherical coordinates are so natural for that boundary. However, here is a more general solution that would work for other smooth boundaries.
The idea is to allow input of a boundary function, and cull points when they are too large or too small. In your case it would be the squared distance from the origin, and you would want to cull points where the value is bigger than 4.8^2. But sometimes the triangles being drawn to make the smooth surface should only be partially culled: one point would be kept and two deleted, or two kept and one deleted. If you cull the whole triangle that leads to the jagged edges in your original plot.
To fix this, the points can be modified. If only one is supposed to be kept, then the other two points can be shrunk towards it until they lie on an approximation to the boundary. If two are supposed to be kept you want the shape to be a quadrilateral, so you would build that out of two triangles.
This function does that, assuming the input surf is the output of computeContour3d:
boundSurface <- function(surf, boundFn, bound = 0, greater = TRUE) {
# Surf is n x 3: each row is a point, triplets are triangles
values <- matrix(boundFn(surf) - bound, 3)
# values is (m = n/3) x 3: each row is the boundFn value at one point
# of a triangle
if (!greater)
values <- -values
keep <- values >= 0
# counts is m vector counting number of points to keep in each triangle
counts <- apply(keep, 2, sum)
# result is initialized to an empty array
result <- matrix(nrow = 0, ncol = 3)
# singles is set to all the rows of surf where exactly one
# point in the triangle is kept, say s x 3
singles <- surf[rep(counts == 1, each = 3),]
if (length(singles)) {
# singleValues is a subset of values where only one vertex is kept
singleValues <- values[, counts == 1]
singleIndex <- 3*col(singleValues) + 1:3 - 3
# good is the index of the vertex to keep, bad are those to fix
good <- apply(singleValues, 2, function(col) which(col >= 0))
bad <- apply(singleValues, 2, function(col) which(col < 0))
for (j in 1:ncol(singleValues)) {
goodval <- singleValues[good[j], j]
for (i in 1:2) {
badval <- singleValues[bad[i,j], j]
alpha <- goodval/(goodval - badval)
singles[singleIndex[bad[i,j], j], ] <-
(1-alpha)*singles[singleIndex[good[j], j],] +
alpha *singles[singleIndex[bad[i,j], j],]
}
}
result <- rbind(result, singles)
}
doubles <- surf[rep(counts == 2, each = 3),]
if (length(doubles)) {
# doubleValues is a subset of values where two vertices are kept
doubleValues <- values[, counts == 2]
doubleIndex <- 3*col(doubleValues) + 1:3 - 3
doubles2 <- doubles
# good is the index of the vertex to keep, bad are those to fix
good <- apply(doubleValues, 2, function(col) which(col >= 0))
bad <- apply(doubleValues, 2, function(col) which(col < 0))
newvert <- matrix(NA, 2, 3)
for (j in 1:ncol(doubleValues)) {
badval <- doubleValues[bad[j], j]
for (i in 1:2) {
goodval <- doubleValues[good[i,j], j]
alpha <- goodval/(goodval - badval)
newvert[i,] <-
(1-alpha)*doubles[doubleIndex[good[i,j], j],] +
alpha *doubles[doubleIndex[bad[j], j],]
}
doubles[doubleIndex[bad[j], j],] <- newvert[1,]
doubles2[doubleIndex[good[1,j], j],] <- newvert[1,]
doubles2[doubleIndex[bad[j], j],] <- newvert[2,]
}
result <- rbind(result, doubles, doubles2)
}
# Finally add all the rows of surf where the whole
# triangle is kept
rbind(result, surf[rep(counts == 3, each = 3),])
}
You would use it after computeContour3d and before makeTriangles, e.g.
fn <- function(x) {
apply(x^2, 1, sum)
}
drawScene.rgl(makeTriangles(boundSurface(surf, fn, bound = 4.8^2,
greater = FALSE),
smooth = TRUE))
Here's the output I see:
It's not quite as good as yours, but it would work for many different boundary functions.
Edited to add: Version 0.100.26 of rgl now has a function clipMesh3d which incorporates these ideas.

Constructing Brownian bridge from 0 to T

I'm trying to simulate a Brownian bridge from Wiener process, but struggling with code.
Here is what i'm trying to do in math form:
B(t) = W (t) − tW (1)
It is important, that W(T) = 0, so that the process is pinned at the origin at both t=0 and t=T (should start and end with B(t)=B(T)= 0
Here I'm defining Wiener process:
n <- 1000
T <- 1
delta <- T/n
t <- seq(0,T, delta)
set.seed(17)
W1 <- cumsum(c(0,rnorm(n, 0, 1) * sqrt(delta)))
plot(t, W1 ,type = 'l',
xlab = 't', ylab = 'W(t)', xlim = c(0, T + delta))
Here I'm trying to build the Brownian bridge, but getting wrong result:
B <- W1 - t * W1[T] # The Brownian bridge from (0,0) to (1,target)
plot(times, B, type="l")
I expect B[1] == B[1001] to be [TRUE] and equal to 0, but in my case B[1001] is not equal to 0.
Any suggestions?
There's just one issue - in the definition of B. Instead you want
B <- W1 - t * W1[n + 1]
since the indexing n + 1 (i.e., choosing the last element corresponding to t = 1) refers to W1 as a vector rather than a process. Then indeed
B[1]
# [1] 0
B[1001]
# [1] 0
as expected. Note also though that using t and T as variable names is not advisable as both of them already have important roles; see ?t and ?T.

fitting a 5th order Bézier Curve to a data set

This is a slightly specific problem, so a bit of knowledge of R and of Bézier curves is required to be of help... (thanks if you do!!)
So I need some help with my R code: I have a series of discretely sampled observations and I am trying to fit a Bézier Curve of the 5th order through these points with simple LSS regression. I have some limitations on the position of the 6 control points:
A & B have the same Y-axis coordinate
B & C have the same X-axis coordinate
C & D have the same Y-axis coordinate
D & E have the same X-axis coordinate
E & F have the same Y-axis coordinate
A is located on the observation 2 turning points ago from the last
observation
The X-axis coordinate of the last observation is
somewhere between the X-axis coordinates of E and F
Like this image:
Say I have these data:
-0.01105
-0.01118
-0.01271
-0.01479
-0.01729
-0.01996
-0.02250
-0.02473
-0.02554
-0.02478
-0.02207
-0.01788
-0.01319
-0.00956
They have a "curvy" shape so a Bézier curve would fit: the result of my code is this image: the data are in red, the 5th order Bézier and its control points with their restrictions in blue:
Like this image:
So you see that I have some kind of solution, but this is the problem:
The X-axis location of right-most control point is always to the right of the last input data point, and to get an appropriate fit, I had to require a value of t (t goes from 0 to 1 in a Bézier) where t is at if the input data end (the "limit" variable in my code). How do I rewrite it so I don't have to do that anymore, and the horizontal spread of the t-values remains constant, also outside of the input data?
(given the restrictions on the control points, and maximizing the fit of the part of the curve that overlaps with the input data)
If you can help, please take a look at this R code, any help is .. much much appreciated and happy holidays!!
ps: what I call exampledata.csv in my code is just the data above.
getT <- function(x){
# Calculates length from origin of each point in the path.
# args:
# x : a one dimensional vector
# Returns:
# out : a vector of distances from the origin, as a percent of end point - start point distance
out <- cumsum(abs(diff(x)))
out <- c(0, out/ out[length(out)])
return(out)
}
cost_f <- function(X,Y,K){
pred <-K%*%X
c <- Y- pred
out <- list(loss= as.vector(t(c)%*%c), pred = pred)
return(out)
}
df <- read.csv('exampledata.csv')
T <- nrow(df)
df['d'] = 1:T
# # identify all turning points:
# turn_point <- c(1)
# for(i in 2:(T-1)){
# if( ( (df[i,'x'] < df[i-1,'x']) & (df[i,'x'] < df[i+1,'x'])) | ( (df[i,'x'] > df[i-1,'x']) & (df[i,'x'] > df[i+1,'x'])) ){
# turn_point <- c(turn_point, i)
# }
# }
fit_last_piece <- function(df){
limit <- .79
turn_point <- c(1)
for(i in 2:(T-1)){
if( ( (df[i,'x'] < df[i-1,'x']) & (df[i,'x'] < df[i+1,'x'])) | ( (df[i,'x'] > df[i-1,'x']) & (df[i,'x'] > df[i+1,'x'])) ){
turn_point <- c(turn_point, i)
}
}
nk <- length(turn_point) # number of turning points
data <- df[turn_point[nk-1]:nrow(df),]
end_x <- data$d[1]
end_y <- data$x[1]
constr_x <- matrix(c(1,0,0,0,0,0, # remember data is input column to column
0,1,1,0,0,0,
0,0,0,1,1,0,
0,0,0,0,0,1),nrow = 6, ncol = 4)
constr_y <- matrix(c(1,1,0,0,0,0,
0,0,1,1,0,0,
0,0,0,0,1,1),nrow = 6, ncol = 3)
M = matrix(c(-1,5,-10,10,-5,1,
5,-20,30,-20,5,0,
-10,30,-30,10,0,0,
10,-20,10,0,0,0,
-5,5,0,0,0,0,
1,0,0,0,0,0),nrow = 6, ncol = 6)
t_x = getT(data$d)*limit
T_x = cbind(t_x^5, t_x^4 ,t_x^3, t_x^2, t_x,rep(1,length(t_x)))
in_par <- ( tail(data$d,1)-data$d[1])*c(2/5,4/5,6/5) + data$d[1] # initial values of the intermediate x levels are at 1/3 and 2/3 midpoints
res_x <- optim(par = in_par, fn = function(par){cost_f(c(data$d[1], par[1],par[2], par[3]), data$d, T_x%*%M%*%constr_x)$loss})
#res_x <- optimize(f = function(par){cost_f(c(df$d[1],par,df$d[nrow(df)]), df$d, T_x%*%M%*%constr_x)$loss}, interval = c(df$d[1],df$d[nrow(df)]),tol = .Machine$double.eps^0.25)
optim_x <- c(data$d[1],res_x$par)
pred_x <- cost_f(optim_x, data$d, T_x%*%M%*%constr_x)$pred
t_y = getT(data$x)*limit
T_y = cbind(t_y^5, t_y^4,t_y^3, t_y^2, t_y,rep(1,length(t_y)))
in_par <- c()
res_y <- optim(par = c(data$x[floor(nrow(data)/2)],tail(data$x,1)), fn = function(par){cost_f(c(data$x[1],par[1],par[2]), data$x, T_y%*%M%*%constr_y)$loss})
optim_y <- c(data$x[1],res_y$par[1],res_y$par[2])
#pred_y <- cost_f(res_y$par, df$x, T_y%*%M%*%constr_y)$pred
pred_y <- cost_f(optim_y, data$x, T_y%*%M%*%constr_y)$pred
t_x_p <- c(t_x,seq(tail(t_x,1),1,length.out = 10))
T_x_p <- cbind(t_x_p^5, t_x_p^4 ,t_x_p^3, t_x_p^2, t_x_p,rep(1,length(t_x_p)))
t_y_p <- c(t_y,seq(tail(t_y,1),1,length.out = 10))
T_y_p <- cbind(t_y_p^5, t_y_p^4 ,t_y_p^3, t_y_p^2, t_y_p,rep(1,length(t_y_p)))
pred_x <- T_x_p%*%M%*%constr_x%*%optim_x
pred_y <- T_y_p%*%M%*%constr_y%*%optim_y
# this part is new:
plot(pred_x,pred_y, ylim = c(min(c(data$x, pred_y,res_y$par)), max(c(data$x, pred_y,res_y$par))),col="blue",type="b")
points(data$d,data$x,col = 'red',type="b")
points(pred_x[1],pred_y[1],pch=20,col='blue')
points(res_x$par[1],pred_y[1],pch=20,col='blue')
points(res_x$par[1],res_y$par[1],pch=20,col='blue')
points(res_x$par[2],res_y$par[1],pch=20,col='blue')
points(res_x$par[2],res_y$par[2],pch=20,col='blue')
points(res_x$par[3],res_y$par[2],pch=20,col='blue')
segments(pred_x[1],pred_y[1],res_x$par[1],pred_y[1],lty=3,col='blue')
segments(res_x$par[1],pred_y[1],res_x$par[1],res_y$par[1],lty=3,col='blue')
segments(res_x$par[1],res_y$par[1],res_x$par[2],res_y$par[1],lty=3,col='blue')
segments(res_x$par[2],res_y$par[1],res_x$par[2],res_y$par[2],lty=3,col='blue')
segments(res_x$par[2],res_y$par[2],res_x$par[3],res_y$par[2],lty=3,col='blue')
}
fit_last_piece(df)

Resources