Meridional Flatten

Axisymmetric body-of-revolution -> 2D meridional finite-volume geometry.

A body of revolution stores the same (x, r) mesh once per theta plane: every theta plane of a true body of revolution is identical, so a 3D Block built by revolving a wall curve about the x-axis can be collapsed to a single 2D (x, r) node grid with no loss of information, then turned into 2D axisymmetric finite-volume geometry (cell volumes, face weights, face normals) suitable for a 2D solver that reproduces the 3D body of revolution exactly.

Deliberately distinct from two other, differently-scoped “flatten” concepts in this package, to avoid confusion:

  • plot3d.flatmesh (flatten_mesh/FlatMesh) flattens a multi-block 3D structured mesh into an unstructured finite-volume graph (full 3D hex cells, owner/neighbor adjacency across welded block boundaries). It has no axisymmetric/2D-collapse logic and does not require a body of revolution.

  • plot3d.glennht.plot3d_flatten_deck exports a GlennHT solver “flatten deck” file format; it is a boundary-condition/connectivity export, not a geometry module.

This module instead collapses a single, axisymmetric block’s redundant theta dimension and builds the resulting 2D mesh’s finite-volume metrics.

Everything here is axisymmetric: a cell’s true volume is 2*pi * integral(r dA) and a face’s true area is 2*pi * integral(r dL); the common 2*pi divides out of a finite-volume balance, so build_metrics() stores the per-radian quantities (vol = area * r_centroid, face weight s = length * r_midpoint). The axis is not a singularity: the j = 0 face weight is exactly zero because r = 0 there.

class plot3d.meridional_flatten.MeridionalMetrics(xc: ndarray, rc: ndarray, area: ndarray, vol: ndarray, si: ndarray, nix: ndarray, nir: ndarray, sj: ndarray, njx: ndarray, njr: ndarray)[source]

Finite-volume geometry of a flattened meridional mesh.

A typing.NamedTuple of arrays is automatically a valid JAX pytree, so downstream code can pass this straight through jax.jit with no registration, without this module depending on JAX itself.

Index convention for a node grid of shape (NI+1, NJ+1): cells (NI, NJ); j = 0 touches the axis, j = NJ-1 touches the wall. i-faces (NI+1, NJ) are constant-i faces with normal toward +i. j-faces (NI, NJ+1) are constant-j faces with normal toward +j.

__getnewargs__()

Return self as a plain tuple. Used by copy and pickle.

static __new__(_cls, xc: ndarray, rc: ndarray, area: ndarray, vol: ndarray, si: ndarray, nix: ndarray, nir: ndarray, sj: ndarray, njx: ndarray, njr: ndarray)

Create new instance of MeridionalMetrics(xc, rc, area, vol, si, nix, nir, sj, njx, njr)

area: ndarray

Alias for field number 2

nir: ndarray

Alias for field number 6

nix: ndarray

Alias for field number 5

njr: ndarray

Alias for field number 9

njx: ndarray

Alias for field number 8

rc: ndarray

Alias for field number 1

si: ndarray

Alias for field number 4

sj: ndarray

Alias for field number 7

vol: ndarray

Alias for field number 3

xc: ndarray

Alias for field number 0

plot3d.meridional_flatten.analytic_volume(x: ndarray, r_wall: ndarray) float[source]

Volume of the body of revolution, integral(pi R**2) dx.

Trapezoidal rule written out by hand (np.trapezoid is numpy >= 2.0 only and np.trapz has since been removed, so neither name is safe).

Parameters:
  • x (np.ndarray) – Axial stations.

  • r_wall (np.ndarray) – Wall radius at those stations.

Returns:

The reference volume enclosed_volume() should match.

Return type:

float

plot3d.meridional_flatten.axisymmetry_error(block: Block) float[source]

How far the block is from being a true body of revolution.

Compares the radius at every theta plane against the first plane. For a mesh that is a genuine body of revolution this is round-off sized, which is the license to keep only one plane.

Parameters:

block (Block) – Block to check.

Returns:

max |r(i,j,k) - r(i,j,0)|.

Return type:

float

plot3d.meridional_flatten.block_radius(block: Block) ndarray[source]

Radius about the x-axis at every node of a block.

Parameters:

block (Block) – Body-of-revolution block about the x-axis.

Returns:

r of shape (IMAX, JMAX, KMAX).

Return type:

np.ndarray

plot3d.meridional_flatten.build_metrics(x2d: ndarray, r2d: ndarray) MeridionalMetrics[source]

Build cell volumes, face weights and face normals from a node grid.

Parameters:
  • x2d (np.ndarray) – Node x coordinates, shape (NI+1, NJ+1).

  • r2d (np.ndarray) – Node r coordinates, shape (NI+1, NJ+1).

Returns:

Geometry arrays as plain numpy.

Return type:

MeridionalMetrics

plot3d.meridional_flatten.enclosed_volume(metrics: MeridionalMetrics) float[source]

Total duct volume implied by the metrics, 2*pi * sum(vol).

Parameters:

metrics (MeridionalMetrics) – Mesh metrics.

Returns:

Volume of the full body of revolution.

Return type:

float

plot3d.meridional_flatten.flatten_to_meridional(block: Block, k_index: int = 0) Tuple[ndarray, ndarray][source]

Extract one constant-theta slice as a 2D (x, r) node grid.

Parameters:
  • block (Block) – Body-of-revolution block about the x-axis.

  • k_index (int, optional) – Which theta plane to keep. Defaults to 0.

Returns:

(x2d, r2d), each of shape (IMAX, JMAX). Index j = 0 sits on the axis, j = JMAX-1 on the wall.

Return type:

Tuple[np.ndarray, np.ndarray]

plot3d.meridional_flatten.node_count_reduction(block: Block) Tuple[int, int, float][source]

Nodes before and after flattening.

Parameters:

block (Block) – The 3D block.

Returns:

(nodes_3d, nodes_2d, factor).

Return type:

Tuple[int, int, float]