Let's work on the clarity and usability of the API. For example:
# ---------------------------------------------------
# Top-level interface, which mirrors jax' autodiff, and e.g. np.gradient(), np.diff().
# Essentially, functions in here only chain coefficients.apply() with coefficients.jac()
# ---------------------------------------------------
pnfindiff.gradient(fx, axis, k=exp_quad(), acc=2)
pnfindiff.jac(fx, axis, acc)
pnfindiff.laplace(fx axis, acc)
# (...)
# ---------------------------------------------------
# Implementation of common derivatives.
# Every grad-and-vmap-ready kernel 'k' can be used. For example, those in the utils
# Points to `collocation`
# ---------------------------------------------------
dfx, base_unc = pnfindiff.coefficients.apply(f, weights=(w, sc), indices=idcs)
dfx, base_unc = pnfindiff.coefficients.apply_along_axis(f, weights=(w, sc), axis=1)
(w, sc), idcs, info = pnfindiff.coefficients.derivative(*, xs, k, acc=2)
(w, sc), idcs, info = pnfindiff.coefficients.derivative_higher(*, order, xs, k=None ,acc=2)
(w, sc), idcs, info = pnfindiff.coefficients.gradient(xs, k, acc=2)
(w, sc), idcs, info = pnfindiff.coefficients.laplace(xs, k, acc=2)
(w, sc), idcs, info = pnfindiff.coefficients.divergence(xs, k, acc=2)
(w, sc), idcs, info = pnfindiff.coefficients.jac(xs, k, acc=2)
(w, sc), idcs, info = pnfindiff.coefficients.approx(L, xs, k, *, order=2, acc=2) # the magic stuff. maybe move to collocation?
# ---------------------------------------------------
# Calibration API
# ---------------------------------------------------
output_scale = pnfindiff.stats.mle_output_scale(f, weights=(w, sc), info=info)
logpdf = pnfindiff.stats.logpdf(f, weights=(w, sc), info=info)
# ---------------------------------------------------
# API for 1d coefficients.
# This is like in FinDiff, but for PNFinDiff,
# I suppose such a module has only didactic purposes.
# Stack copies of coefficients for apply()-conformity.
# Points to `collocation`
# ---------------------------------------------------
(w, sc), idx, info = pnfindiff.coefficients_1d.forward(dx, deriv, acc)
(w, sc), idx, info = pnfindiff.coefficients_1d.center()
(w, sc), idx, info = pnfindiff.coefficients_1d.backward()
(w, sc), idx, info = pnfindiff.coefficients_1d.from_offset()
# ---------------------------------------------------
# Collocation implementations. This is where the magic happens, but only few will look at it.
# ---------------------------------------------------
pnfindiff.collocation.prepare_gram(x, xs, ks)
pnfindiff.collocation.unsymmetric(K, LK, LLK )
# ---------------------------------------------------
# Simplify our life via AD utility functions
# ---------------------------------------------------
pnfindiff.utils.autodiff.deriv_scalar(fun, **kwargs)
pnfindiff.utils.autodiff.div(fun, **kwargs)
pnfindiff.utils.autodiff.laplace(fun, **kwargs)
pnfindiff.utils.autodiff.compose(L1, L2)
# ---------------------------------------------------
# ... and kernel utility functions
# ---------------------------------------------------
pnfindiff.utils.kernel.batch_gram(k)
k, lk, llk = pnfindiff.utils.kernel.differentiate(k)
pnfindiff.utils.kernel.zoo.polynomial()
k, lk, llk = pnfindiff.utils.kernel.zoo.exp_quad()