23
23
24
24
import pymc as pm
25
25
26
- from pymc .gp .cov import Covariance
26
+ from pymc .gp .cov import Covariance , Periodic
27
27
from pymc .gp .gp import Base
28
28
from pymc .gp .mean import Mean , Zero
29
29
@@ -69,19 +69,16 @@ def calc_eigenvectors(
69
69
def calc_basis_periodic (
70
70
Xs : TensorLike ,
71
71
period : TensorLike ,
72
- m : Sequence [ int ] ,
72
+ m : int ,
73
73
tl : ModuleType = np ,
74
74
):
75
75
"""
76
76
Calculate basis vectors for the cosine series expansion of the periodic covariance function.
77
77
These are derived from the Taylor series representation of the covariance.
78
78
"""
79
- if len (m ) != 1 :
80
- raise ValueError ("`Periodic` basis vectors only implemented for 1-dimensional case." )
81
- m0 = m [0 ] # for compatibility with other kernels, m must be a sequence
82
79
w0 = (2 * np .pi ) / period # angular frequency defining the periodicity
83
- m1 = tl .tile (w0 * Xs , m0 )
84
- m2 = tl .diag (tl .arange (0 , m0 , 1 ))
80
+ m1 = tl .tile (w0 * Xs , m )
81
+ m2 = tl .diag (tl .arange (0 , m , 1 ))
85
82
mw0x = m1 @ m2
86
83
phi_cos = tl .cos (mw0x )
87
84
phi_sin = tl .sin (mw0x )
@@ -128,8 +125,8 @@ class HSGP(Base):
128
125
parameterization: str
129
126
Whether to use `centred` or `noncentered` parameterization when multiplying the
130
127
basis by the coefficients.
131
- cov_func: None, 2D array, or instance of `Covariance`
132
- The covariance function. Defaults to zero .
128
+ cov_func: Covariance function, must be an instance of `Stationary` and implement a
129
+ `power_spectral_density` method .
133
130
mean_func: None, instance of Mean
134
131
The mean function. Defaults to zero.
135
132
@@ -431,10 +428,11 @@ class HSGPPeriodic(Base):
431
428
432
429
Parameters
433
430
----------
434
- m: list
435
- The number of basis vectors to use. Must be a list with one element.
436
- cov_func: Instance of `Periodic` covariance
437
- The covariance function. Defaults to zero.
431
+ m: int
432
+ The number of basis vectors to use. Must be a positive integer.
433
+ scale: TensorLike
434
+ The standard deviation (square root of the variance) of the GP effect. Defaults to 1.0.
435
+ cov_func: Must be an instance of instance of `Periodic` covariance
438
436
mean_func: None, instance of Mean
439
437
The mean function. Defaults to zero.
440
438
@@ -447,10 +445,11 @@ class HSGPPeriodic(Base):
447
445
448
446
with pm.Model() as model:
449
447
# Specify the covariance function, only for the 1-D case
448
+ scale = pm.HalfNormal(10)
450
449
cov_func = pm.gp.cov.Periodic(1, period=1, ls=0.1)
451
450
452
451
# Specify the approximation with 25 basis vectors
453
- gp = pm.gp.HSGPPeriodic(m=[25] , cov_func=cov_func)
452
+ gp = pm.gp.HSGPPeriodic(m=25, scale=scale , cov_func=cov_func)
454
453
455
454
# Place a GP prior over the function f.
456
455
f = gp.prior("f", X=X)
@@ -472,31 +471,37 @@ class HSGPPeriodic(Base):
472
471
473
472
def __init__ (
474
473
self ,
475
- m : Sequence [int ],
474
+ m : int ,
475
+ scale : Optional [Union [float , TensorLike ]] = 1.0 ,
476
476
* ,
477
477
mean_func : Mean = Zero (),
478
- cov_func : Covariance ,
478
+ cov_func : Periodic ,
479
479
):
480
- arg_err_msg = (
481
- "`m` and `L`, if provided, must be sequences with one element per active "
482
- "dimension of the kernel or covariance function."
483
- )
480
+ arg_err_msg = "`m` must be a positive integer as the `Periodic` kernel approximation is only implemented for 1-dimensional case."
484
481
485
- if not isinstance (m , Sequence ):
482
+ if not isinstance (m , int ):
483
+ raise ValueError (arg_err_msg )
484
+
485
+ if m <= 0 :
486
486
raise ValueError (arg_err_msg )
487
487
488
+ if not isinstance (cov_func , Periodic ):
489
+ raise ValueError (
490
+ "`cov_func` must be an instance of a `Periodic` kernel only. Use the `scale` parameter to control the variance."
491
+ )
492
+
488
493
if cov_func .n_dims > 1 :
489
494
raise ValueError (
490
495
"HSGP approximation for `Periodic` kernel only implemented for 1-dimensional case."
491
496
)
492
- m = tuple (m )
493
497
494
498
self ._m = m
499
+ self .scale = scale
495
500
496
501
super ().__init__ (mean_func = mean_func , cov_func = cov_func )
497
502
498
503
def prior_linearized (self , Xs : TensorLike ):
499
- """Linearized version of the approximation. Returns the cosine and sine bases and coeffients
504
+ """Linearized version of the approximation. Returns the cosine and sine bases and coefficients
500
505
of the expansion needed to create the GP.
501
506
502
507
This function allows the user to bypass the GP interface and work directly with the basis
@@ -529,10 +534,11 @@ def prior_linearized(self, Xs: TensorLike):
529
534
X = np.linspace(0, 10, 100)[:, None]
530
535
531
536
with pm.Model() as model:
537
+ scale = pm.HalfNormal(10)
532
538
cov_func = pm.gp.cov.Periodic(1, period=1, ls=ell)
533
539
534
- # m = [ 200] means 200 basis vectors for the first dimension
535
- gp = pm.gp.HSGPPeriodic(m=[ 200] , cov_func=cov_func)
540
+ # m= 200 means 200 basis vectors
541
+ gp = pm.gp.HSGPPeriodic(m=200, scale=scale , cov_func=cov_func)
536
542
537
543
# Order is important. First calculate the mean, then make X a shared variable,
538
544
# then subtract the mean. When X is mutated later, the correct mean will be
@@ -542,19 +548,19 @@ def prior_linearized(self, Xs: TensorLike):
542
548
Xs = X - X_mean
543
549
544
550
# Pass the zero-subtracted Xs in to the GP
545
- (phi_cos, phi_sin), psd, sqrt_psd = gp.prior_linearized(Xs=Xs)
551
+ (phi_cos, phi_sin), psd = gp.prior_linearized(Xs=Xs)
546
552
547
553
# Specify standard normal prior in the coefficients. The number of which
548
554
# is twice the number of basis vectors minus one.
549
555
# This is so that each cosine term has a `beta` and all but one of the
550
556
# sine terms, as first eigenfunction for the sine component is zero
551
- m0 = gp._m[0]
552
- beta = pm.Normal("beta", size=(m0 * 2 - 1))
557
+ m = gp._m
558
+ beta = pm.Normal("beta", size=(m * 2 - 1))
553
559
554
560
# The (non-centered) GP approximation is given by
555
561
f = pm.Deterministic(
556
562
"f",
557
- phi_cos @ (psd * self._beta[:m0 ]) + phi_sin[..., 1:] @ (psd[1:] * self._beta[m0 :])
563
+ phi_cos @ (psd * beta[:m ]) + phi_sin[..., 1:] @ (psd[1:] * beta[m :])
558
564
)
559
565
...
560
566
@@ -572,8 +578,9 @@ def prior_linearized(self, Xs: TensorLike):
572
578
Xs , _ = self .cov_func ._slice (Xs )
573
579
574
580
phi_cos , phi_sin = calc_basis_periodic (Xs , self .cov_func .period , self ._m , tl = pt )
575
- J = pt .arange (0 , self ._m [0 ], 1 )
576
- psd = self .cov_func .power_spectral_density_approx (J )
581
+ J = pt .arange (0 , self ._m , 1 )
582
+ # rescale basis coefficients by the sqrt variance term
583
+ psd = self .scale * self .cov_func .power_spectral_density_approx (J )
577
584
return (phi_cos , phi_sin ), psd
578
585
579
586
def prior (self , name : str , X : TensorLike , dims : Optional [str ] = None ): # type: ignore
@@ -594,14 +601,14 @@ def prior(self, name: str, X: TensorLike, dims: Optional[str] = None): # type:
594
601
595
602
(phi_cos , phi_sin ), psd = self .prior_linearized (X - self ._X_mean )
596
603
597
- m0 = self ._m [ 0 ]
598
- self ._beta = pm .Normal (f"{ name } _hsgp_coeffs_" , size = (m0 * 2 - 1 ))
604
+ m = self ._m
605
+ self ._beta = pm .Normal (f"{ name } _hsgp_coeffs_" , size = (m * 2 - 1 ))
599
606
# The first eigenfunction for the sine component is zero
600
607
# and so does not contribute to the approximation.
601
608
f = (
602
609
self .mean_func (X )
603
- + phi_cos @ (psd * self ._beta [:m0 ]) # type: ignore
604
- + phi_sin [..., 1 :] @ (psd [1 :] * self ._beta [m0 :]) # type: ignore
610
+ + phi_cos @ (psd * self ._beta [:m ]) # type: ignore
611
+ + phi_sin [..., 1 :] @ (psd [1 :] * self ._beta [m :]) # type: ignore
605
612
)
606
613
607
614
self .f = pm .Deterministic (name , f , dims = dims )
@@ -619,11 +626,12 @@ def _build_conditional(self, Xnew):
619
626
Xnew , _ = self .cov_func ._slice (Xnew )
620
627
621
628
phi_cos , phi_sin = calc_basis_periodic (Xnew - X_mean , self .cov_func .period , self ._m , tl = pt )
622
- m0 = self ._m [0 ]
623
- J = pt .arange (0 , m0 , 1 )
624
- psd = self .cov_func .power_spectral_density_approx (J )
629
+ m = self ._m
630
+ J = pt .arange (0 , m , 1 )
631
+ # rescale basis coefficients by the sqrt variance term
632
+ psd = self .scale * self .cov_func .power_spectral_density_approx (J )
625
633
626
- phi = phi_cos @ (psd * beta [:m0 ]) + phi_sin [..., 1 :] @ (psd [1 :] * beta [m0 :])
634
+ phi = phi_cos @ (psd * beta [:m ]) + phi_sin [..., 1 :] @ (psd [1 :] * beta [m :])
627
635
return self .mean_func (Xnew ) + phi
628
636
629
637
def conditional (self , name : str , Xnew : TensorLike , dims : Optional [str ] = None ): # type: ignore
0 commit comments