Najpierw zróbmy analizę.
Załóżmy, że w obrębie wielokąta jego gęstość prawdopodobieństwa jest funkcją proporcjonalną Zatem stała proporcjonalności jest odwrotnością całki nad wielokątem,Pp(x,y).p
μ0,0(P)=∬Pp(x,y)dxdy.
Środka ciężkości wielokąta jest punktem średnich współrzędnych, obliczony jako pierwszych chwil. Pierwszy to
μ1,0(P)=1μ0,0(P)∬Pxp(x,y)dxdy.
Bezwładnościowy napinacz może być przedstawiony jako symetryczny tablicy drugich momentów obliczane po przeliczeniu wielokąta umieścić jej środka ciężkości w punkcie początkowym, to znaczy w matrycy centralnych drugich momentów
μ′k,l(P)=1μ0,0(P)∬P(x−μ1,0(P))k(y−μ0,1(P))lp(x,y)dxdy
gdzie wynoszą od do do Sam tensor - inaczej macierz kowariancji - jest(k,l)(2,0)(1,1)(0,2).
I(P)=(μ′2,0(P)μ′1,1(P)μ′1,1(P)μ′0,2(P)).
PCA od otrzymuje się główne osie z są to wektory jednostkowe skalowane przez ich wartości własnych.I(P)P:
Następnie sprawdźmy, jak wykonać obliczenia. Ponieważ wielokąt jest przedstawiany jako sekwencja wierzchołków opisujących jego zorientowaną granicę naturalne jest wywoływanie∂P,
Twierdzenie Greena: gdzie to jedna forma zdefiniowana w sąsiedztwie i∬Pdω=∮∂Pω
ω=M(x,y)dx+N(x,y)dyPdω=(∂∂xN(x,y)−∂∂yM(x,y))dxdy.
Na przykład, z i stałą ( tj. Jednolitą) gęstością możemy (przez inspekcję) wybrać jedną z wielu rozwiązania, takie jakdω=xkyldxdyp,ω(x,y)=−1l+1xkyl+1dx.
Chodzi o to, że całka konturu podąża za segmentami linii wyznaczonymi przez sekwencję wierzchołków. Każdy segment linii od wierzchołka do wierzchołka można sparametryzować za pomocą zmiennej rzeczywistej w postaciuvt
t→u+tw
gdzie to normalny kierunek jednostki od doWartości wahają się zatem od do Pod tą parametryzacją i są liniowymi funkcjami i a są liniowymi funkcjami Tak więc podcałkową całki konturu na każdej krawędzi zostaje funkcja wielomianowa od , która jest łatwo ocenione dla małych iw∝v−uuv.t0|v−u|.xytdxdydt.t,kl.
Wdrożenie tej analizy jest tak proste, jak kodowanie jej komponentów. Na najniższym poziomie potrzebujemy funkcji do zintegrowania wielomianowej formy jednoczęściowej na segmencie linii. Funkcje wyższego poziomu agregują je, aby obliczyć momenty surowe i centralne w celu uzyskania barycentrum i tensora bezwładnościowego, a na koniec możemy działać na tym tensorze, aby znaleźć główne osie (które są jego skalowanymi wektorami własnymi). Poniższy R
kod wykonuje tę pracę. Nie ma żadnych pretensji do wydajności: ma on jedynie zilustrować praktyczne zastosowanie powyższej analizy. Każda funkcja jest prosta, a konwencje nazewnictwa są zbieżne z konwencjami analizy.
Kod zawiera procedurę generowania prawidłowych zamkniętych, po prostu połączonych, nie przecinających się wielokątów (przez losowe deformowanie punktów wzdłuż koła i dołączenie początkowego wierzchołka jako jego ostatniego punktu w celu utworzenia zamkniętej pętli). Poniżej znajduje się kilka instrukcji do wykreślenia wielokąta, wyświetlenia jego wierzchołków, przylegania do centrum środka i wykreślenia głównych osi w kolorze czerwonym (największym) i niebieskim (najmniejszym), tworząc układ współrzędnych zorientowany dodatnio na wielokąt.
#
# Integrate a monomial one-form x^k*y^l*dx along the line segment given as an
# origin, unit direction vector, and distance.
#
lintegrate <- function(k, l, origin, normal, distance) {
# Binomial theorem expansion of (u + tw)^k
expand <- function(k, u, w) {
i <- seq_len(k+1)-1
u^i * w^rev(i) * choose(k,i)
}
# Construction of the product of two polynomials times a constant.
omega <- normal[1] * convolve(rev(expand(k, origin[1], normal[1])),
expand(l, origin[2], normal[2]),
type="open")
# Integrate the resulting polynomial from 0 to `distance`.
sum(omega * distance^seq_along(omega) / seq_along(omega))
}
#
# Integrate monomials along a piecewise linear path given as a sequence of
# (x,y) vertices.
#
cintegrate <- function(xy, k, l) {
n <- dim(xy)[1]-1 # Number of edges
sum(sapply(1:n, function(i) {
dv <- xy[i+1,] - xy[i,] # The direction vector
lambda <- sum(dv * dv)
if (isTRUE(all.equal(lambda, 0.0))) {
0.0
} else {
lambda <- sqrt(lambda) # Length of the direction vector
-lintegrate(k, l+1, xy[i,], dv/lambda, lambda) / (l+1)
}
}))
}
#
# Compute moments of inertia.
#
inertia <- function(xy) {
mass <- cintegrate(xy, 0, 0)
barycenter = c(cintegrate(xy, 1, 0), cintegrate(xy, 0, 1)) / mass
uv <- t(t(xy) - barycenter) # Recenter the polygon to obtain central moments
i <- matrix(0.0, 2, 2)
i[1,1] <- cintegrate(uv, 2, 0)
i[1,2] <- i[2,1] <- cintegrate(uv, 1, 1)
i[2,2] <- cintegrate(uv, 0, 2)
list(Mass=mass,
Barycenter=barycenter,
Inertia=i / mass)
}
#
# Find principal axes of an inertial tensor.
#
principal.axes <- function(i.xy) {
obj <- eigen(i.xy)
t(t(obj$vectors) * obj$values)
}
#
# Construct a polygon.
#
circle <- t(sapply(seq(0, 2*pi, length.out=11), function(a) c(cos(a), sin(a))))
set.seed(17)
radii <- (1 + rgamma(dim(circle)[1]-1, 3, 3))
radii <- c(radii, radii[1]) # Closes the loop
xy <- circle * radii
#
# Compute principal axes.
#
i.xy <- inertia(xy)
axes <- principal.axes(i.xy$Inertia)
sign <- sign(det(axes))
#
# Plot barycenter and principal axes.
#
plot(xy, bty="n", xaxt="n", yaxt="n", asp=1, xlab="x", ylab="y",
main="A random polygon\nand its principal axes", cex.main=0.75)
polygon(xy, col="#e0e0e080")
arrows(rep(i.xy$Barycenter[1], 2),
rep(i.xy$Barycenter[2], 2),
-axes[1,] + i.xy$Barycenter[1], # The -signs make the first axis ..
-axes[2,]*sign + i.xy$Barycenter[2],# .. point to the right or down.
length=0.1, angle=15, col=c("#e02020", "#4040c0"), lwd=2)
points(matrix(i.xy$Barycenter, 1, 2), pch=21, bg="#404040")