Skip to content

Commit fb74b17

Browse files
committed
gp: add gp subpackage
1 parent 5d3ba4f commit fb74b17

File tree

3 files changed

+632
-0
lines changed

3 files changed

+632
-0
lines changed

UncertainSCI/gp/__init__.py

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
"""Gaussian process methods."""
2+
3+
__version__ = "0.0.1"
4+
5+
6+
from . import wrapper
7+
from . import kernel
8+
9+
import numpy as np
10+
11+
NUGGET = 1e-6
12+
13+
14+
class ScalarGaussianProcess():
15+
mu: wrapper.ScalarFunction
16+
k: kernel.ScalarKernel
17+
x_obs: np.ndarray | None = None
18+
y_obs: np.ndarray | None = None
19+
sigma: np.ndarray | None = None
20+
inv: np.ndarray | None = None
21+
22+
def __init__(self,
23+
mu: wrapper.ScalarFunction,
24+
k: kernel.ScalarKernel,
25+
discretized: bool | np.ndarray = False):
26+
if discretized is not False:
27+
raise NotImplementedError
28+
29+
self.mu = mu
30+
self.k = k
31+
32+
if k.dim != mu.dim:
33+
raise ValueError('Dims of mu and k do not match!')
34+
35+
def condition(self, x: np.ndarray,
36+
y: np.ndarray,
37+
sigma: np.ndarray | float):
38+
sigma = np.asarray(sigma)
39+
if sigma.ndim > 1:
40+
raise ValueError('Sigma has too many dimensions!')
41+
elif sigma.ndim == 1 and len(sigma) != len(x):
42+
raise ValueError('Sigma has the wrong size!')
43+
else:
44+
sigma = sigma * np.ones(len(x))
45+
46+
self.x_obs = x
47+
self.y_obs = y
48+
self.sigma = sigma
49+
self.inv = np.linalg.inv(self.k(x) + sigma * np.eye(len(x)))
50+
51+
def mu_posterior(self, x: np.ndarray):
52+
if (self.x_obs is None) or (self.y_obs is None) or (self.inv is None):
53+
raise ValueError('GP not conditioned: call ScalarGaussianProcess.'
54+
'condition with observations before computing '
55+
'posterior!')
56+
return self.mu(x) + self.k(x, self.x_obs) @ self.inv @ \
57+
(self.y_obs - self.mu(self.x_obs))
58+
59+
def k_posterior(self, x1: np.ndarray, x2: np.ndarray | None = None):
60+
if (self.x_obs is None) or (self.y_obs is None) or (self.inv is None):
61+
raise ValueError('GP not conditioned: call ScalarGaussianProcess.'
62+
'condition with observations before computing '
63+
'posterior!')
64+
return (self.k(x1) if x2 is None else self.k(x1, x2)) - \
65+
self.k(x1, self.x_obs) @ self.inv @ \
66+
(self.k(self.x_obs, x1) if x2 is None else self.k(self.x_obs, x2))
67+
68+
def prior(self, x: np.ndarray, n: int = 1) -> np.ndarray:
69+
ell = np.linalg.cholesky(self.k(x) + NUGGET * np.eye(len(x)))
70+
return (self.mu(x)[:, None] if n > 1 else self.mu(x)) + \
71+
ell @ np.random.normal(0, 1, (len(x), n) if n > 1 else len(x))
72+
73+
def posterior(self, x: np.ndarray, n: int = 1) -> tuple[np.ndarray, np.ndarray]:
74+
ell = np.linalg.cholesky(self.k_posterior(x) + NUGGET * np.eye(len(x)))
75+
return (self.mu_posterior(x)[:, None] if n > 1 else self.mu_posterior(x)) + \
76+
ell @ np.random.normal(0, 1, (len(x), n) if n > 1 else len(x))
77+
78+
79+
class VectorGaussianProcess():
80+
dim: int
81+
cdim: int
82+
mu: wrapper.VectorFunction
83+
k: kernel.MatrixKernel
84+
x_obs: np.ndarray | None = None
85+
y_obs: np.ndarray | None = None
86+
sigma: np.ndarray | None = None
87+
inv: np.ndarray | None = None
88+
89+
def __init__(self,
90+
mu: wrapper.VectorFunction,
91+
k: kernel.MatrixKernel,
92+
discretized: bool | np.ndarray = False):
93+
if discretized is not False:
94+
raise NotImplementedError
95+
96+
self.mu = mu
97+
self.k = k
98+
99+
if k.dim != mu.dim:
100+
raise ValueError('Dims of mu and k do not match!')
101+
self.dim = mu.dim
102+
103+
if k.cdim != mu.cdim:
104+
raise ValueError('Codomain dim of mu and k do not match!')
105+
self.cdim = mu.cdim
106+
107+
def condition(self, x: np.ndarray,
108+
y: np.ndarray,
109+
sigma: np.ndarray | float):
110+
# TODO: add dim checks on x_obs and y_obs:
111+
# x_obs should be (n_obs x self.dim)
112+
# y_obs should be (n_obs x self.cdim)
113+
# sigma should be (n_obs x self.cdim)
114+
# note that n_obs = len(x)
115+
sigma = np.asarray(sigma)
116+
if not sigma.shape == y.shape:
117+
raise ValueError('Sigma has the wrong size!')
118+
else:
119+
sigma = sigma * np.ones_like(y)
120+
121+
self.x_obs = x
122+
self.y_obs = y
123+
self.sigma = sigma
124+
self.inv = np.linalg.inv(self.k(x) + sigma.flatten() * np.eye(len(x) * self.cdim))
125+
126+
def mu_posterior(self, x: np.ndarray):
127+
if (self.x_obs is None) or (self.y_obs is None) or (self.inv is None):
128+
raise ValueError('GP not conditioned: call VectorGaussianProcess.'
129+
'condition with observations before computing '
130+
'posterior!')
131+
return self.mu(x) + \
132+
(self.k(x, self.x_obs) @ self.inv @ (self.y_obs - self.mu(self.x_obs)).flatten()).reshape((len(x), self.cdim))
133+
134+
def k_posterior(self, x1: np.ndarray, x2: np.ndarray | None = None):
135+
if (self.x_obs is None) or (self.y_obs is None) or (self.inv is None):
136+
raise ValueError('GP not conditioned: call VectorGaussianProcess.'
137+
'condition with observations before computing '
138+
'posterior!')
139+
return (self.k(x1) if x2 is None else self.k(x1, x2)) - \
140+
self.k(x1, self.x_obs) @ self.inv @ (self.k(self.x_obs, x1) if x2 is None else self.k(self.x_obs, x2))
141+
142+
def prior(self, x: np.ndarray, n: int = 1) -> np.ndarray:
143+
if n > 1:
144+
sn = (len(x) * self.cdim, n)
145+
sr = (len(x), self.cdim, n)
146+
else:
147+
sn = (len(x) * self.cdim)
148+
sr = (len(x), self.cdim)
149+
150+
ell = np.linalg.cholesky(self.k(x) + NUGGET * np.eye(self.cdim * len(x)))
151+
y = (self.mu(x)[..., None] if n > 1 else self.mu(x)) + \
152+
(ell @ np.random.normal(0, 1, sn)).reshape(sr)
153+
return y
154+
155+
def posterior(self, x: np.ndarray, n: int = 1) -> tuple[np.ndarray, np.ndarray]:
156+
if n > 1:
157+
sn = (len(x) * self.cdim, n)
158+
sr = (len(x), self.cdim, n)
159+
else:
160+
sn = (len(x) * self.cdim)
161+
sr = (len(x), self.cdim)
162+
163+
ell = np.linalg.cholesky(self.k_posterior(x) + NUGGET * np.eye(self.cdim * len(x)))
164+
y = (self.mu_posterior(x)[..., None] if n > 1 else self.mu_posterior(x)) + \
165+
(ell @ np.random.normal(0, 1, (sn))).reshape(sr)
166+
return y

0 commit comments

Comments
 (0)