jno.numpy Reference
This page covers the full jno.numpy (alias jnn) utility API: math functions, reductions, array operations, symbolic arithmetic, and constants.
Mathematical Functions
Trigonometric
jnn.sin(x), jnn.cos(x), jnn.tan(x)
jnn.arcsin(x), jnn.arccos(x), jnn.arctan(x)
jnn.arctan2(y, x) # alias: jnn.atan2
Hyperbolic
Exponential / Logarithm
Power / Root
Absolute / Rounding
Constants
Reduction Operations
jnn.sum(x)
jnn.mean(x)
jnn.std(x)
jnn.var(x)
jnn.min(x)
jnn.max(x)
jnn.median(x)
jnn.prod(x)
jnn.norm(x, ord=None, axis=None)
All support axis= and keepdims= keyword arguments.
Reducing over a named axis
axis= also accepts a coordinate Variable — the same object domain.variable(...) returns —
instead of an integer index into the (B, T, N, D) context layout:
x, y, _ = dom.variable("interior")
err = jnn.mean((net(_f) - _u) ** 2, axis=(x, y)) # reduce the grid, keep the sample axis
The axis is resolved from the coordinate's own extent when the array exists, so it is
order-independent (axis=(y, x) gives the same reduction) and never depends on argument position.
Where several axes share that extent — a square grid, which is the common case — the whole grid
block is located and the coordinate indexed within it.
Requires a structured grid, and fails loud when ambiguous
Defined only on a domain with a grid (Geometries.poseidon, Geometries.equi_distant_rect, or a
structured mesh). On an unstructured domain the points live on one flat axis that no coordinate
names — reduce it with axis=None.
A layout where the grid block occurs more than once (e.g. shape (128, 128, 128) on a
128×128 grid) raises, naming both candidate axes and both block offsets, rather than guessing.
A transposed layout on a square grid cannot be detected — reshape it instead.
Reductions with no axis= are not registered as reductions
Passing an explicit axis (jnn.mean(r, axis=0)) marks the node as a reduction, which is what
lets the ENGD Gram builder and residual-based resampling unwrap it to reach the vector
residual underneath. Omitting the axis (jnn.mean(r)) does not: a bare axis=None is
indistinguishable from the jno.fn(f, args) escape hatch, which uses the same default.
Only matters if you write a custom loss with a functional reduction. The .mse / .mae
properties always register correctly, so the common path is unaffected.
Reduction Properties on Placeholders
Every Placeholder expression exposes reduction properties that return scalar nodes suitable as loss terms or trackers:
expr.mse # mean(expr²) — most common loss term
expr.mae # mean(|expr|)
expr.mean # mean(expr)
expr.sum # sum(expr)
expr.max # max(expr)
expr.min # min(expr)
expr.std # std(expr)
Example:
Array Operations
jnn.concat([x, y], axis=-1) # concatenate along last axis (default)
jnn.concatenate([x, y]) # alias for concat
jnn.stack([x, y], axis=0) # stack along new axis
jnn.reshape(x, shape)
jnn.squeeze(x, axis=None)
jnn.expand_dims(x, axis)
jnn.transpose(x, axes=None)
Comparison / Conditional
Comparison operators are also available as methods on Placeholder objects:
x > 0.5 # FunctionCall(greater, [x, Literal(0.5)])
x.equal(y) # element-wise equality (traced)
x.not_equal(y)
Linear Algebra
jnn.dot(x, y)
jnn.matmul(x, y)
jnn.cross(x, y)
# Matrix multiply on Placeholder: x @ A
result = x @ A
Second-order tensors
All act on the last two axes and broadcast over any leading (e.g. quadrature) axes.
jnn.inv(A) # inverse
jnn.det(A) # determinant → scalar per leading index
jnn.eigvalsh(A) # ascending eigenvalues of a symmetric tensor → principal values
Symmetric matrix functions — f(A) = V diag(f(λ)) Vᵀ via eigh. Their
gradient stays finite at repeated eigenvalues (equal principal stretches —
the common case in finite-strain plasticity) using the Daleckiĭ–Kreĭn / Löwner
divided-difference form (Higham, Functions of Matrices, SIAM 2008, Thm 3.11).
Distinct from the element-wise scalar log / exp / sqrt above.
jnn.logm(A) # matrix logarithm E = 0.5 * jnn.logm(jnn.matmul(F.T, F)) # Hencky strain
jnn.expm(A) # matrix exponential (inverse of logm on SPD)
jnn.sqrtm(A) # matrix square root U = jnn.sqrtm(jnn.matmul(F.T, F)) # right stretch
These are the tensor primitives a finite-strain constitutive update needs:
inv for the multiplicative split F = Fₑ·Fₚ, det for the volumetric part
J = det F, and logm/expm for a log-strain (Hencky) return map.
Symbolic Arithmetic
Placeholders support standard Python arithmetic, enabling natural PDE notation:
Constants Namespace
Load constant values from a file or dict and use them symbolically in expressions:
C = jnn.constant("C", {
"k": 1.5,
"rho": 2700,
"cp": 900,
"physics": {"g": 9.81, "nu": 1.5e-5},
})
# Use in constraints
pde = -C.k * jnn.laplacian(u, [x, y]) - C.rho * jnn.grad(u, t)
# Load from file
C = jnn.constant("C", "params.json") # JSON
C = jnn.constant("C", "params.yaml") # YAML
C = jnn.constant("C", "params.toml") # TOML
C = jnn.constant("C", "data.npz") # NumPy npz
View Factor Operator (Radiation)
For radiation boundary conditions the domain can compute a view-factor matrix. Use jnn.view_factor to create an operator that applies it symbolically:
# Get view factor matrix from domain
xb, yb, tb, nx, ny, VF = domain.variable("boundary", normals=True, view_factor=True)
# Wrap as a symbolic linear operator
VF_op = jnn.view_factor(VF)
# Apply: radiative heat flux received by each boundary point
q_inc = VF_op @ q_emitted # F @ q (matrix-vector product)
q_inc = q_emitted @ VF_op # q @ F
# Solve (I - αF)x = rhs
x = VF_op.solve(rhs, alpha)