Building functions¶
Declare the shape and structure of a function with arg, group, and @function. The derivative
wrappers, such as gradient, then create functions from that declaration. See the
functions guide for examples and the
derivatives guide for choosing a derivative.
Declared trees¶
Tree ¶
A pytree declaration whose leaves are Expr symbolically and NumPy arrays numerically.
A tree is its structure: only group introduces a tuple, so a one-leaf tree is the bare leaf
and never a one-element tuple. See arg for what that means at a call site.
symbols ¶
Create named input expressions with this tree's structure.
relabel ¶
Return the same structure with prefix added to every leaf name.
with_types ¶
Return the same structure with resolved leaf types.
resolved ¶
Check traced output types against this declaration and resolve inferred shapes.
is_symbolic ¶
Whether value has at least one leaf and every leaf is an Expr.
This is the leaf-kind half of Function.__call__'s dispatch. It deliberately ignores
structure so that a wrongly-shaped tree is reported by flatten_symbolic against the
declared names instead of being rejected here as a kind mismatch.
is_numerical ¶
Whether no leaf of value is an Expr.
The numerical side is the fallback: array-likes are coerced by flatten_numerical, so a
leaf only has to not be symbolic. Values with no leaves land here and are reported as a
structure error rather than as a mixed call.
flatten_symbolic ¶
Validate and flatten a symbolic value.
flatten_numerical ¶
Validate and flatten a numerical value.
unflatten ¶
Rebuild this tree's structure from flat values.
arg(name, shape=None, /, *, layout=None, pattern=None) ¶
Declare one named tensor, optionally leaving its shape to a call or trace.
A leaf is passed and returned as a bare value. Use group to declare tuple structure.
A TensorType sets the dtype and differentiability explicitly, and a stored pattern for a
sparse matrix. Dense leaves use row-major buffers by default. layout="column-major" orders
each matrix's values by column, keeping leading batch axes in row-major order. A sparse leaf's
layout is "csc" (the default), "csr", or the coordinate order of a
SparsityPattern. pattern=... leaves the pattern to the call, with a "csc" or
"csr" layout.
group ¶
group(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC]) -> Tree[tuple[SA, SB, SC], tuple[NA, NB, NC]]
group(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND]) -> Tree[tuple[SA, SB, SC, SD], tuple[NA, NB, NC, ND]]
group(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE]) -> Tree[tuple[SA, SB, SC, SD, SE], tuple[NA, NB, NC, ND, NE]]
group(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF]) -> Tree[tuple[SA, SB, SC, SD, SE, SF], tuple[NA, NB, NC, ND, NE, NF]]
group(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF], g: Tree[SG, NG]) -> Tree[tuple[SA, SB, SC, SD, SE, SF, SG], tuple[NA, NB, NC, ND, NE, NF, NG]]
The decorator¶
function ¶
function(*, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[], SO]], Function[tuple[], tuple[], SO, NO]]
function(a: Tree[SA, NA], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA], SO]], Function[tuple[SA], tuple[NA], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB], SO]], Function[tuple[SA, SB], tuple[NA, NB], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB, SC], SO]], Function[tuple[SA, SB, SC], tuple[NA, NB, NC], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB, SC, SD], SO]], Function[tuple[SA, SB, SC, SD], tuple[NA, NB, NC, ND], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB, SC, SD, SE], SO]], Function[tuple[SA, SB, SC, SD, SE], tuple[NA, NB, NC, ND, NE], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB, SC, SD, SE, SF], SO]], Function[tuple[SA, SB, SC, SD, SE, SF], tuple[NA, NB, NC, ND, NE, NF], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF], g: Tree[SG, NG], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB, SC, SD, SE, SF, SG], SO]], Function[tuple[SA, SB, SC, SD, SE, SF, SG], tuple[NA, NB, NC, ND, NE, NF, NG], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF], g: Tree[SG, NG], h: Tree[SH, NH], /, *, outputs: Tree[SO, NO], name: str | None = None) -> Callable[[Callable[[SA, SB, SC, SD, SE, SF, SG, SH], SO]], Function[tuple[SA, SB, SC, SD, SE, SF, SG, SH], tuple[NA, NB, NC, ND, NE, NF, NG, NH], SO, NO]]
function(a: Tree[SA, NA], b: Tree[SB, NB], /, *, name: str | None = None) -> _InferredOutputs[tuple[NA, NB], SA, SB]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], /, *, name: str | None = None) -> _InferredOutputs[tuple[NA, NB, NC], SA, SB, SC]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], /, *, name: str | None = None) -> _InferredOutputs[tuple[NA, NB, NC, ND], SA, SB, SC, SD]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], /, *, name: str | None = None) -> _InferredOutputs[tuple[NA, NB, NC, ND, NE], SA, SB, SC, SD, SE]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF], /, *, name: str | None = None) -> _InferredOutputs[tuple[NA, NB, NC, ND, NE, NF], SA, SB, SC, SD, SE, SF]
function(a: Tree[SA, NA], b: Tree[SB, NB], c: Tree[SC, NC], d: Tree[SD, ND], e: Tree[SE, NE], f: Tree[SF, NF], g: Tree[SG, NG], /, *, name: str | None = None) -> _InferredOutputs[tuple[NA, NB, NC, ND, NE, NF, NG], SA, SB, SC, SD, SE, SF, SG]
function(*inputs: Tree[Any, Any], outputs: Tree[Any, Any] | None = None, name: str | None = None) -> Callable[[Callable[..., Any]], Function[Any, Any, Any, Any]]
Trace a callable with one declaration per parameter and an optional output tree.
Source code in src/scaly/function/api.py
Derivative requests¶
Pass these request objects to Function.factory when you need several outputs or derivatives in
one function. For a single derivative, the named wrappers below are usually more convenient.
DerivSpec
dataclass
¶
Base class for typed requests passed to ConcreteFunction.factory.
Source code in src/scaly/function/concrete.py
Jac
dataclass
¶
Grad
dataclass
¶
Hess
dataclass
¶
Bases: DerivSpec
Dense Hessian of a scalar output.
Source code in src/scaly/function/factory.py
SpJac
dataclass
¶
Bases: DerivSpec
Sparse Jacobian, stored in layout.
Source code in src/scaly/function/factory.py
SpHess
dataclass
¶
Bases: DerivSpec
Sparse Hessian of a scalar output, one triangle of it stored in layout.
Source code in src/scaly/function/factory.py
Fwd
dataclass
¶
Bases: DerivSpec
Seeded forward mode: J(of, wrt) @ fwd:wrt.
Source code in src/scaly/function/factory.py
Adj
dataclass
¶
Bases: DerivSpec
Seeded reverse mode: J(of, wrt).T @ lam:of.
Source code in src/scaly/function/factory.py
Named wrappers¶
The common derivative functions accept either an Expr and an Expr input, or a Function, an output name, and an input name.
jacobian ¶
jacobian(source: Expr | Function | ConcreteFunction, *args: Expr | str, wrt: Expr | str | None = None, of: str | None = None, name: str | None = None) -> Expr | Function
Build a dense Jacobian for an Expr or a named Function output.
For an expression source, wrt must be an independent input.
Source code in src/scaly/function/api.py
gradient ¶
gradient(source: Expr | Function | ConcreteFunction, *args: Expr | str, wrt: Expr | str | None = None, of: str | None = None, name: str | None = None) -> Expr | Function
Build a gradient for an Expr or a named Function output.
For an expression source, wrt must be an independent input.
Source code in src/scaly/function/api.py
hessian ¶
hessian(source: Expr | Function | ConcreteFunction, *args: Expr | str, wrt: Expr | str | None = None, of: str | None = None, name: str | None = None) -> Expr | Function
Build a Hessian for an Expr or a named Function output.
For an expression source, wrt must be an independent input.
Source code in src/scaly/function/api.py
sparse_jacobian ¶
sparse_jacobian(source: Expr | Function | ConcreteFunction, *args: Expr | str, wrt: Expr | str | None = None, of: str | None = None, name: str | None = None, layout: Layout = 'csc') -> Expr | Function
Build a sparse Jacobian that stores only its structural nonzeros, for an Expr or a named Function output.
For an expression source, wrt must be an independent input, and the result is a sparse
tensor of shape (source.size, wrt.size). For a Function, the output is a sparse leaf whose
values are stored in layout: "csc" (the default), "csr", or a SparsityPattern
listing the same coordinates in the order the caller wants.
Source code in src/scaly/function/api.py
sparse_hessian ¶
sparse_hessian(source: Expr | Function | ConcreteFunction, *args: Expr | str, wrt: Expr | str | None = None, of: str | None = None, name: str | None = None, triangle: Triangle = 'full', layout: Layout = 'csc') -> Expr | Function
Build a sparse Hessian that stores only its structural nonzeros, for an Expr or a named scalar Function output.
For an expression source, wrt must be an independent input, and the result is a sparse
tensor of shape (wrt.size, wrt.size). triangle selects the stored entries: "full"
keeps the whole symmetric pattern, while "lower" and "upper" keep one triangle with the
diagonal. For a Function, the output is a sparse leaf whose values are stored in layout, as
for sparse_jacobian.
Source code in src/scaly/function/api.py
forward ¶
forward(fn: Function | ConcreteFunction, of: str | None = None, wrt: str | None = None, *, name: str | None = None) -> Function
Create a forward derivative with one seed parameter shaped and typed as wrt.
Source code in src/scaly/function/api.py
adjoint ¶
adjoint(fn: Function | ConcreteFunction, of: str | None = None, wrt: str | None = None, *, name: str | None = None) -> Function
Create an adjoint derivative with one seed parameter shaped and typed as of.
Source code in src/scaly/function/api.py
lagrangian_hessian ¶
lagrangian_hessian(fn: Function[tuple[*Ss,], tuple[*Ns,], SO, NO], wrt: str | None = None, *, name: str | None = None, aux_name: str = 'gamma') -> Function[tuple[*Ss, SO], tuple[*Ns, NO], Expr, Array]
Create the dense weighted Hessian with one multiplier parameter matching the output tree.
Source code in src/scaly/function/api.py
sparse_lagrangian_hessian ¶
sparse_lagrangian_hessian(fn: Function[tuple[*Ss,], tuple[*Ns,], SO, NO], wrt: str | None = None, *, name: str | None = None, aux_name: str = 'gamma', triangle: Triangle = 'full', layout: Layout = 'csc') -> Function[tuple[*Ss, SO], tuple[*Ns, NO], Expr, NumericalArray]
Create the sparse weighted Hessian with multipliers matching the output tree, stored in layout.
Source code in src/scaly/function/api.py
Custom derivative rules¶
custom_derivative ¶
custom_derivative(fn: Function[SI, NI, SO, NO] | ConcreteFunction[SI, NI, SO, NO], *, jvp: _Rule | None = None, fwd: _Rule | None = None, bwd: _Rule | None = None, sparsity: Callable[[str, str, tuple[int, int]], SparsityPattern | ndarray] | None = None) -> Function[SI, NI, SO, NO]
Return fn with derivative rules that replace differentiating its body.
jvp(*inputs, *directions) is the forward rule, the product of the Jacobian with a direction.
It takes fn's parameters and then one direction per parameter, and returns the change of
each output along that direction, in fn's output structure. Several directions apply the
rule to each direction in one mapped loop, and a direction the rule never reads is never computed.
fwd and bwd form the reverse rule, the product of output weights with the Jacobian.
fwd(*inputs) returns (outputs, residuals), and the outputs become fn's.
bwd(residuals, weights) takes one weight per output and returns one weighted derivative per
parameter of fn. A call computes its outputs and residuals together, so the reverse rule
reuses them instead of computing them again, and a second derivative still differentiates them.
A mode without a rule, forward or reverse, differentiates the body. Rules may be shape templates,
which take the shapes of each call of fn. sparsity(of, wrt, shape) gives the Jacobian pattern of output
of in input wrt, as a SparsityPattern or a boolean mask of shape, which is
(of.size, wrt.size). Without it, the pattern of a function with rules is dense.
Source code in src/scaly/function/api.py
Mapped calls¶
vmap ¶
vmap(callee: Function[SI, NI, SO, NO] | ConcreteFunction[SI, NI, SO, NO], length: int) -> Function[SI, NI, SO, NO]
Map independent calls over a leading axis, preserving parameter and output trees.
Each output leaf has shape (length, *callee_shape). Use broadcast for a whole
shared tensor and window for rank-1 overlapping slices of a declared width.
Affine views are read from their base in place. Other views preserve their element order.
broadcast ¶
window ¶
Mark rank-1 windows whose width comes from the callee's declared shape.
Iteration i reads at start + i * stride. For a shape hole, use a leading-axis
view or explicitly instantiate the callee to specify the window width.
Source code in src/scaly/function/sugar.py
Control flow¶
scan(body, init, xs=(), *, length, trip=None, index=False, reverse=False, in_place=(), max_trajectory=None) ¶
Run body for length steps, threading a carry and stacking per-step outputs.
body takes one parameter per entry of init (one carry when init is not a tuple),
then the step number as an int64 scalar when index is true, then one parameter per entry of
xs. It returns the new carries first, each with its input's shape and dtype, followed by
any per-step outputs. Each entry of xs is sliced per step as vmap slices its arguments:
a leading axis of length, broadcast for a value shared by every step, or window.
Returns (carry, ys): the final carry, shaped like init, and a tuple of the per-step
outputs, each with a leading axis of length. trip, an int64 scalar, runs only the first
max(0, min(trip, length)) steps. The unused entries of the stacked outputs are zero. With reverse
the steps run from the last to the first, and step k still reads slice k and writes
output k. in_place names carry positions or input names that lowering must prove safe
to update in place. Forward derivatives follow the same steps
as the primal loop. Reverse derivatives store the entering carries and traverse the steps
backwards. max_trajectory limits the number of stored carry elements for this loop,
using the static length even when trip is smaller. None leaves it unlimited.
Exceeding the limit raises ValueError and suggests sc.custom_derivative.
Derivative loops do not inherit the limit.
while_loop ¶
while_loop(cond: Any, body: Any, init: Any, *, max_iter: int, params: tuple[Any, ...] = (), index: bool = False, max_trajectory: int | None = None) -> tuple[Any, Expr]
Apply body to a carry while cond holds, at most max_iter times.
body and cond take the same parameters: one per entry of init (one carry when
init is not a tuple), then the step number as an int64 scalar when index is true, then
one per entry of params, which every step reads whole. cond returns a bool scalar and is
evaluated before each step. body returns the new carries, each with its input's shape and
dtype. Returns (carry, count): the final carry, shaped like init, and the number of
steps taken as an int64 scalar. Forward derivatives take the same steps, with the condition
evaluated on primal carries. Reverse derivatives traverse exactly the steps taken backwards.
max_trajectory limits stored carry elements using max_iter times the stored carry
size, even when the loop exits early. Each nested loop has its own limit. None leaves
storage unlimited. Exceeding the limit raises ValueError and suggests sc.custom_derivative.
Derivative loops do not inherit the limit.
cond ¶
Call the branch selected by a scalar boolean or integer, with operands as its inputs.
False selects branch zero and True selects branch one. A boolean requires exactly two
branches. Integer selectors clamp to [0, len(branches) - 1]. Only the selected branch
runs, including its prints. Branches must have matching input and output structures,
shapes, dtypes and stored patterns. Results follow the first branch's output tree.
The selector has no derivative. Derivatives follow the selected branch. Structural
sparsity is the union of the branches.