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.
Source code in src/scaly/function/tree.py
28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 | |
symbols ¶
relabel ¶
with_types ¶
resolved ¶
Check traced output types against this declaration and resolve inferred shapes.
Source code in src/scaly/function/tree.py
index ¶
Return the flat index for name, or raise with the declared choices.
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.
Source code in src/scaly/function/tree.py
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.
Source code in src/scaly/function/tree.py
flatten_symbolic ¶
flatten_numerical ¶
arg ¶
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.
Source code in src/scaly/function/tree.py
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
¶
Bases: DerivSpec
Dense Jacobian d of / d wrt.
Source code in src/scaly/function/factory.py
Grad
dataclass
¶
Bases: DerivSpec
Gradient of a scalar output.
Source code in src/scaly/function/factory.py
Hess
dataclass
¶
Bases: DerivSpec
Dense Hessian of a scalar output.
Source code in src/scaly/function/factory.py
SpJac
dataclass
¶
Bases: DerivSpec
Compact nonzero Jacobian values, with the sparsity that indexes them.
Source code in src/scaly/function/factory.py
SpHess
dataclass
¶
Bases: DerivSpec
Compact nonzero Hessian values, with the sparsity that indexes them.
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) -> SparseJacobian | Function
Build compact nonzero Jacobian values 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_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') -> SparseJacobian | Function
Build compact nonzero Hessian values for an Expr or a named Function output.
For an expression source, wrt must be an independent input.
triangle selects the symmetric pattern returned by the sparse Hessian: "full" keeps
every entry, while "lower" and "upper" keep one triangle in the full pattern's order.
Source code in src/scaly/function/api.py
forward ¶
forward(fn: Function[tuple[*Ss,], tuple[*Ns,], SO, NO], of: str | None = None, wrt: str | None = None, *, name: str | None = None) -> Function[tuple[*Ss, Expr], tuple[*Ns, Array], Expr, Array]
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[tuple[*Ss,], tuple[*Ns,], SO, NO], of: str | None = None, wrt: str | None = None, *, name: str | None = None) -> Function[tuple[*Ss, Expr], tuple[*Ns, Array], Expr, Array]
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') -> Function[tuple[*Ss, SO], tuple[*Ns, NO], Expr, Array]
Create compact weighted Hessian values with multipliers matching the output tree.
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.
Source code in src/scaly/function/sugar.py
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.