77from . import kernel
88
99import numpy as np
10+ from scipy import linalg
1011
1112NUGGET = 1e-6
1213
@@ -17,7 +18,7 @@ class ScalarGaussianProcess():
1718 x_obs : np .ndarray | None = None
1819 y_obs : np .ndarray | None = None
1920 sigma : np .ndarray | None = None
20- inv : np .ndarray | None = None
21+ cho_factor : tuple [ np .ndarray , bool ] | None = None
2122
2223 def __init__ (self ,
2324 mu : wrapper .ScalarFunction ,
@@ -46,24 +47,26 @@ def condition(self, x: np.ndarray,
4647 self .x_obs = x
4748 self .y_obs = y
4849 self .sigma = sigma
49- self .inv = np .linalg .inv (self .k (x ) + sigma * np .eye (len (x )))
50+ self .cho_factor = linalg .cho_factor (self .k (x ) + sigma * np .eye (len (x )))
51+
52+ def tune (self ):
53+ pass
5054
5155 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 ):
56+ if (self .x_obs is None ) or (self .y_obs is None ) or (self .cho_factor is None ):
5357 raise ValueError ('GP not conditioned: call ScalarGaussianProcess.'
5458 'condition with observations before computing '
5559 'posterior!' )
56- return self .mu (x ) + self .k (x , self .x_obs ) @ self . inv @ \
57- ( self .y_obs - self .mu (self .x_obs ))
60+ return self .mu (x ) + self .k (x , self .x_obs ) @ \
61+ linalg . cho_solve ( self . cho_factor , self .y_obs - self .mu (self .x_obs ))
5862
5963 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 ):
64+ if (self .x_obs is None ) or (self .y_obs is None ) or (self .cho_factor is None ):
6165 raise ValueError ('GP not conditioned: call ScalarGaussianProcess.'
6266 'condition with observations before computing '
6367 'posterior!' )
6468 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 ))
69+ self .k (x1 , self .x_obs ) @ linalg .cho_solve (self .cho_factor , self .k (self .x_obs , x1 ) if x2 is None else self .k (self .x_obs , x2 ))
6770
6871 def sample_prior (self , x : np .ndarray , n : int = 1 ) -> np .ndarray :
6972 ell = np .linalg .cholesky (self .k (x ) + NUGGET * np .eye (len (x )))
@@ -84,7 +87,7 @@ class VectorGaussianProcess():
8487 x_obs : np .ndarray | None = None
8588 y_obs : np .ndarray | None = None
8689 sigma : np .ndarray | None = None
87- inv : np .ndarray | None = None
90+ cho_factor : tuple [ np .ndarray , bool ] | None = None
8891
8992 def __init__ (self ,
9093 mu : wrapper .VectorFunction ,
@@ -121,23 +124,26 @@ def condition(self, x: np.ndarray,
121124 self .x_obs = x
122125 self .y_obs = y
123126 self .sigma = sigma
124- self .inv = np .linalg .inv (self .k (x ) + sigma .flatten () * np .eye (len (x ) * self .cdim ))
127+ self .cho_factor = linalg .cho_factor (self .k (x ) + sigma .flatten () * np .eye (len (x ) * self .cdim ))
128+
129+ def tune (self ):
130+ pass
125131
126132 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 ):
133+ if (self .x_obs is None ) or (self .y_obs is None ) or (self .cho_factor is None ):
128134 raise ValueError ('GP not conditioned: call VectorGaussianProcess.'
129135 'condition with observations before computing '
130136 'posterior!' )
131137 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 ))
138+ (self .k (x , self .x_obs ) @ linalg . cho_solve ( self .cho_factor , (self .y_obs - self .mu (self .x_obs )).flatten () )).reshape ((len (x ), self .cdim ))
133139
134140 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 ):
141+ if (self .x_obs is None ) or (self .y_obs is None ) or (self .cho_factor is None ):
136142 raise ValueError ('GP not conditioned: call VectorGaussianProcess.'
137143 'condition with observations before computing '
138144 'posterior!' )
139145 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 ))
146+ self .k (x1 , self .x_obs ) @ linalg . cho_solve ( self .cho_factor , self .k (self .x_obs , x1 ) if x2 is None else self .k (self .x_obs , x2 ))
141147
142148 def sample_prior (self , x : np .ndarray , n : int = 1 ) -> np .ndarray :
143149 if n > 1 :
@@ -147,6 +153,7 @@ def sample_prior(self, x: np.ndarray, n: int = 1) -> np.ndarray:
147153 sn = (len (x ) * self .cdim )
148154 sr = (len (x ), self .cdim )
149155
156+ # TODO: make this faster (use precomputed cho_factor and Schur complement)
150157 ell = np .linalg .cholesky (self .k (x ) + NUGGET * np .eye (self .cdim * len (x )))
151158 y = (self .mu (x )[..., None ] if n > 1 else self .mu (x )) + \
152159 (ell @ np .random .normal (0 , 1 , sn )).reshape (sr )
@@ -160,6 +167,7 @@ def sample_posterior(self, x: np.ndarray, n: int = 1) -> tuple[np.ndarray, np.nd
160167 sn = (len (x ) * self .cdim )
161168 sr = (len (x ), self .cdim )
162169
170+ # TODO: make this faster (use precomputed cho_factor and Schur complement)
163171 ell = np .linalg .cholesky (self .k_posterior (x ) + NUGGET * np .eye (self .cdim * len (x )))
164172 y = (self .mu_posterior (x )[..., None ] if n > 1 else self .mu_posterior (x )) + \
165173 (ell @ np .random .normal (0 , 1 , (sn ))).reshape (sr )
0 commit comments