Substrate Geometry
Brian S. Yandell
03 August 2026
geometry.RmdThis document details the tridiagonal triangular coordinate system , substrate network topology, unit coordinate rescaling (), discrete 6-sided hexagonal polygon tile generation, and spatial organism movement visualization modules.
1. Tridiagonal Coordinate System & Cartesian Transformation
This document outlines the triangular coordinate system used in the
ewing simulation package for modeling substrates and
organism movement, as described in the vignettes/ewing.Rmd
and implemented via the substrate.*.txt files.
Prompts
Start a new document inst/doc/refactor/triangle.md. This will be used
to document the triangular coordinate system described in
vignettes/ewing.Rmd subsection “Substrates and movement
around triangular grid”. Refer also to data/substrate.*.txt
files. Add to this discussion of R/triangle.R routines and
their use (notably rtri()) in other R routines.
Overview
The ewing simulation system allows individuals to
disperse across a set of interconnected substrate elements.
This environment is structured upon a triangular coordinate system.
The triangular grid captures the majority (~95%) of movement dynamics while significantly simplifying computational overhead. Approximations using minimum and maximum calculations replace the more intensive quadratic (Pythagorean) calculations that would be necessary in a standard rectangular coordinate system.
Each substrate patch on the grid is modeled as an interconnected
triangle, with an effective diameter of 10 units (hardwired in the
internal routine event.move).
Substrate Connectivity (substrate.substrate)
Connectivity between different substrate segments is determined by
the interaction matrix defined in
data/substrate.substrate.txt.
For example, a typical grid consists of fruits (fr1,
fr2, fr3, fr4),
twig, and leaves (lftop, lfbot),
with the configuration indicating pairwise connections. A value of
1 signifies an available path between components, whereas
0 (typically the diagonal) meaning no self-loop transition
is explicitly defined on the graph level.
Organism Movement Arrays (substrate.host and
substrate.parasite)
Movement options and biases for species traversing the grid are
directed by the data/substrate.host.txt and
data/substrate.parasite.txt matrices.
These tables parameterize:
-
substrate: The type of substrate component (e.g.,fruit,twig,leaf). -
side: Substrate elements may have complex, multi-sided topographies. For instance, fruits might have sides1, 2, 3, 4, and leaves may be categorized intotopandbottom. -
init: Relative weight determining the probability of an individual’s initial placement on this element. -
find: The relative probability parameter related to a parasite finding a host. -
move: The relative weight dictating the probability an individual will choose to traverse from the current element. -
Relative Destination Weights: The final columns
(e.g.,
fruit,twig,leaf) specify the comparative preference/weight of an organism transitioning from its current substrate to an adjacent one of the specified type.
By configuring these text files, users can fully customize the graph of the environment, establishing behavioral traits like affinity to specific plant structures (e.g., parasites preferring the top of a leaf compared to the underside) or aggregation dynamics.
Implementation of Tridiagonal Coordinates
(R/triangle.R)
The mathematical backing of the triangular coordinate system resides
in R/triangle.R, converting simulation parameters into
physical space. The fundamental characteristic of the tridiagonal system
is that coordinates consist of three axes values
()
which intrinsically sum to zero
().
Note that the terms triangle, tridiagonal, and
tri are used interchangeably in the codebase to reference
this same 3-axis space.
Core Routines
-
rtri(n, width, tri, roundoff): Generates randomized adjustments to coordinate positions for individuals. Within the spatial dimension bounds[0, width], it calculates randomized uniform variable transformations onto the 3 tridiagonal axes. -
car2tri(xy)andtri2car(tri): Helper transformation matrices parsing objects between conventional 2D Cartesian () mappings and the native tridiagonal format (). This translation is especially helpful for downstream plotting limits (plot.current) or interactions needing euclidean geometric interpretations. -
tridist(tri)andcardist(xy): Evaluate distance in 3-axis versus 2-axis formats. Distance in triangular bounds computationally reduces to retrieving themaxvalue of the matrices across an axis, further enabling performance optimizations mentioned earlier (~95% approximation with minimum processing cost).
Usages within Simulation State
The coordinate engine interacts closely with initialization and organism event handling:
-
R/init.population.R: During baseline generation viainit.population(), an initial positional dispersion on the substrate space is computed utilizingrtri(n, width = 100). Thus, individuals manifest randomly displaced throughout the substrate plane up to a radius of 100 units prior to assignment to nodespos.a,pos.b, andpos.c. -
R/move.R: Individuals progressing to anevent.movescheduled activity can hop across the substrate grid. Assuming they do not transfer directly entirely between disjoint components (sub.future), their physical step size traversing within their local substrate updates viartri(n, width = 10). This encapsulates standard micro-movements of scale 10 simulation units.
Substrate Triangle Reconstruction
Prompts
Develop an R script under
inst/scripts/substrate_triangle.R to programmatically
reconstruct the tridiagonal grid image from
Documents/plant_triangle.jpg. The image depicts a network
composed of 8 connected triangles: lftop,
lfbot, tw1, tw2,
fr1, fr2, fr3, and
fr4.
Instead of using random noise generation via rtri(), we
will generate a regular geometric lattice using native triangular
coordinates
,
and apply topology offsets to map the interconnected substrate
shapes.
Walkthrough
-
Fixed
car2tri.default(R/triangle.R): Discovered and patched a silent matrix dimension bug.rbind(x, y)was improperly generating a2xNmatrix instead of the expectedNx2matrix. This caused parsing errors and translation failures in the tridiagonal coordinate conversions. The function now appropriately runscbind, feeding correctly oriented memory tocar2tri(). -
Created
substrate_triangle.R: Added the new tridiagonal topological visualization toolinst/scripts/substrate_triangle.R. This tool generates a localized mesh array modeling interlocking geometries for standard upward and downward component orientations, correctly restricted to precisely 10 dots per edge using mathematical delta- stepadjustments. -
Topology Mappings: Formulated a refined tridiagonal
topology matrix spanning the visual plane. This explicitly builds the
hexagonal layout dictated by the adjacency limits, positioning
fr2as the downward central triangle seamlessly abutting (fr1,fr3,fr4), and sequentially attaching thetwigandleafmodules to form proper continuous branches without empty grid gaps. -
Visual Overlays & Alignment: In addition to
resolving a 1-dot alignment slip (calibrating the adjacency anchors
exactly to the offset bound distances
W = 9), the structure generates explicit bounding polygons spanning the substrate borders with black outer lines. Finally, it dynamically calculates side boundary midpoints and interpolates them 25% inward towards the substrate centroid to properly overlay the1, 2, 3numeric axis boundary identifiers right inside their respective sides.
By passing the aggregate topology to the package’s internal
tri2car() geometry transformer, the code accurately maps
the layout to standardized Euclidean spatial data
enabling robust visualization.
Object-Oriented Refactoring
To maximize reusability across the simulation suite, the core
construction loops from the prototype script have been completely
abstracted into the package’s source directory under
R/substrate_triangle.R. This explicitly decouples the
network structure into formal functional operations:
-
substrate_topology(width, step): Isolates mathematical configuration offsets and coordinate boundary limits globally. We explicitly transitioned away from “plant” nomenclature to the “substrate” standard to support generalized interaction meshes. -
create_substrate(topology, width, step): Iterates across the defined configuration to compute analytical geometry components (extracting Euclidean mesh dots, defining polygon bounds via topological vertices, and interpolating numerical boundary indicators automatically). Returns an S3 object of formatclass = "substrate". -
autoplot.substrate(object): Implements a scalable visual overlay handler utilizingggplot2. Users can instantiate the tridiagonal substrate network internally and map it instantly utilizing native commands likeautoplot(my_substrate)!
The inst/scripts/substrate_triangle.R script now
exclusively invokes these native package functions (via
library(ewing)) to render the reconstruction!
Interactive Substrate Network Explorer
(triangleApp)
Prompts
Use `inst/scripts/substrate_triangle.R` to create `R/triangleApp.R`. Create a `demos/triangleApp.qmd` for deployment. First create an Implementation Plan for approval.
Document in `inst/doc/refactor/triangle.md`.
Architectural Rationale & Features
-
Interactive Substrate Grid Scaling (
width&step):-
triangleApp(width = 10, step = 1)provides numeric inputs allowing users to dynamically scale substrate component radius (width) and grid dot density spacing (step). - Modifying
widthorstepinstantly recalculates the tridiagonal topology lattice (substrate_topology) and spatial geometry (create_substrate).
-
-
Modular Filtering & Layer Toggles:
-
Substrate Module Selector: Users can select or
remove individual plant components (
fr1..fr4fruits,tw1..tw2twigs,lftop..lfbotleaves) using a multi-select tag list (selectizeInput). -
Display Layer Checkboxes: Users can toggle
individual graphical layers:
-
Boundaries(geom_polygonoutlining substrate patch perimeters). -
Dots(geom_pointshowing grid coordinate points). -
Labels(geom_textdisplaying component center labels). -
Numbers(geom_textdisplaying side edge numbers 1, 2, 3).
-
-
Substrate Module Selector: Users can select or
remove individual plant components (
-
Substrate Statistics & Metrics Panel:
- A dedicated card displays real-time summary metrics:
- Active module count vs. total available modules.
- Total spatial coordinate dots across selected modules.
- Extents spanning spatial and dimensions.
- Point density breakdown list per substrate patch.
- A dedicated card displays real-time summary metrics:
-
Ultra-Compact Sidebar Design:
- Follows the tight side panel layout established in
tempApp.R(side-by-side flex inputs, inline checkboxes, ~180px height), ensuring the sidebar aligns with the plot height and eliminates empty space.
- Follows the tight side panel layout established in
-
Serverless Shinylive WebAssembly Deployment
(
demos/triangleApp.qmd):- Includes standalone implementations of core substrate routines
(
tricoord,tri2car,get_substrate_grid,substrate_topology,create_substrate). - Indexed in
demos/index.qmd(listing grid) anddemos/_quarto.yml(navbar navigation).
- Includes standalone implementations of core substrate routines
(
Hexagonal Grid Overlay & Global Organism Positioning
(hexmoveApp)
The tridiagonal substrate network engine has been expanded to map live simulation organism coordinates directly onto hexagonal substrate grid overlays:
-
Substrate Patch Resolution (ewing_substrate): Maps
organism substrate stage indices
individual["sub.stage"]to specific plant substrate elements (fr1,fr2,fr3,fr4,twig/tw1,lftop,lfbot). -
Hexagonal Overlay Generation (create_hex_overlay):
Constructs 6-vertex polygon tiles for each lattice dot in a
substrateobject, rendering discrete hexagonal cells across all substrate components (fr1..fr4,tw1..tw2,lftop..lfbot). -
Per-Substrate Surface Coordinate Rescaling (ewing_substrate):
Rescales organism local coordinates
into the unit triangle
of each active substrate patch (supporting custom substrate sizes
per surface). Applies topological offsets (
offset) and orientations (dir = "up"or"down"), converting to global Euclidean coordinates viatri2car()so all organisms are visualized strictly within their designated substrate surface bounds. -
Integrated Module Controls (substrateApp): Supports
switching between
"hex"(global hexagonal grid overlay) and"facet"(panel view faceted by substrate element), alongside action buttons for stepping through simulation events (+1,+10,+100steps). -
Interactive Application (hexmoveApp): Composes
initParInput,initServer, andsubstrateServerinto an interactive exploration tool. See hexmove.md for full documentation.
Overview
The primary goal of this refactoring pipeline is to utilize
substrateApp (substrateInput,
substrateOutput, substrateServer) to track and
simulate organism movement across substrate grid networks, establishing
a foundation to project host and parasite spatial positions onto
real-world geographic maps identified with
hexmapApp.
Modular Integration Milestones
1. sysetholApp Integration
(R/sysetholApp.R)
-
Sidebar Composition: Replaced simplified/duplicated
species display controls in
sysetholInputwithsubstrateInput("substrate"). Users can now adjust layout views (Hex Substrate Overlay vs Faceted Substrates), species modes (Overlay vs Separate), step densities, substrate radii, display layers (boundaries, hex grid, organisms, labels), and incremental step buttons directly within the Systems Ethology Platform. -
Output Tab Composition: Updated
sysetholOutputto embedsubstrateOutput("substrate")inside the Substrate Plots tab. -
Server Module Delegation: Replaced ~45 lines of
custom plotting code in
sysetholServerwith a clean call tosubstrateServer("substrate", simres = current_sim).
2. hexmoveApp Modularization
(R/hexmoveApp.R)
-
Exported Module Components: Added and exported
hexmoveAppInput(),hexmoveAppOutput(), andhexmoveAppServer()inR/hexmoveApp.RandNAMESPACE. -
Composable App Launcher: Re-architected
hexmoveApp()to compose its sidebar and main panel directly fromhexmoveAppInput,hexmoveAppOutput, andhexmoveAppServer, cleanly delegating spatial grid stepping and plotting tosubstrateServer.
3. Shinylive WebAssembly Demos (demos/)
-
WebAssembly Script Dependencies
(
inc_files): Expandedinc_filesin bothdemos/sysetholApp.qmdanddemos/hexmoveApp.qmdto includeR/triangle.R,R/community.R,R/Org.R,R/substrate_triangle.R,R/ewing_substrate.R,R/substrateApp.R, andR/sysetholApp.R/R/hexmoveApp.R. -
get.species&OrgFallbacks: Updatedget.speciesinR/community.RandgetOrgFuture,getOrgFeature,getOrgInteractinR/Org.Rwith safe fallbacks for webR adapter objects (wherecommunity$pop[[species]]$orgis present), resolving the Shinylive WebAssembly'could not find function "get.species"'runtime error.
Strategic Roadmap: Spatial Movement on Geographic Hexmaps
flowchart LR
A["Substrate Geometry<br>(tricoord a,b,c)"] --> B["substrateApp<br>Movement & Display"]
B --> C["hexmoveApp / sysetholApp<br>Interactive Stepping"]
D["hexmapApp / leafletApp<br>USGS HUC12 Watersheds"] --> E["add_watershed_hex_overlay()<br>Polygon Clipping"]
B --> F["Geographic Substrate Map<br>(Organisms on Map)"]
E --> F
-
Phase 1 (Completed): Standardize
substrateAppas the central organism movement visualizer and module acrosssysetholAppandhexmoveApp. -
Phase 2 (Next Step): Map tridiagonal triangular
coordinates
from
substrateAppto geographic latitude/longitude polygons generated byhexmapApp(R/hexmapApp.R,R/watershed_overlay.R). - Phase 3: Implement dynamic organism movement animation over watershed boundaries with spatial density overlays on real-world GIS features (e.g., Isle Royale USGS HUC12 subwatersheds).
Prompts
I want to develop an app in this repo that begins with
a simulation at some stage, say
mysim <- init.simulation()
mysim <- future.events(mysim, nstep=100)
I want to visualize the position of each organism on the hexagonal substrate of the `triangleApp()` app (with hexagonal grid overlay instead of centroid points) using
symbols as in `substrateApp()`. Place organisms on hexagons
based on their triangular coordinates rather than the horizontal/vertical system used in `substrateApp()`.
I want to be able to step through the simulation, either
one step at a time or multiple steps.
Architectural Rationale & Overview
The hexmoveApp application visualizes host and parasite
spatial positions mapped directly onto a unified hexagonal substrate
network topology (create_substrate).
Instead of rendering substrate components (fr1..fr4,
tw1..tw2, lftop, lfbot) in
isolated rectangular panel facets (as done in the original
substrateApp()), hexmoveApp places every
organism on a continuous hexagonal substrate plane based on its native
tridiagonal coordinates
and its active substrate patch (sub.stage).
Organism coordinates are dynamically rescaled per substrate component
so that all individuals are visualized strictly within the boundaries of
their respective substrate surface patches (fr1,
fr2, lftop, twig, etc.).
Substrate Resolution & Rescaling Coordinate Mapping
1. Substrate Component Resolution
Each organism in an ewing simulation tracks its
substrate position in individual["sub.stage"]. This integer
index maps to specific plant substrate elements defined in
getOrgInteract(community, substrate, species): -
fr1, fr2, fr3, fr4:
Fruit component facets - twig / tw1,
tw2: Twig component facets - lftop,
lfbot: Leaf top and bottom surface facets
substrate_topology
defines topological offsets
and component orientations (dir = "up" or
"down") for each substrate element. Substrate component
names like twig are mapped to tw1 to ensure
every individual is placed precisely on its active substrate patch.
2. Per-Substrate Unit Triangle Rescaling
Simulation coordinates generated by
init.population(width = 100) or rtri() span up
to 100 units. To map organisms accurately within substrate surface
triangles of size
(which may vary per substrate in future configurations):
Local Coordinate Normalization: For the subset of organisms on substrate : Applying a 15% inner padding buffer ():
-
Global Tridiagonal Coordinate Transformation:
- If component orientation is
"up": - If component orientation is
"down"(inverted triangle):
- If component orientation is
Global Cartesian Mapping: Converting to Euclidean coordinates via
tri2car():
This guarantees that all organisms on substrate are displayed strictly inside that substrate’s surface area.
Multi-Species Display Modes & Filtering
hexmoveApp supports multi-species simulation communities
(e.g. host and parasite):
Species Filtering: Users can toggle which species to display (
Hostand/orParasite) using inline checkboxes in the sidebar (show_species).-
Multi-Species View Modes:
-
Overlay (1 Map): Renders all selected species (hosts AND parasites) simultaneously on a single unified hexagonal substrate map. Host stage symbols (0,1,2,3…) and parasite stage symbols (E,L,P,p…) sit together on the same hexagonal grid layout, allowing direct visualization of spatial host-parasite overlaps. -
Separate (Adjacent Maps): Renders each selected species on its own distinct hexagonal grid map side-by-side (cowplot::plot_grid(ncol = length(species), align = "h")).
-
Hexagonal Grid Overlay (create_hex_overlay)
A discrete hexagonal grid overlay is generated over the substrate
lattice points (sub_obj$points) using create_hex_overlay().
For each lattice center
,
a 6-sided polygon cell is computed:
where radius
scales with grid spacing step and nearest-neighbor distance
.
Interactive Simulation Stepping
The interactive controls in substrateInput and substrateServer provide: -
Stepping Action Buttons: +1 Step,
+10 Steps, +100 Steps, and Reset.
Clicking a step button executes
future.events(sim, nstep = n) and reactively updates the
substrate visualization. - Species Filter & View
Modes: Checkboxes for selecting species (host,
parasite) and radio buttons for
Overlay (1 Map) vs Separate (Adjacent Maps). -
Layout Switching: Toggle between
"Hex Substrate Overlay" (global hexagonal network) and
"Faceted Substrates" (panel view faceted by substrate
element fr1..fr4, twig, lftop,
lfbot). - Layer Controls: Dynamic
checkboxes for Substrate Boundaries (poly), Hex Grid
Overlay (hex), Organism Symbols (organisms),
Substrate Identifiers (centers), and Side Numbers
(labels).
Application Usage
Launch hexmoveApp with an initial simulation object or
run default initialization:
library(ewing)
# Initialize simulation and run initial 100 steps
mysim <- init.simulation()
mysim <- future.events(mysim, nstep = 100)
# Launch interactive Shiny app
hexmoveApp(mysim)The app launcher script is located at inst/scripts/hexmoveApp.R.