AGILE supports five coordinate settings: Cartesian (x, y, z), spherical
(r, θ, φ), cylindrical (r, z, φ), and the two logarithmic-radius variants
logSpherical and logCylindrical (the same two curvilinear systems with a
radial mesh that is uniform in ln(r) rather than in r -- see Logarithmically
stretched radius below). All are always 3D, so a
curvilinear run is the full 3D system, never an axisymmetric or 1D/2D
reduction.
The coordinate system is a compile-time choice: the finite-volume
kernels are specialized for it, so that the (much cheaper) Cartesian kernel
stays free of curvilinear bookkeeping. It is selected via geometry in
&meshlist of the parameter file:
&meshlist
geometry = 'spherical' ! or 'cylindrical', 'logSpherical',
! 'logCylindrical', or 'Cartesian' (the default)
/
logSpherical/logCylindrical are a spherical/cylindrical build (GEOM,
and the runtime coordinate, stay 'spherical'/'cylindrical') plus a
LOG_RADIUS flag; every geometry-specific branch stays correct untouched and
only the radial spacing differs.
Conventions to know when setting up a curvilinear case:
xprobmin2 = 0.125d0, xprobmax2 = 0.375d0 places θ between π/4 and
3π/4. For cylindrical, only the φ bound (xprobmin3/xprobmax3) gets
this treatment; the z bound (xprobmin2/xprobmax2) is a plain length,
exactly like r.logSpherical/logCylindrical, xprobmin1/xprobmax1 are
physical radii in the parameter file and are converted to the logical
ln-radius coordinate by read_par_files. A case's mod_usr.fpp that reads
those bounds for a physical radius must map them back with r_of_s from
mod_geometry (not by hand -- the map is only a plain exponential when
log_r0 = 0).ps(igrid)%x are the volume barycentre of the cell, not
the midpoint of its faces -- so x ± dx/2 is not a cell face. See Cell
positions are volume barycentres below.ps(igrid)%x -- which every case's mod_usr.fpp reads -- holds the volume
barycentre of the cell. A cell average equals the point value at the
barycentre to second order (the linear term of the Taylor expansion
integrates to zero only about that point), so the barycentre is where the
state, the initial condition and any position-dependent source term belong.
Only the directions with a non-uniform volume weight are affected: r in both
systems, and θ in spherical. φ in both, and z in cylindrical, carry uniform
weight, so their barycentres are their midpoints. The offset is about 1% of a
cell width radially on the test grids and grows to dθ/6 -- a sixth of a cell,
pushed away from the axis -- for the first polar cell off θ = 0.
θ in spherical. φ (both) and cylindrical z carry uniform weight, so their barycentres already were their midpoints. fill_geometry_device evaluates the offset forms with the common factor cancelled analytically, because computing an O(h²) quantity as the difference of two O(1) ones would throw away most of the mantissa on a fine grid.
fill_geometry_device is written face-first: the radial-cell macro produces
the two faces, the midpoint and the extent, and every area and volume is then
written in terms of those so the expressions stay algebraically exact for any
face pair. Three places in the tree that assumed x ± dx/2 was a face -- the
face coordinates handed to the Riemann solver in mod_finite_volume, the VTU
corner grid in calc_x, and fill_geometry_device itself -- rebuild the face
from rnode instead. bgeo%ds deliberately stays on the cell midpoint: it is
a geometric extent that setdt turns into the CFL length, and following the
barycentre there would quietly relax the timestep.
AGILE discretizes the generic conservation law
(see Physics modules and equations) with a finite-volume
update that divides the net flux through a cell's faces by the cell volume.
In spherical or cylindrical coordinates the face areas and cell volume
depend on position (e.g. a spherical shell's outer face is larger than its
inner face), so this ratio is what actually implements the curvilinear
divergence for scalar quantities -- no extra term is needed for the mass,
energy, or (for ffhd) the field-aligned quantities, since these are
advected as genuine scalars and Gauss's theorem then guarantees the finite
volume update alone reproduces correctly in
any coordinate system.
Momentum (and, for mhd, the magnetic field) is different: it is a
vector quantity, and its flux is a tensor built from a position-dependent
(r, θ, φ) or (r, z, φ) basis. Writing the vector Laplacian/divergence out in
curvilinear coordinates leaves over extra terms beyond the flux-divergence
that the finite-volume update captures -- physically, these account for how
the basis vectors themselves change from one face of a cell to the other.
AGILE adds these leftover terms explicitly as a per-cell geometric source,
in the addsource_geometry routine of each physics module
(src/{hd,mhd,srhd,ffhd}/mod_*_templates.fpp); a physics module that does
not implement one aborts at startup rather than silently omitting these
terms. The explicit expressions below are exactly what these routines
evaluate (rewritten into physical notation matching
Physics modules and equations; see the source files for
the exact discretization, in particular how the isotropic-pressure-like
prefactor is evaluated from the actual discrete face areas rather than
plugging in the continuous coordinate factor directly, so that a uniform
pressure produces no net force to the same precision the code otherwise
resolves -- for cylindrical this recovers the continuous prefactor exactly,
since the radial face area of a cylindrical cell is linear in r). Because
those discrete factors are the exact volume averages of the continuous
2/r, cot(θ)/r (spherical) or 1/r (cylindrical) prefactors for any face
pair, no curvature term depends on where in the cell the stored position
sits, and addsource_geometry takes no position argument -- which is what
keeps it correct now that ps%x is the volume barycentre rather than the
face midpoint.
The equations below give the additional source term
that a curvilinear run adds to the corresponding equation from
Physics modules and equations; everywhere else the
equations are unchanged. ffhd needs none, for any geometry (see above), so
it is omitted below beyond noting .
With the total (thermal + magnetic) pressure :
where ψ is the GLM field (see Divergence B source treatments); ψ itself has no geometric source.
Structurally identical to the hd case, with the momentum density
(rather than ) and
using the spatial four-velocity components
(the primitive momentum-slot variables; ),
so that :
with as in Physics modules and equations.
Only the / (and, for mhd, /)
components pick up a geometric source; / do not, since a
cylindrical volume element's z-extent does not depend on r.
With :
As for spherical, with and :
geometry = 'logSpherical' and geometry = 'logCylindrical' place the
radial mesh uniformly in ln(r + r0) rather than in r. The offset r0 is the
log_r0 entry of &meshlist and defaults to zero, the plain ln(r): the cell
width then grows in proportion to r, so the relative resolution dr/r is
constant -- the one stretching astrophysics usually wants, and the only one
supported.
The design principle is that taking the logical radial coordinate to be
ξ = ln(r + r0) leaves the mesh uniform in ξ, which is exactly what the
geometry kernel already assumes -- an analytic function of the block corner
and a constant spacing. The tree, the AMR level ladder, rnode, prolongation,
coarsening and the ghost exchange all keep working unchanged in ξ; only the
step from logical faces to physical ones takes an exp. It needs none of
upstream MPI-AMRVAC's general stretched-grid machinery (qstretch, dxfirst,
per-level stretch tables) -- which AGILE has removed -- and refinement and log
stretching commute for free.
A positive log_r0 moves the singularity of the map from r = 0 out to
r = −r0, making the mesh genuinely uniform (cell width r0·dξ) for r ≪ r0 and
logarithmic for r ≫ r0. That is what lets a stretched radial grid reach
r = 0: with the default log_r0 = 0 a log-radial grid can never reach the
axis, so xprobmin1 ≤ 0 is rejected unless log_r0 > 0. logCylindrical
with log_r0 > 0 therefore supports the cylindrical axis; logSpherical
still has no usable origin (nothing fills the ghost cells across r = 0).
log_r0 = 0 the map is plain ln(r) and cannot reach r = 0. A positive log_r0 moves the singularity out to r = −r0, so the mesh is genuinely uniform near the axis and logarithmic far out. Three properties of that form are deliberate: the pivot is the axis (s = 0 is r = 0 exactly, so rnode's corner is exactly zero); the map is odd, so the pole copy fills a ghost cell from an interior cell of mirrored volume; and the odd branch is confined to the ghost layer, so the physical domain still uses the cheaper log_ra·exp(s) + log_rb and a plain log_r0 = 0 build is bit-for-bit unchanged.
Two limitations of the stretching:
log_r0 helps here, since the mesh
is genuinely uniform in the region next to the axis where the stencil
assumption would otherwise be worst.log_r0 chosen for
near-axis resolution buys a CFL restriction on top of the one the polar axis
already imposes.A spherical domain may span the full θ from 0 to π, and a cylindrical one may
start at r = 0. The axis is not a boundary: a ghost cell just past the axis at
azimuth φ is the interior cell at φ + π, so it is filled from the block half
a revolution away (a "pi-periodic" treatment) rather than by a boundary
condition. Setting it up takes three things in &meshlist/&boundlist, and
check_pole_setup stops the run with a specific message if any is missing:
'pole' on the axis face(s) -- typeboundary_min2/max2 in spherical,
typeboundary_min1 in cylindrical; it is all-or-nothing per face.xprobmin3 = 0, xprobmax3 = 1 in
2π units, periodic at both φ faces).The sign rule is the same in both systems: the component along the mirrored direction flips, the φ component flips, everything else is symmetric. Note that this makes the radial component odd at the cylindrical axis (m_r, B_r as well as m_φ, B_φ) -- upstream MPI-AMRVAC marks only the φ component antisymmetric there, which is wrong.
The polar axis is supported for hd, mhd, srhd and ffhd. Two things to
know:
setdt gives a severe CFL restriction. No axis filtering or ring averaging
is implemented; keep pole cases at modest resolution.mean(w). The pole test cases therefore assert on the ghost cells
directly against the analytic state at it = 0.fix_conserve is
disabled for all geometries in src/mod_advance.fpp.stretch_dim that varied the
cell spacing within a block could not be expressed by the analytic metric
derivation, so upstream MPI-AMRVAC's stretched-grid machinery was removed and
the stretch_dim / qstretch_baselevel / nstretchedblocks_baselevel /
stretch_uncentered keys are no longer read from &meshlist. The
logarithmic radius above is the one supported non-uniform mesh, precisely
because it is still uniform in ξ = ln(r + r0).divvector/curlvector in src/mod_geometry.fpp still assume a face lies
midway between two cell centres, which is untrue for the volume barycentre.
Neither routine is on a path this fork currently exercises.