Skip to content

Commit 46ced70

Browse files
committed
replaces kernel_construction.py with covariance.py containing new extensible classes for combining operators and kernels into covariance matrices. user extensions for combining custom operators and kernels or for defining new combination rules can now be achieved by inheriting from covariance.TwoSided/OneSided and passing instances to the model constructor.
1 parent 84488a4 commit 46ced70

3 files changed

Lines changed: 128 additions & 93 deletions

File tree

fredipy/covariance.py

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
from __future__ import annotations
2+
from typing import TYPE_CHECKING, List
3+
if TYPE_CHECKING:
4+
from .kernels import Kernel
5+
from .constraints import LinearEquality
6+
7+
from .operators import Integral, Identity, ConstMatrix, Derivative
8+
import numpy as np
9+
10+
11+
class TwoSided:
12+
"""
13+
Construct the symmetric block matrix of covariances of all linear equality
14+
constraints by sandwiching the kernel between all combinations of the
15+
associated operators.
16+
17+
Rules for combining operators are defined by functions named with the
18+
appropriate types, e.g. _Integral_Identity() to sandwich the kernel between
19+
an integral operator and the identity operator.
20+
21+
New rules for user-defined operators can be defined by creating a class that
22+
inherits from this class, adding the appropriate functions following the
23+
naming scheme, and passing an instance to the model constructor.
24+
"""
25+
def __init__(self):
26+
pass
27+
28+
def __call__(
29+
self,
30+
kernel: Kernel,
31+
constraints: List[LinearEquality]
32+
) -> np.ndarray:
33+
rows = []
34+
for c1 in constraints:
35+
columns = []
36+
for c2 in constraints:
37+
try:
38+
entry = getattr(self, f"_{type(c1.op).__name__}_{type(c2.op).__name__}")(c1, kernel, c2)
39+
except AttributeError:
40+
try:
41+
entry = getattr(self, f"_{type(c2.op).__name__}_{type(c1.op).__name__}")(c2, kernel, c1).T
42+
except AttributeError:
43+
raise NotImplementedError(
44+
f"No rule found to combine operators of types {type(c1.op).__name__} and {type(c2.op).__name__} with kernel.")
45+
columns.append(entry)
46+
rows.append(np.concatenate(columns, axis=1))
47+
return np.concatenate(rows)
48+
49+
def _Integral_Integral(self, c1, k, c2):
50+
if c1 == c2:
51+
return c1.op.integrator.doubleIntegrationSymmetric(c1, k)
52+
else:
53+
return c1.op.integrator.doubleIntegration(c1, k, c2)
54+
55+
def _Identity_Identity(self, c1, k, c2):
56+
return k(c1.x, c2.x)
57+
58+
def _Derivative_Derivative(self, c1, k, c2):
59+
return k.d2K_dxdy(c1.x, c2.x)
60+
61+
def _Integral_Identity(self, c1, k, c2):
62+
return c1.op.integrator.singleIntegration(c1, k, c2.x)
63+
64+
def _Integral_Derivative(self, c1, k, c2):
65+
return c1.op.integrator.singleIntegration(c1, k.dK_dy, c2.x)
66+
67+
def _Identity_Derivative(self, c1, k, c2):
68+
return k.dK_dy(c1.x, c2.x)
69+
70+
def _ConstMatrix_ConstMatrix(self, c1, k, c2):
71+
return c1.op() @ k(c1.x, c2.x) @ c2.op().T
72+
73+
74+
class OneSided:
75+
"""
76+
Construct the asymmetric block matrix of covariances of the prediction
77+
points and all linear equality constraints by left-application of the
78+
associated operators to the kernel.
79+
80+
Rules for combining operators with the kernel are defined by functions
81+
named with the appropriate type, e.g. _Integral() to apply an integral
82+
operator.
83+
84+
New rules for user-defined operators can be defined by creating a class that
85+
inherits from this class, adding the appropriate functions following the
86+
naming scheme, and passing an instance to the model constructor.
87+
"""
88+
def __init__(self):
89+
pass
90+
91+
def __call__(
92+
self,
93+
kernel: Kernel,
94+
constraints: List[LinearEquality],
95+
w: np.ndarray
96+
) -> np.ndarray:
97+
entries = []
98+
for c in constraints:
99+
try:
100+
entry = getattr(self, f"_{type(c.op).__name__}")(c, kernel, w)
101+
except AttributeError:
102+
raise NotImplementedError(
103+
f"No rule found to combine operator of type {type(c.op).__name__} with kernel.")
104+
entries.append(entry)
105+
return np.concatenate(entries)
106+
107+
def _Integral(self, c, k, w):
108+
return c.op.integrator.singleIntegration(c, k, w)
109+
110+
def _Identity(self, c, k, w):
111+
return k(c.x, w)
112+
113+
def _Derivative(self, c, k, w):
114+
return k.dK_dx(c.x, w)
115+
116+
def _ConstMatrix(self, c, k, w):
117+
return c.op()

fredipy/kernel_construction.py

Lines changed: 0 additions & 88 deletions
This file was deleted.

fredipy/models.py

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import numpy as np
88
import scipy as sp
99

10-
from .kernel_construction import construct_OpKer, construct_OpKerOp
10+
from .covariance import TwoSided, OneSided
1111
from .util import make_column_vector
1212

1313

@@ -42,10 +42,16 @@ class GaussianProcess(Model):
4242
def __init__(
4343
self,
4444
kernel: Kernel,
45-
constraints: List[LinearEquality]
45+
constraints: List[LinearEquality],
46+
OpKerOp: TwoSided =TwoSided(),
47+
OpKer: OneSided =OneSided(),
4648
) -> None:
4749
self.kernel = kernel
4850
self.constraints = constraints
51+
52+
self.OpKerOp = OpKerOp
53+
self.OpKer = OpKer
54+
4955
self.y = np.concatenate([c.y for c in self.constraints])
5056
self.dy = np.concatenate([c.dy for c in self.constraints])
5157

@@ -169,7 +175,7 @@ def log_likelihood_grad(self) -> List[float]:
169175
kernel_grad = self.kernel.params_gradient()
170176

171177
for i in range(self.kernel.dim):
172-
OpKer_grad = construct_OpKerOp(kernel_grad[i], self.constraints)
178+
OpKer_grad = self.OpKerOp(kernel_grad[i], self.constraints)
173179
loglik_grad[i] = 0.5 * np.trace((alpha @ alpha.T - K_inv) @ OpKer_grad)
174180

175181
return loglik_grad
@@ -194,7 +200,7 @@ def _empty_cache(self) -> None:
194200

195201
def _maybe_prepare_posterior(self) -> None:
196202
if not self._posterior_cache:
197-
OpKerOp = construct_OpKerOp(
203+
OpKerOp = self.OpKerOp(
198204
self.kernel, self.constraints)
199205
OpKerOp_cholesky = np.linalg.cholesky(
200206
OpKerOp + self.dy**2 * np.eye(OpKerOp.shape[0]))
@@ -213,7 +219,7 @@ def _maybe_prepare_inference(
213219
w_pred: np.ndarray,
214220
) -> None:
215221
if not self._inference_cache:
216-
OpKer = construct_OpKer(
222+
OpKer = self.OpKer(
217223
self.kernel, self.constraints, w_pred)
218224
self._inference_cache = {
219225
'OpKer': OpKer}

0 commit comments

Comments
 (0)