This gallery fits the same six graph families as the multiscale GRIP gallery, using SGD metric MDS. Graphs, edge lengths and seeds are retained so their representations are easy to compare. This is an illustration, not a controlled benchmark of optimizer quality or runtime. All SGD fits here use three dimensions; the preserved GRIP tree example uses two.
Each fit uses weighted graph shortest-path distances, uniform pair weights, and full metric MDS with the SGD backend. The examples are small enough for full distances. No edge-KK refinement follows MDS. For larger graphs see sparse SGD.
The reference coordinates generate edge lengths; they are not the MDS
targets. Shortest-path distances need not admit an exact Euclidean
representation, so a fitted layout can differ from the reference
geometry even with low stress. The ivue viewers support rotation, zoom
and selection of displayed geometries. Coordinates are centered for
display without rotation or scale fitting; these are not aligned
shape-recovery comparisons. The parameter grid is planar and is
displayed at z = 0. Install ivue and rgl to
build the interactive views.
The table below lists a representative subset of the larger family collection.
| Family class | Representative helper | Why it matters |
|---|---|---|
| Lifted lattices | mesh.surface.graph() |
Plain topology, nontrivial metric geometry |
| Wrapped surfaces | torus.surface.graph() |
Closed periodic surface geometry |
| Recursive fractals | vicsek.surface.graph() |
Self-similar bottlenecks and holes |
| Irregular manifolds | irregular.annulus.surface.graph() |
Non-lattice surface geometry with boundary |
| Porous 3D families | cube.channel.network.surface.graph() |
Volumetric geodesic detours |
| Intrinsic trees | kary.tree.weighted.graph() |
Geometry from edge lengths rather than ambient space |
This article shows one representative example from each class. The
interactive family browser described in
Interactive Exploration with gripui exposes a much larger
catalog.
The simplest geometry-rich family keeps the graph topology of a
rectangular mesh but induces edge lengths from a curved embedding in
R^3.
mesh.graph <- mesh.surface.graph(
5, 5,
surface = "saddle",
amplitude = 0.8
)
mesh.fit <- metric.mds(
edges = mesh.graph$edges,
n = mesh.graph$n,
edge.weights = mesh.graph$edge_weights,
dim = 3,
seed = 1,
backend = "sgd", pair.weights = "uniform",
max.iter = 1000, diagnostics = FALSE
)
#> Warning: Selected metric.mds() start terminated with iteration_limit; budget
#> exhaustion does not certify convergence; inspect metadata$starts
mesh.coords <- mesh.fit$coords
mesh.summary <- data.frame(
family = mesh.graph$family,
surface = mesh.graph$surface,
n = mesh.graph$n,
m = nrow(mesh.graph$edges),
weight_scale = mesh.graph$weight_scale
)
knitr::kable(mesh.summary, digits = 3)| family | surface | n | m | weight_scale |
|---|---|---|---|---|
| mesh | saddle | 25 | 40 | 0.66 |
documentation.view(
list(`SGD metric MDS`=mesh.coords,
`Reference geometry`=mesh.graph$coords_surface,
`Parameter grid`=cbind(mesh.graph$coords_param, z=0)),
edges=mesh.graph$edges, aligned=TRUE,
description="Mesh: drag to rotate, scroll to zoom; use the selector to compare geometries."
)Mesh: drag to rotate, scroll to zoom; use the selector to compare geometries.
The useful design pattern here is that the topology stays simple while the metric geometry becomes nontrivial.
Wrapped lattices extend the same idea to periodic topologies such as a torus.
torus.graph <- torus.surface.graph(
5, 6,
surface = "pinched",
minor.radius = 0.33,
amplitude = 0.18,
twist = 0.18
)
torus.fit <- metric.mds(
edges = torus.graph$edges,
n = torus.graph$n,
edge.weights = torus.graph$edge_weights,
dim = 3,
seed = 2,
backend = "sgd", pair.weights = "uniform",
max.iter = 1000, diagnostics = FALSE
)
#> Warning: Selected metric.mds() start terminated with iteration_limit; budget
#> exhaustion does not certify convergence; inspect metadata$starts
torus.coords <- torus.fit$coords
knitr::kable(
data.frame(
family = torus.graph$family,
surface = torus.graph$surface,
n = torus.graph$n,
m = nrow(torus.graph$edges)
)
)| family | surface | n | m |
|---|---|---|---|
| torus | pinched | 30 | 60 |
documentation.view(
list(`SGD metric MDS`=torus.coords,
`Reference geometry`=torus.graph$coords_surface),
edges=torus.graph$edges, aligned=TRUE,
description="Torus: drag to rotate, scroll to zoom; use the selector to compare geometries."
)Torus: drag to rotate, scroll to zoom; use the selector to compare geometries.
These wrapped families are especially useful when ordinary planar intuition is misleading and a 3D layout is the natural target.
The package now includes recursive square-mask families such as the Vicsek fractal and Sierpinski carpet variants, all of which can be lifted into curved surface geometries.
vicsek.graph <- vicsek.surface.graph(
level = 2,
surface = "ripple",
amplitude = 0.55
)
vicsek.fit <- metric.mds(
edges = vicsek.graph$edges,
n = vicsek.graph$n,
edge.weights = vicsek.graph$edge_weights,
dim = 3,
seed = 3,
backend = "sgd", pair.weights = "uniform",
max.iter = 1000, diagnostics = FALSE
)
#> Warning: Selected metric.mds() start terminated with iteration_limit; budget
#> exhaustion does not certify convergence; inspect metadata$starts
vicsek.coords <- vicsek.fit$coordsdocumentation.view(
list(`SGD metric MDS`=vicsek.coords,
`Reference geometry`=vicsek.graph$coords_surface),
edges=vicsek.graph$edges, aligned=TRUE,
description="Vicsek: drag to rotate, scroll to zoom; use the selector to compare geometries."
)Vicsek: drag to rotate, scroll to zoom; use the selector to compare geometries.
Recursive families are useful because they create holes, bottlenecks, and multi-scale detours that are hard to capture with simpler lattices.
Not all surface families in grip are based on regular
grids. The package also includes irregular triangulated manifolds with
boundary and closed irregular surfaces.
annulus.graph <- irregular.annulus.surface.graph(
rings = 5,
outer.count = 24,
inner.radius = 0.42,
surface = "folded",
amplitude = 0.2
)
annulus.fit <- metric.mds(
edges = annulus.graph$edges,
n = annulus.graph$n,
edge.weights = annulus.graph$edge_weights,
dim = 3,
seed = 4,
backend = "sgd", pair.weights = "uniform",
max.iter = 1000, diagnostics = FALSE
)
#> Warning: Selected metric.mds() start terminated with iteration_limit; budget
#> exhaustion does not certify convergence; inspect metadata$starts
annulus.coords <- annulus.fit$coordsdocumentation.view(
list(`SGD metric MDS`=annulus.coords,
`Reference geometry`=annulus.graph$coords_surface),
edges=annulus.graph$edges, aligned=TRUE,
description="Annulus: drag to rotate, scroll to zoom; use the selector to compare geometries."
)Annulus: drag to rotate, scroll to zoom; use the selector to compare geometries.
These families are important because they reduce lattice bias and produce more variable local geometry than mesh-like constructions.
The cubical branch introduces graphs that are genuinely volumetric rather than surface-like. Channel networks and related porous families create 3D geodesic detours that are hard to flatten cleanly.
cube.graph <- cube.channel.network.surface.graph(
side = 5,
level = 1,
surface = "twisted",
twist = 0.2
)
cube.fit <- metric.mds(
edges = cube.graph$edges,
n = cube.graph$n,
edge.weights = cube.graph$edge_weights,
dim = 3,
seed = 5,
backend = "sgd", pair.weights = "uniform",
max.iter = 1000, diagnostics = FALSE
)
#> Warning: Selected metric.mds() start terminated with iteration_limit; budget
#> exhaustion does not certify convergence; inspect metadata$starts
cube.coords <- cube.fit$coords
knitr::kable(
data.frame(
family = cube.graph$family,
n = cube.graph$n,
m = nrow(cube.graph$edges)
)
)| family | n | m |
|---|---|---|
| cube.channel.network | 104 | 212 |
documentation.view(
list(`SGD metric MDS`=cube.coords,
`Reference geometry`=cube.graph$coords_surface),
edges=cube.graph$edges, aligned=TRUE,
description="Cube: drag to rotate, scroll to zoom; use the selector to compare geometries."
)Cube: drag to rotate, scroll to zoom; use the selector to compare geometries.
This family class is especially useful for testing whether a method can preserve large-scale volumetric structure without collapsing tunnels and channels.
Some family geometries are intrinsic rather than ambient. The weighted tree helpers preserve the exact tree topology while assigning edge lengths directly from branch-depth and branch-position rules.
tree.graph <- kary.tree.weighted.graph(
k = 2,
depth = 4,
depth.rule = "geometric",
depth.decay = 0.82,
branch.rule = "linear",
branch.spread = 0.25
)
tree.fit <- metric.mds(
edges = tree.graph$edges,
n = tree.graph$n,
edge.weights = tree.graph$edge_weights,
dim = 3,
seed = 6,
backend = "sgd", pair.weights = "uniform",
max.iter = 1000, diagnostics = FALSE
)
#> Warning: Selected metric.mds() start terminated with iteration_limit; budget
#> exhaustion does not certify convergence; inspect metadata$starts
tree.coords <- tree.fit$coords
knitr::kable(
head(tree.graph$edge_table[, c(
"parent",
"child",
"child_depth",
"branch_index",
"edge_weight"
)]),
digits = 3
)| parent | child | child_depth | branch_index | edge_weight |
|---|---|---|---|---|
| 1 | 2 | 1 | 1 | 1.411 |
| 1 | 3 | 1 | 2 | 1.814 |
| 2 | 4 | 2 | 1 | 1.157 |
| 2 | 5 | 2 | 2 | 1.487 |
| 3 | 6 | 2 | 1 | 1.157 |
| 3 | 7 | 2 | 2 | 1.487 |
documentation.view(
list(`SGD metric MDS`=tree.coords),
edges=tree.graph$edges, aligned=TRUE,
description="Tree: drag to rotate, scroll to zoom; use the selector to compare geometries."
)Tree: drag to rotate, scroll to zoom; use the selector to compare geometries.
These tree families are useful because they separate intrinsic weighted geometry from any arbitrary 3D embedding choice.
The table records each fit’s starts. An iteration-limit warning means that coordinates were returned without meeting the stopping criterion; it does not establish convergence or an optimum. Assess graph-distance error and stability across starts before interpreting a layout as evidence about geometry.
fits <- list(Saddle=mesh.fit, Torus=torus.fit, Vicsek=vicsek.fit,
Annulus=annulus.fit, Porous=cube.fit, Tree=tree.fit)
for (name in names(fits)) {
cat("\n\n### ", name, "\n\n", sep="")
print(knitr::kable(fits[[name]]$metadata$starts, digits=5))
}| start | initialization | initial_raw_stress | raw_stress | normalized_stress | iterations | converged | termination | backend_stress | warnings | error | elapsed_seconds | native_seed | pair_updates | best_epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | classical | 55.80461 | 49.18985 | 0.01288 | 1000 | FALSE | iteration_limit | 3.86546 | 0.102 | 1140350788 | 3e+05 | 907 |
| start | initialization | initial_raw_stress | raw_stress | normalized_stress | iterations | converged | termination | backend_stress | warnings | error | elapsed_seconds | native_seed | pair_updates | best_epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | classical | 103.5559 | 65.48395 | 0.0123 | 1000 | FALSE | iteration_limit | 5.34843 | 0.142 | 794080207 | 435000 | 957 |
| start | initialization | initial_raw_stress | raw_stress | normalized_stress | iterations | converged | termination | backend_stress | warnings | error | elapsed_seconds | native_seed | pair_updates | best_epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | classical | 70.32794 | 51.06717 | 0.00487 | 1000 | FALSE | iteration_limit | 1.46075 | 0.101 | 721735354 | 3e+05 | 992 |
| start | initialization | initial_raw_stress | raw_stress | normalized_stress | iterations | converged | termination | backend_stress | warnings | error | elapsed_seconds | native_seed | pair_updates | best_epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | classical | 421.7898 | 280.528 | 0.00239 | 1000 | FALSE | iteration_limit | 9.35364 | 1.293 | 368509515 | 3916000 | 993 |
| start | initialization | initial_raw_stress | raw_stress | normalized_stress | iterations | converged | termination | backend_stress | warnings | error | elapsed_seconds | native_seed | pair_updates | best_epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | classical | 2558.222 | 2236.12 | 0.01245 | 1000 | FALSE | iteration_limit | 66.66874 | 1.802 | 859942763 | 5356000 | 985 |
| start | initialization | initial_raw_stress | raw_stress | normalized_stress | iterations | converged | termination | backend_stress | warnings | error | elapsed_seconds | native_seed | pair_updates | best_epoch |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 1 | classical | 340.2358 | 122.8133 | 0.00596 | 1000 | FALSE | iteration_limit | 2.76944 | 0.153 | 456454154 | 465000 | 944 |