Graph gallery: SGD metric MDS

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.

library(grip)

Family classes at a glance

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.

Lifted lattices

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 surfaces

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.

Recursive and fractal families

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$coords
documentation.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.

Irregular manifolds

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$coords
documentation.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.

Porous 3D families

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.

Intrinsic weighted trees

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.

Stopping status

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))
}

Saddle

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

Torus

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

Vicsek

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

Annulus

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

Porous

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

Tree

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