Skip to content

First-tentative assembly: sphere body in a cylindrical domain

This guide walks through SHORE's first end-to-end assembly example — two structured meshes, generated independently, sharing the same world-coordinate domain. Run via examples/05-chimera-assembly/assembly_sphere_in_cylinder.py.

The example is first-tentative in a deliberately limited sense: SHORE produces both meshes, writes them to disk, and bundles them into a single ParaView-friendly .vtm for visual inspection. Hole cutting, fringe identification, and donor search are explicitly the solver's responsibility — that's the boundary between what SHORE ships today and what a full Chimera assembly framework would do, and where it stays for now.

What the example produces

Two meshes covering the same domain [-10, 10]² × [-10, 50]:

MeshTopologyBlocksRole
Near-bodyCubed sphere6Wraps a unit-radius sphere body at the origin, marched outward to total wall-normal extent ~0.2 (outer shell at r ≈ 0.7).
BackgroundFlat-caps cylinder5Fills the cylindrical domain (r ≤ 10, z ∈ [-10, 50]) with no central hole — central square + 4 trapezoidal sub-blocks. No Chimera fringe at the inner boundary.

The two meshes overlap in the near-body region (r < ~0.7 around the origin). Resolving that overlap is the downstream solver's job; SHORE just generates the geometry.

                background flat-caps cylinder (z ∈ [-10, 50], r ≤ 10)
              ┌─────────────────────────────────────────────────────┐
              │                                                     │
              │              near-body cubed sphere                 │
              │              (6 blocks, r ∈ [0.5, 0.7])             │
              │                       ╱──╲                          │
              │                      ╱    ╲                         │
              │                     ╲      ╱                        │
              │                      ╲────╱                         │
              │                                                     │
              │                                                     │
              └─────────────────────────────────────────────────────┘
                       ←  overlap region (Chimera fringe)  →

Running the example

bash
# Run with the built-in defaults; opens a PyVista viewer at the end.
python examples/05-chimera-assembly/assembly_sphere_in_cylinder.py

# Skip the viewer (CI-friendly).
python examples/05-chimera-assembly/assembly_sphere_in_cylinder.py --no-view

# Custom output directory.
python examples/05-chimera-assembly/assembly_sphere_in_cylinder.py -o my_assembly --no-view

# Drive both pieces from TOML configs.
python examples/05-chimera-assembly/assembly_sphere_in_cylinder.py \
    --near-body-config examples/05-chimera-assembly/config_assembly_near_body.toml \
    --background-config examples/05-chimera-assembly/config_assembly_background.toml \
    --no-view

By default the script:

  1. Builds an in-memory icosphere of radius 0.5 (diameter 1.0) and writes it to examples_out/sphere.stl.
  2. Builds the cubed-sphere near-body mesh through Mesh.from_stl(...) with CubedSphereTopology(), marches it outward, writes 6 .geo files (wall_sub0..3.geo, wall_cap_north.geo, wall_cap_south.geo).
  3. Builds the flat-caps cylinder background through FlatCapsCylinder.build(...), writes 5 .geo files (background_center.geo, background_sub_e/n/w/s.geo).
  4. Bundles all 11 blocks into one assembly.vtm MultiBlock for ParaView. PyVista creates a sister assembly/ directory holding the per-block .vts files; the .vtm is the manifest.

Output layout:

examples_out/
  sphere.stl                         # in-memory icosphere
  wall_sub0..sub3.geo            # 4 cubed-sphere equatorial blocks
  wall_cap_north/south.geo       # 2 cubed-sphere cap blocks
  background_center.geo              # flat-caps central square
  background_sub_e/n/w/s.geo         # flat-caps 4 trapezoidal sub-blocks
  assembly.vtm                       # 11-block ParaView MultiBlock manifest
  assembly/                          # 11 per-block .vts files (PyVista internals)

Inspecting the result in ParaView

Open examples_out/assembly.vtm. ParaView pulls in all 11 blocks automatically. Useful operations:

  • Surface with edges representation — see the cell structure of every block.
  • Color by Jacobian — every block carries a per-cell Jacobian as CellData, attached at write time. Healthy meshes are uniformly green on the default RdYlGn palette.
  • Block visibility toggle — hide the background sub-blocks to see the near-body mesh in isolation, or vice versa.
  • Slice through z = 0 — the cleanest way to see the overlap region; the cubed-sphere outer shell at r ≈ 0.7 sits inside the background's central-square block (which extends to r ≈ 4 with the default inner_frac = 0.4).

TOML configs

Both configs ship next to the script and are validated like any other SHORE config:

bash
shore config validate examples/05-chimera-assembly/config_assembly_near_body.toml
shore config validate examples/05-chimera-assembly/config_assembly_background.toml

Each config can also be used standalone with the corresponding CLI:

bash
shore mesh -c examples/05-chimera-assembly/config_assembly_near_body.toml -o wall
# (will fail because input STL doesn't exist; the orchestrator overrides
#  the input key with the in-memory icosphere it generates)

shore primitive flat-caps -c examples/05-chimera-assembly/config_assembly_background.toml -o background
# 5 .geo files written; same cylinder as the assembly produces

Parameter-tuning lessons baked into the defaults

Two parameter choices in this example are not obvious from first principles. Both are documented as comments inside the TOML configs; this section explains why they matter so users adapting the example to their own bodies don't have to rediscover them.

ni / nj and the C1 pinning window

The cubed-sphere topology pins each equator meridian's first cell to the matching cap-edge first cell (per-j C1 seam pin). The pin is active when the cap radial step θcap/(nc1) and the equator's natural radial step (π2θcap)/(ni1) are within a factor of 2 of each other, where nc=nj/4+1.

For this example with theta_cap = 30°:

ninjnccap stepequator stepratio
3060160.03490.07220.48 (just outside)
3040110.05240.07220.72 (inside)

30 × 60 would be a more natural choice on its own merits (more cells on the equator and on the cap edges), but it falls outside the pinning window — the equator falls back to unpinned spacing and emits a UserWarning. 30 × 40 keeps the pinning active.

Geometric vs. tanh wall-clustering on small bodies

Wall-clustered tanh spacing (k_spacing = "tanh" with total_thickness) is the obvious default for boundary-layer meshes — it gives a fixed outer shell and clusters cells near the wall. But on a small body (here, radius 0.5), pushing nk and beta typical for production wall-resolved LES (nk = 25, beta = 4) gives a first cell of order 104, which is < 1 % of the surface cell scale. The cubed-sphere cap-equator corner cells then invert on the very first march step.

Geometric spacing (ds, growth) gives a uniform cell aspect ratio across layers and avoids this trap. The example uses ds = 0.01, growth = 1.1, nk = 13 — total thickness ≈ 0.21, first cell ~30 % of the surface cell scale. The corner cells march cleanly.

For larger bodies (wall, wing) the tanh-clustered law works as expected — it's the geometric-scale ratio of ds to surface cell size that matters, not the law per se.

Generating Xall cc.par files

Once the geometry is verified in ParaView, convert each mesh to Xall inputs with examples/05-chimera-assembly/chimera_assembly.py — a direct continuation of this example that adds four steps: adjacency JSON sidecars, VTK MultiBlock exports, binary .grd files, and cc.par generation.

bash
# Generate everything headlessly (no viewer).
python examples/05-chimera-assembly/chimera_assembly.py

# Custom output directory.
python examples/05-chimera-assembly/chimera_assembly.py -o my_run

# Open the interactive viewer after generation.
python examples/05-chimera-assembly/chimera_assembly.py --view

CLI-only equivalent

The same pipeline runs through shore subcommands alone — see examples/05-chimera-assembly/chimera_assembly.sh. The Python and bash paths produce byte-identical assembly.{grd, cc.par, proc.input} outputs.

The script uses shore.io.cc_par.BlockMeta to assign per-block BC codes — demonstrating that write_cc_par's block_metadata argument (and the matching CLI --meta flag) give full per-block control:

python
from shore.io.cc_par import BlockMeta, write_cc_par
from shore.io.grd import write_grd

# Pack each mesh into a binary .grd — block order must match cc.par.
write_grd("wall.grd", [
    "examples_out/wall_sub0.geo", "examples_out/wall_sub1.geo",
    "examples_out/wall_sub2.geo", "examples_out/wall_sub3.geo",
    "examples_out/wall_cap_north.geo", "examples_out/wall_cap_south.geo",
])
write_grd("background.grd", [
    "examples_out/background_center.geo",
    "examples_out/background_sub_e.geo", "examples_out/background_sub_n.geo",
    "examples_out/background_sub_w.geo", "examples_out/background_sub_s.geo",
])

# Near-body: equatorial sub-blocks and caps all use chimera BC 40.
nb_meta = {
    "sub0":      BlockMeta(free_face_bc=40),
    "sub1":      BlockMeta(free_face_bc=40),
    "sub2":      BlockMeta(free_face_bc=40),
    "sub3":      BlockMeta(free_face_bc=40),
    "cap_north": BlockMeta(free_face_bc=40),
    "cap_south": BlockMeta(free_face_bc=40),
}

# Background: all blocks get far-field extrapolation BC -9.
bg_meta = {blk.label: BlockMeta(free_face_bc=-9) for blk in bg_blocks}

write_cc_par(nb_adj, "wall.cc.par", grd_filename="wall.grd", block_metadata=nb_meta)
write_cc_par(bg_adj, "background.cc.par", grd_filename="background.grd", block_metadata=bg_meta)

The same result from the CLI:

bash
# Binary grids
shore grd wall_sub0.geo wall_sub1.geo wall_sub2.geo wall_sub3.geo \
          wall_cap_north.geo wall_cap_south.geo \
          --output wall.grd

shore grd background_center.geo background_sub_e.geo background_sub_n.geo \
          background_sub_w.geo background_sub_s.geo \
          --output background.grd

# Chimera descriptors (block order must match the grd invocations above)
shore cc-par wall.adjacency.json wall.cc.par --grd wall.grd

shore cc-par background.adjacency.json background.cc.par \
  --grd background.grd --free-bc -9

Additional output files:

examples_out/
  wall.adjacency.json          # near-body adjacency sidecar
  background.adjacency.json    # background adjacency sidecar
  wall.vtm                     # near-body ParaView MultiBlock
  background.vtm               # background ParaView MultiBlock
  wall.grd                     # near-body Xall binary grid
  background.grd               # background Xall binary grid
  wall.cc.par                  # near-body Xall cc.par
  background.cc.par            # background Xall cc.par

See shore grd for the binary format reference and shore cc-par for the full CLI reference including the --meta flag format and BC code table.

What's deliberately not in this example

  • Hole cutting / fringe identification / donor search. The assembly is "side by side" — both meshes are written to disk in their world coordinates, the solver does the rest. SHORE has no in-tree assembly logic and there are no plans to add it.
  • Multi-body assemblies. Single body. The pattern would extend naturally to N near-body meshes plus 1 background, but the orchestrator script doesn't loop.
  • Adaptive overlap sizing. The background's inner_frac = 0.4 was chosen so the central-square block extends well clear of the near-body mesh's outer shell (4.0 vs. 0.7). For tighter overlap budgets, reduce inner_frac and re-check the geometry in ParaView.

See also

  • Chimera context — what overset meshing is and why SHORE produces these particular outputs.
  • Cubed-sphere topology — the near-body mesh.
  • Primitives — the flat-caps cylinder used for the background.
  • Config files — TOML schema for the mesh side; primitives use the parallel kind = "primitive" schema.
  • shore grd — pack per-block .geo files into the Xall binary .grd.
  • shore export — the CLI command that bundles multiple .geo files into a .vtm. The orchestrator script does the same in-process so all 11 blocks share one manifest.

Released under the MIT License.