librarylibs/Manifold/src/Manifold.xtl
Manifold: the unit sphere, the Poincare ball, and the pictures of the circle, the sphere, the torus and the Swiss roll: the distance measured along the space, putting a point onto it, the tangent part of a vector at a point, a step from a point along a tangent vector that stays on the space and the arrow that undoes it, a mean that stays on the space, how far off it a point is; the points of each space at given coordinates of its map, for drawing it; and the distortion of an embedding's distances against those it should keep. Import it with an alias of your choice: "mf:" u_se< "Manifold". Put libs/Manifold/src on XETAL_PATH ("just path"); the reference is libs/Manifold/docs. Names with l: are exported; those under h: are private to this file.
A point is a vector of Floats, in any number of dimensions; a set of points is a matrix, one point per row. Each space has a name that starts its functions (s_phere, b_all the Poincare ball, c_ircle, t_orus, r_oll the Swiss roll), and the word after it is the same verb for every space: Project (onto it), Distance (along it), Tangent (the part of a vector along it at a point), Step (from a point, along a tangent vector, staying on it), Arrow (the tangent vector a Step would take from one point to another), Mean (of a set, on it), Off (how far a point is from it); and for drawing, Points (the points at rows of coordinates of the space's map), Arc or Geodesic (points along the shortest path between two points). So a program written for the sphere reads the same on the ball. Every result is a Float.
Vectors
ʰd̲ot : (Num a, Num b) => a -> b -> Float
u d̲ot v: the sum of the products, as a Float.
ʰd̲ot ← { u v → '+ r̲/ (f̲loat u) × f̲loat v }
ʰn̲orm : Num a => a -> Float
n̲orm v: the length of v, straight through the space it sits in.
ʰn̲orm ← { v → s̲qrt v ʰd̲ot v }
ʰs̲caleRows : Num a => a -> a -> a
s v s̲caleRows M: each row of M scaled by the matching item of s.
ʰs̲caleRows ← { s M → M × s 'l̲eft t̲able o̲ffsets 2 s̲elect s̲hape M }
ʰa̲ngle : (Num a, Num b) => a -> b -> Float
m a̲ngle s: the angle, 0 to pi, between two unit vectors p and
q, from the lengths m = |p - q| and s = |p + q|: 2 atan2(m, s).
Accurate near 0 and near pi alike, where a_cos of the product
p.q is not (near 0 it loses half the digits), because neither
length is a difference of nearly equal numbers.
ʰa̲ngle ← { m s → 2.0 × (f̲loat m) ᵗʳa̲tan2 f̲loat s }
ʰa̲part : (Num a, Num b) => a -> b -> (Float, Float)
p a̲part q: |p - q| and |p + q|, as a tuple.
ʰa̲part ← { p q → (ʰn̲orm (f̲loat p) − f̲loat q, ʰn̲orm (f̲loat p) + f̲loat q) }
ʰa̲partRows : (Num a, Num b) => a -> b -> (Float, Float)
P a̲partRows p: |r - p| and |r + p| for every row r of P.
ʰa̲partRows ← { P p → Q ← ((f̲irst s̲hape P) r̲eshape 1.0) '× t̲able f̲loat p ⍝ p in every row D ← (f̲loat P) − Q S ← (f̲loat P) + Q (s̲qrt '+ r̲/₂ D × D, s̲qrt '+ r̲/₂ S × S) }
ʰa̲partPairs : Num a => a -> (Float, Float)
a̲partPairs P: |r - r'| and |r + r'| for every pair of rows of
P, two matrices, summed one coordinate at a time (so no number is
a difference of nearly equal ones).
ʰa̲partPairs ← { P → n ← f̲irst s̲hape P zero ← (n c̲at n) r̲eshape 0.0 (D, S, _) ← (2 s̲elect s̲hape P) '{ (d, s, k) → c ← f̲loat k s̲elect₂ P (d + (c '− t̲able c) ^ 2.0, s + (c '+ t̲able c) ^ 2.0, k + 1) } p̲ower (zero, zero, 1) (s̲qrt D, s̲qrt S) }
The unit sphere
ˡs̲phereProject : Num a => a -> Float
s̲phereProject v: v put onto the unit sphere, scaled to length 1;
the direction of v, kept. Given a set (a matrix), every row. The
zero vector has no direction and is refused.
"mf:" u̲se< "Manifold"
ᵐᶠs̲phereProject 3.0 4.0 0.0 0.6 0.8 0.0
ᵐᶠs̲phereProject 2 3 r̲eshape 3.0 4.0 0.0 0.0 0.0 2.0 0.6 0.8 0.0 0.0 0.0 1.0
ˡs̲phereProject ← { v → 2 = t̲ally s̲hape v ? ʰp̲rojectRows v n ← ʰn̲orm v n = 0.0 ? @ p̲anic< "the zero vector has no direction: it cannot be put onto the sphere" (f̲loat v) ÷ n }
ʰp̲rojectRows : Num a => a -> Float
Every row of P put onto the sphere.
ʰp̲rojectRows ← { P → n ← s̲qrt '+ r̲/₂ (f̲loat P) × f̲loat P '∨ r̲/ n = 0.0 ? @ p̲anic< "a zero row has no direction: it cannot be put onto the sphere" (f̲loat P) ÷ n 'l̲eft t̲able o̲ffsets 2 s̲elect s̲hape P }
ˡs̲phereDistance : (Num a, Num b) => a -> b -> Float
p s̲phereDistance q: the great-circle distance between two points
of the sphere, the angle between them in radians (0 to pi): the
shortest path along the sphere, as against the straight line
through it, which is shorter (its length is 2 sin of half the
angle). p and q must be on the sphere (s_phereProject).
"mf:" u̲se< "Manifold"
1.0 0.0 0.0 ᵐᶠs̲phereDistance 0.0 1.0 0.0 1.5707963267948966
ˡs̲phereDistance ← { p q → (m, s) ← p ʰa̲part q◆ m ʰa̲ngle s }
ˡs̲phereDistances : Num a => a -> Float
s̲phereDistances P: the great-circle distance between every pair
of rows of P (points on the sphere): a matrix, 0 on its diagonal.
"mf:" u̲se< "Manifold"
ᵐᶠs̲phereDistances 2 2 r̲eshape 1.0 0.0 0.0 1.0 0.0 1.5707963267948966 1.5707963267948966 0.0
ˡs̲phereDistances ← { P → (M, S) ← ʰa̲partPairs P◆ M ʰa̲ngle S }
ˡs̲phereTangent : (Num a, Num b) => a -> b -> Float
p s̲phereTangent v: the part of v that lies along the sphere at
p (in the tangent plane there): v less its part along p, the
normal. A tangent vector at p is one with no part along p.
"mf:" u̲se< "Manifold"
1.0 0.0 0.0 ᵐᶠs̲phereTangent 2.0 3.0 0.0 0.0 3.0 0.0
ˡs̲phereTangent ← { p v → (f̲loat v) − (p ʰd̲ot v) × f̲loat p }
ˡs̲phereStep : (Num a, Num b) => a -> b -> Float
p s̲phereStep v: the point reached from p by going along the
sphere in the direction of the tangent vector v, for a distance
equal to v's length (the exponential map): cos t p + sin t v / t
with t = |v|, put back onto the sphere so no rounding takes it
off. A zero v goes nowhere. (A quarter turn from the x axis along
y lands on the y axis; the x left over is cos of a quarter turn,
0 up to the machine's rounding.)
"mf:" u̲se< "Manifold"
1.0 0.0 0.0 ᵐᶠs̲phereStep 0.0 1.5707963267948966 0.0 0.00000000000000006123233995736766 1.0 0.0
ˡs̲phereStep ← { p v → t ← ʰn̲orm v t = 0.0 ? f̲loat p ˡs̲phereProject ((c̲os t) × f̲loat p) + (s̲in t) × (f̲loat v) ÷ t }
ˡs̲phereArrow : (Num a, Num b) => a -> b -> Float
p s̲phereArrow q: the tangent vector at p that s_phereStep
carries to q (the logarithmic map): it points along the sphere
toward q and is as long as the distance between them. For q = p
it is zero; for q the opposite point, which every direction
reaches at distance pi, it is zero too (there is no one arrow).
"mf:" u̲se< "Manifold"
1.0 0.0 0.0 ᵐᶠs̲phereArrow 0.0 1.0 0.0 0.0 1.5707963267948966 0.0
ˡs̲phereArrow ← { p q → u ← p ˡs̲phereTangent q n ← ʰn̲orm u n = 0.0 ? u (p ˡs̲phereDistance q) × u ÷ n }
ʰa̲rrows : (Num a, Num b) => a -> b -> Float
The arrows at p to every row of P, one per row (s_phereArrow, over a set at once): the mean's one step.
ʰa̲rrows ← { p P → c ← (f̲loat P) '+ '× i̲nner f̲loat p U ← (f̲loat P) − c '× t̲able f̲loat p n ← s̲qrt '+ r̲/₂ U × U (m, s) ← P ʰa̲partRows p ((m ʰa̲ngle s) ÷ n m̲ax 1e-300) ʰs̲caleRows U }
ˡs̲phereMean : Num a => a -> Float
s̲phereMean P: the mean of the rows of P on the sphere (the
Frechet or Karcher mean): the point whose summed squared distances
along the sphere to the rows is least. The plain mean of the rows
lies inside the sphere; this starts from that mean put onto the
sphere and takes 30 steps, each along the average of the arrows
to the rows, which is a fixed point when the arrows cancel. Points
spread over less than a hemisphere have one mean, and the steps
reach it; rows whose plain mean is the zero vector (say, two
opposite points) are refused.
"mf:" u̲se< "Manifold"
ᵐᶠs̲phereMean 2 3 r̲eshape 1.0 0.0 0.0 0.0 1.0 0.0 0.7071067811865476 0.7071067811865476 0.0
ˡs̲phereMean ← { P → n ← f̲loat f̲irst s̲hape P start ← ˡs̲phereProject ('+ r̲/ f̲loat P) ÷ n 30 '{ p → p ˡs̲phereStep ('+ r̲/ p ʰa̲rrows P) ÷ n } p̲ower start }
ˡs̲phereOff : Num a => a -> Float
s̲phereOff p: how far p is from the sphere: the difference
between its length and 1, as a size. 0 for a point on it; what a
test of a constraint reports. Given a set (a matrix), one per row.
"mf:" u̲se< "Manifold"
ᵐᶠs̲phereOff 0.0 0.5 0.0 0.5
ᵐᶠs̲phereOff 2 2 r̲eshape 0.0 0.5 3.0 4.0 0.5 4.0
ˡs̲phereOff ← { p → 2 = t̲ally s̲hape p ? a̲bs (s̲qrt '+ r̲/₂ (f̲loat p) × f̲loat p) − 1.0 a̲bs (ʰn̲orm p) − 1.0 }
ʰc̲oordinate : Num a => Int -> a -> Float
k c̲oordinate p: the k-th coordinate of a point, or the k-th
column of a set of points (a matrix), as Floats.
ʰc̲oordinate ← { k p → 2 = t̲ally s̲hape p ? f̲loat k s̲elect₂ p◆ f̲loat k s̲elect p }
ʰc̲olumns : Int -> a -> a
c̲olumns M: the columns of a (rows x n) matrix given as one vector
of n columns laid end to end, back as the matrix: the transpose of
the n x rows reshape. For writing a set of points coordinate by
coordinate.
ʰc̲olumns ← { n v → o̲\ (n c̲at (t̲ally v) d̲iv n) r̲eshape v }
The sphere, drawn
ˡs̲pherePoints : Num a => a -> Float
s̲pherePoints LL: the point of the unit sphere at each
(longitude, latitude) row of LL, in radians (longitude round the
z axis from the x axis, latitude up from the equator): one row
(x, y, z) per row of LL. These are the sphere's map: a parallel is
rows of one latitude and many longitudes, a meridian rows of one
longitude. (A quarter turn of latitude is the pole: its x is cos
of a quarter turn, 0 up to the machine's rounding.)
"mf:" u̲se< "Manifold"
ᵐᶠs̲pherePoints 2 2 r̲eshape 0.0 0.0 0.0 1.5707963267948966 1.0 0.0 0.0 0.00000000000000006123233995736766 0.0 1.0
ˡs̲pherePoints ← { LL → lon ← 1 ʰc̲oordinate LL lat ← 2 ʰc̲oordinate LL 3 ʰc̲olumns ((c̲os lat) × c̲os lon) c̲at ((c̲os lat) × s̲in lon) c̲at s̲in lat }
ˡs̲phereArc : (Num a, Num b) => (a, b) -> Int -> Float
(p, q) s̲phereArc n: n + 1 points along the great circle from p
to q (both on the sphere), p first and q last, evenly spaced along
it: the points s_phereStep reaches from p along 0, 1/n, ..., 1 of
the arrow to q. For q = p, or q the opposite point (no one arc),
every point is p.
"mf:" u̲se< "Manifold"
(1.0 0.0 0.0, 0.0 1.0 0.0) ᵐᶠs̲phereArc 2 1.0 0.0 0.0 0.7071067811865476 0.7071067811865475 0.0 0.00000000000000006123233995736766 1.0 0.0
ˡs̲phereArc ← { (p, q) n → v ← p ˡs̲phereArrow q t ← ʰn̲orm v rows ← (n + 1) r̲eshape 1.0 t = 0.0 ? rows '× t̲able f̲loat p T ← (t ÷ f̲loat n) × (f̲loat r̲ange n + 1) − 1.0 ʰp̲rojectRows ((c̲os T) '× t̲able f̲loat p) + (s̲in T) '× t̲able v ÷ t }
The circle
ˡc̲irclePoints : Num a => a -> Float
c̲irclePoints A: the point of the unit circle at each angle of A
(radians from the x axis, counterclockwise): one row (x, y) per
angle. The circle is the sphere of one dimension, the unit sphere
in two: s_phereProject puts a point onto it, s_phereDistance
measures along it (the angle between two points), s_phereArc draws
between them.
"mf:" u̲se< "Manifold"
ᵐᶠc̲irclePoints 0.0 3.141592653589793 1.0 0.0 -1.0 0.00000000000000012246467991473532
ˡc̲irclePoints ← { A → a ← f̲loat A◆ 2 ʰc̲olumns (c̲os a) c̲at s̲in a }
The torus
ˡt̲orusPoints : (Num a, Num b) => a -> b -> Float
radii t̲orusPoints UV: the point of the torus of radii R r (R
from the middle of the hole to the middle of the tube, r the
tube's) at each (u, v) row of UV, in radians (u round the hole, v
round the tube): ((R + r cos v) cos u, (R + r cos v) sin u,
r sin v), one row per row of UV. The torus's map is the (u, v)
rectangle, 0 to 2 pi each way, each pair of opposite edges joined.
"mf:" u̲se< "Manifold"
3.0 1.0 ᵐᶠt̲orusPoints 2 2 r̲eshape 0.0 0.0 0.0 3.141592653589793 4.0 0.0 0.0 2.0 0.0 0.00000000000000012246467991473532
ˡt̲orusPoints ← { radii UV → R ← f̲loat 1 s̲elect radii r ← f̲loat 2 s̲elect radii u ← 1 ʰc̲oordinate UV v ← 2 ʰc̲oordinate UV w ← R + r × c̲os v 3 ʰc̲olumns (w × c̲os u) c̲at (w × s̲in u) c̲at r × s̲in v }
ˡt̲orusOff : (Num a, Num b) => a -> b -> Float
radii t̲orusOff p: how far p is from the torus of radii R r: the
distance from p to the circle through the middle of the tube, less
r, as a size. 0 for a point on it. Given a set (a matrix), one per
row.
"mf:" u̲se< "Manifold"
3.0 1.0 ᵐᶠt̲orusOff 0.0 0.0 0.0 2.0
3.0 1.0 ᵐᶠt̲orusOff 2 3 r̲eshape 4.0 0.0 0.0 3.0 0.0 0.0 0.0 1.0
ˡt̲orusOff ← { radii p → R ← f̲loat 1 s̲elect radii r ← f̲loat 2 s̲elect radii x ← 1 ʰc̲oordinate p y ← 2 ʰc̲oordinate p z ← 3 ʰc̲oordinate p rho ← s̲qrt (x × x) + y × y a̲bs (s̲qrt ((rho − R) ^ 2.0) + z × z) − r }
The Swiss roll
ˡr̲ollPoints : Num a => a -> Float
r̲ollPoints TH: the point of the Swiss roll at each (t, h) row of
TH: (t cos t, h, t sin t). The roll is a flat sheet rolled up:
along t it winds round the y axis, farther out with every turn (a
spiral whose radius is t); along h it runs straight up the axis.
Its map is the sheet itself, the rectangle of (r_ollAlong t, h).
"mf:" u̲se< "Manifold"
ᵐᶠr̲ollPoints 2 2 r̲eshape 0.0 0.0 3.141592653589793 1.0 0.0 0.0 0.0 -3.141592653589793 1.0 0.00000000000000038473413874435795
ˡr̲ollPoints ← { TH → t ← 1 ʰc̲oordinate TH h ← 2 ʰc̲oordinate TH 3 ʰc̲olumns (t × c̲os t) c̲at h c̲at t × s̲in t }
ˡr̲ollAlong : Num a => a -> Float
r̲ollAlong t: the length of the roll's spiral from its middle
(t = 0) out to t, (t sqrt(1 + t^2) + asinh t) / 2: how far along
the unrolled sheet the points at t lie. Its map's first coordinate.
"mf:" u̲se< "Manifold"
ᵐᶠr̲ollAlong 0.0 1.0 0.0 1.147793574696319
ˡr̲ollAlong ← { t → x ← f̲loat t◆ ((x × s̲qrt 1.0 + x × x) + ᵗʳa̲sinh x) ÷ 2.0 }
ˡr̲ollDistance : (Num a, Num b) => a -> b -> Float
a r̲ollDistance b: the distance along the sheet between the
points of the roll at (t, h) a and b. The sheet is flat, so it is
the plain distance between the two unrolled,
sqrt((along a - along b)^2 + (h_a - h_b)^2): two points a turn
apart are near in three dimensions and far along the sheet.
"mf:" u̲se< "Manifold"
0.0 0.0 ᵐᶠr̲ollDistance 1.0 0.0 1.147793574696319
ˡr̲ollDistance ← { a b → da ← (ˡr̲ollAlong 1 s̲elect a) − ˡr̲ollAlong 1 s̲elect b dh ← (f̲loat 2 s̲elect a) − f̲loat 2 s̲elect b s̲qrt (da × da) + dh × dh }
The Poincare ball
ʰm̲obius : (Num a, Num b) => a -> b -> Float
u m̲obius v: Mobius addition, the ball's own way of adding (u
then v, each inside the unit ball): ((1 + 2 u.v + |v|^2) u +
(1 - |u|^2) v) / (1 + 2 u.v + |u|^2 |v|^2). The result stays in
the ball; (-u) m_obius v is v seen from u, as a vector at the
middle.
ʰm̲obius ← { u v → uv ← u ʰd̲ot v uu ← u ʰd̲ot u vv ← v ʰd̲ot v (((1.0 + (2.0 × uv) + vv) × f̲loat u) + (1.0 − uu) × f̲loat v) ÷ (1.0 + (2.0 × uv) + uu × vv) }
ˡb̲allDistance : (Num a, Num b) => a -> b -> Float
p b̲allDistance q: the distance between two points of the
Poincare ball (the inside of the unit sphere with the hyperbolic
metric; in two dimensions the Poincare disk), 2 atanh of the
length of q seen from p, (-p) m_obius q. Near the middle it is
twice the plain distance; toward the rim it grows without bound,
so points near the rim a little apart in the plane are far apart
along the ball. p and q must be inside the ball (length under 1).
"mf:" u̲se< "Manifold"
0.0 0.0 ᵐᶠb̲allDistance 0.5 0.0 1.0986122886681098
ˡb̲allDistance ← { p q → 2.0 × ᵗʳa̲tanh ʰn̲orm (n̲eg p) ʰm̲obius q }
ˡb̲allGeodesic : (Num a, Num b) => (a, b) -> Int -> Float
(p, q) b̲allGeodesic n: n + 1 points along the geodesic (the
shortest path, by the ball's distance) from p to q, p first and q
last, evenly spaced along it: p m_obius the arrow to q scaled by
0, 1/n, ..., 1 in the ball's way (tanh of that fraction of atanh
its length). Drawn flat, a geodesic is an arc of a circle that
meets the rim at right angles, or a diameter.
"mf:" u̲se< "Manifold"
(0.0 0.0, 0.6 0.0) ᵐᶠb̲allGeodesic 2 0.0 0.0 0.3333333333333333 0.0 0.6 0.0
ˡb̲allGeodesic ← { (p, q) n → w ← (n̲eg p) ʰm̲obius q r ← ʰn̲orm w rows ← (n + 1) r̲eshape 1.0 r = 0.0 ? rows '× t̲able f̲loat p S ← (t̲anh (ᵗʳa̲tanh r) × ((f̲loat r̲ange n + 1) − 1.0) ÷ f̲loat n) ÷ r d ← t̲ally p (M, _) ← (n + 1) '{ (M, k) → (M c̲at (1 c̲at d) r̲eshape p ʰm̲obius (k s̲elect S) × w, k + 1) } p̲ower ((0 c̲at d) r̲eshape 0.0, 1) M }
Distortion
ˡd̲istortion : (Num a, Num b) => a -> b -> Float
D d̲istortion E: how badly the distances E (between every pair of
points of an embedding, a matrix as s_phereDistances gives) keep
the distances D they should have (the same shape: the data's own
distances, say along a tree): for every pair of different points,
|E / D - 1|, the fraction by which the pair's distance is too long
or too short; the mean over the pairs and the worst pair, as a
pair of Floats. 0 0 when every distance is kept; 1 for a pair twice
or half as far as it should be (half: 0.5). Pairs at D = 0 (the
same point twice) are left out; the diagonal always is. The measure
of H1 (docs/research-questions.md), the same whatever space E was
measured along.
"mf:" u̲se< "Manifold"
D ← 3 3 r̲eshape 0.0 1.0 2.0 1.0 0.0 1.0 2.0 1.0 0.0
D ᵐᶠd̲istortion D 0.0 0.0
D ᵐᶠd̲istortion 2.0 × D 1.0 1.0
D ᵐᶠd̲istortion 3 3 r̲eshape 0.0 1.0 1.0 1.0 0.0 1.0 1.0 1.0 0.0 0.16666666666666666 0.5
ˡd̲istortion ← { D E → n ← f̲irst s̲hape D pair ← ((r̲ange n) '< t̲able r̲ange n) ∧ (f̲loat D) > 0.0 ⍝ each pair once, where there is a distance to keep k ← '+ r̲/ r̲avel pair k = 0 ? 0.0 0.0 off ← r̲avel (f̲loat pair) × a̲bs ((f̲loat E) ÷ (f̲loat D) m̲ax 1e-300) − 1.0 (('+ r̲/ off) ÷ f̲loat k) c̲at 'm̲ax r̲/ off }