Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 35 additions & 12 deletions fredipy/covariance.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,26 @@ class TwoSided:
New rules for user-defined operators can be defined by creating a class that
inherits from this class, adding the appropriate functions following the
naming scheme, and passing an instance to the model constructor.

Every combiner takes a ``derivative`` flag, which is threaded through
unchanged from :meth:`__call__`. It is ``False`` when the covariance matrix
itself is being assembled and ``True`` when ``kernel`` is a *derivative* of
the kernel with respect to one hyperparameter (see
``GaussianProcess.log_likelihood_grad``). Combiners that build a block by
plain linear application of ``kernel`` can ignore it; only pieces that add a
term which is *not* linear in ``kernel`` -- e.g. the constant analytic UV
tail moment in ``GaussLegendre_1D_log._uv_tail_correction``, whose
hyperparameter derivative is zero rather than itself -- need to act on it.
Custom combiners must accept the flag.
"""
def __init__(self):
pass

def __call__(
self,
kernel: Kernel,
constraints: List[LinearEquality]
constraints: List[LinearEquality],
derivative: bool = False
) -> np.ndarray:
rows = []
for c1 in constraints:
Expand All @@ -36,9 +48,9 @@ def __call__(
combiner12 = getattr(self, f"_{type(c1.op).__name__}_{type(c2.op).__name__}", None)
combiner21 = getattr(self, f"_{type(c2.op).__name__}_{type(c1.op).__name__}", None)
if combiner12:
entry = combiner12(c1, kernel, c2)
entry = combiner12(c1, kernel, c2, derivative=derivative)
elif combiner21:
entry = combiner21(c2, kernel, c1).T
entry = combiner21(c2, kernel, c1, derivative=derivative).T
else:
raise NotImplementedError(
f"No rule found to combine operators of types \
Expand All @@ -47,28 +59,39 @@ def __call__(
rows.append(np.concatenate(columns, axis=1))
return np.concatenate(rows)

def _Integral_Integral(self, c1, k, c2):
def _Integral_Integral(self, c1, k, c2, derivative: bool = False):
# NOTE: c1's integrator drives both sides of the cross block, so the two constraints
# must discretise omega compatibly. That is not checked here: integrators that apply an
# analytic UV tail (GaussLegendre_1D_log._uv_tail_correction) enforce matching nodes and
# weights downstream and raise NotImplementedError otherwise. Blocks (i, j) and (j, i)
# are built independently, each through its own left constraint's integrator, so any
# correction added downstream must be symmetric by construction.
# `derivative` is forwarded because that UV tail correction contains one term (T1 T2^T)
# that is independent of the kernel and must therefore vanish, not be reproduced, when
# k is dK/dtheta.
if c1 == c2:
return c1.op.integrator.doubleIntegrationSymmetric(c1, k)
return c1.op.integrator.doubleIntegrationSymmetric(c1, k, derivative=derivative)
else:
return c1.op.integrator.doubleIntegration(c1, k, c2)
return c1.op.integrator.doubleIntegration(c1, k, c2, derivative=derivative)

def _Identity_Identity(self, c1, k, c2):
def _Identity_Identity(self, c1, k, c2, derivative: bool = False):
return k(c1.x, c2.x)

def _Derivative_Derivative(self, c1, k, c2):
def _Derivative_Derivative(self, c1, k, c2, derivative: bool = False):
return k.d2K_dxdy(c1.x, c2.x)

def _Integral_Identity(self, c1, k, c2):
def _Integral_Identity(self, c1, k, c2, derivative: bool = False):
# No `derivative` handling needed: singleIntegration's analytic tail, T @ K(w_uv, x) /
# f_uv(w_uv), is linear in the kernel, so substituting dK/dtheta already differentiates it.
return c1.op.integrator.singleIntegration(c1, k, c2.x)

def _Integral_Derivative(self, c1, k, c2):
def _Integral_Derivative(self, c1, k, c2, derivative: bool = False):
return c1.op.integrator.singleIntegration(c1, k.dK_dy, c2.x)

def _Identity_Derivative(self, c1, k, c2):
def _Identity_Derivative(self, c1, k, c2, derivative: bool = False):
return k.dK_dy(c1.x, c2.x)

def _ConstMatrix_ConstMatrix(self, c1, k, c2):
def _ConstMatrix_ConstMatrix(self, c1, k, c2, derivative: bool = False):
return c1.op() @ k(c1.x, c2.x) @ c2.op().T


Expand Down
Loading