0
# Measure and Model Management
1
2
Advanced model organization using measures to manage collections of Gaussian processes. Measures provide centralized management of GP relationships, naming, conditioning operations, and sampling across multiple processes simultaneously.
3
4
## Capabilities
5
6
### Measure Construction and Management
7
8
Create and manage measures that serve as containers for collections of related Gaussian processes with shared operations and naming.
9
10
```python { .api }
11
class Measure:
12
def __init__(self):
13
"""Initialize a new measure."""
14
15
ps: List[GP] # List of processes in the measure
16
means: LazyVector # Lazy vector of mean functions
17
kernels: LazyMatrix # Lazy matrix of kernel functions
18
default: ClassVar[Optional[Measure]] # Global default measure
19
```
20
21
### Context Management
22
23
Use measures as context managers to temporarily set default measure for GP construction within a scope.
24
25
```python { .api }
26
class Measure:
27
def __enter__(self) -> Measure:
28
"""Enter context manager, setting as default measure."""
29
30
def __exit__(self, exc_type, exc_val, exc_tb):
31
"""Exit context manager, restoring previous default."""
32
```
33
34
### GP Naming and Identification
35
36
Manage GP names and retrieve GPs by name or names by GP for better organization and debugging.
37
38
```python { .api }
39
class Measure:
40
def __getitem__(self, key):
41
"""
42
Get GP by name or get name by GP.
43
44
Parameters:
45
- key: GP name (str) or GP object
46
47
Returns:
48
- GP or str: GP object if key is name, name if key is GP
49
"""
50
51
def name(self, p, name):
52
"""
53
Assign name to a GP.
54
55
Parameters:
56
- p: Gaussian process to name
57
- name: Name to assign
58
"""
59
```
60
61
### GP Construction and Management
62
63
Add new Gaussian processes to the measure with specified means and kernels, or manage existing processes.
64
65
```python { .api }
66
class Measure:
67
def add_independent_gp(self, p, mean, kernel):
68
"""
69
Add independent GP to the measure.
70
71
Parameters:
72
- p: GP object to add
73
- mean: Mean function for the GP
74
- kernel: Kernel function for the GP
75
"""
76
77
def add_gp(self, mean, kernel, left_rule, right_rule=None):
78
"""
79
Add GP with custom kernel rules.
80
81
Parameters:
82
- mean: Mean function
83
- kernel: Kernel function
84
- left_rule: Left kernel rule
85
- right_rule: Optional right kernel rule
86
87
Returns:
88
- GP: The added process
89
"""
90
91
def __call__(self, p):
92
"""
93
Apply measure to GP or FDD.
94
95
Parameters:
96
- p: GP or FDD to apply measure to
97
98
Returns:
99
- Applied measure result
100
"""
101
```
102
103
### GP Operations Within Measures
104
105
Perform operations between GPs within the same measure, including summation, multiplication, and transformations.
106
107
```python { .api }
108
class Measure:
109
def sum(self, p_sum, p1, p2):
110
"""
111
Perform sum operation between two GPs.
112
113
Parameters:
114
- p_sum: Resulting sum GP
115
- p1: First GP operand
116
- p2: Second GP operand
117
"""
118
119
def mul(self, p_mul, p1, p2):
120
"""
121
Perform multiplication between two GPs.
122
123
Parameters:
124
- p_mul: Resulting product GP
125
- p1: First GP operand
126
- p2: Second GP operand
127
"""
128
129
def shift(self, p_shifted, p, shift):
130
"""
131
Shift GP inputs.
132
133
Parameters:
134
- p_shifted: Resulting shifted GP
135
- p: Source GP
136
- shift: Shift amount
137
"""
138
139
def stretch(self, p_stretched, p, stretch):
140
"""
141
Stretch GP inputs.
142
143
Parameters:
144
- p_stretched: Resulting stretched GP
145
- p: Source GP
146
- stretch: Stretch factor
147
"""
148
149
def select(self, p_selected, p, *dims):
150
"""
151
Select dimensions from GP.
152
153
Parameters:
154
- p_selected: Resulting GP
155
- p: Source GP
156
- *dims: Dimensions to select
157
"""
158
159
def transform(self, p_transformed, p, f):
160
"""
161
Transform GP inputs.
162
163
Parameters:
164
- p_transformed: Resulting transformed GP
165
- p: Source GP
166
- f: Transformation function
167
"""
168
169
def diff(self, p_diff, p, dim=0):
170
"""
171
Differentiate GP.
172
173
Parameters:
174
- p_diff: Resulting derivative GP
175
- p: Source GP
176
- dim: Dimension to differentiate
177
"""
178
179
def cross(self, p_cross, *ps):
180
"""
181
Create cross product of GPs.
182
183
Parameters:
184
- p_cross: Resulting cross product GP
185
- *ps: GPs to combine
186
"""
187
```
188
189
### Conditioning Operations
190
191
Condition the entire measure on observations, creating posterior measures with updated beliefs across all processes.
192
193
```python { .api }
194
class Measure:
195
def condition(self, obs):
196
"""
197
Condition measure on observations.
198
199
Parameters:
200
- obs: Observations object
201
202
Returns:
203
- Measure: Posterior measure
204
"""
205
206
def __or__(self, *args):
207
"""Shorthand for condition() using | operator."""
208
```
209
210
### Sampling Operations
211
212
Sample from multiple processes simultaneously, maintaining correlations and relationships between processes within the measure.
213
214
```python { .api }
215
class Measure:
216
def sample(self, state, n, *fdds):
217
"""
218
Sample from multiple processes with random state.
219
220
Parameters:
221
- state: Random state for sampling
222
- n: Number of samples
223
- *fdds: FDDs to sample from
224
225
Returns:
226
- Tuple: (new_state, samples)
227
"""
228
229
def sample(self, n, *fdds):
230
"""
231
Sample from multiple processes without explicit state.
232
233
Parameters:
234
- n: Number of samples
235
- *fdds: FDDs to sample from
236
237
Returns:
238
- Samples from the processes
239
"""
240
241
def sample(self, *fdds):
242
"""
243
Sample single realization from processes.
244
245
Parameters:
246
- *fdds: FDDs to sample from
247
248
Returns:
249
- Single sample from the processes
250
"""
251
```
252
253
### Probability Computations
254
255
Compute log probability densities for observations under the measure, useful for model comparison and hyperparameter optimization.
256
257
```python { .api }
258
class Measure:
259
def logpdf(self, *pairs):
260
"""
261
Compute log probability density for observation pairs.
262
263
Parameters:
264
- *pairs: (FDD, values) pairs
265
266
Returns:
267
- Log probability density
268
"""
269
270
def logpdf(self, obs):
271
"""
272
Compute log probability density for observations.
273
274
Parameters:
275
- obs: Observations object
276
277
Returns:
278
- Log probability density
279
"""
280
```
281
282
## Usage Examples
283
284
### Basic Measure Usage
285
286
```python
287
import stheno
288
import numpy as np
289
290
# Create measure
291
measure = stheno.Measure()
292
293
# Use as context manager
294
with measure:
295
gp1 = stheno.GP(kernel=stheno.EQ(), name="signal")
296
gp2 = stheno.GP(kernel=stheno.Matern52(), name="noise")
297
298
# Access GPs by name
299
signal_gp = measure["signal"]
300
noise_gp = measure["noise"]
301
302
print(f"Signal GP: {measure[signal_gp]}") # Get name from GP
303
```
304
305
### Managing Multiple Related Processes
306
307
```python
308
# Create measure for climate model
309
climate = stheno.Measure()
310
311
with climate:
312
# Temperature process
313
temp = stheno.GP(
314
kernel=stheno.EQ().stretch(2.0) * stheno.Matern52().stretch(0.5),
315
name="temperature"
316
)
317
318
# Humidity correlated with temperature
319
humidity = 0.8 * temp + stheno.GP(kernel=stheno.EQ(), name="humidity_residual")
320
climate.name(humidity, "humidity")
321
322
# Pressure with seasonal component
323
pressure = stheno.GP(kernel=stheno.EQ().stretch(10.0), name="pressure")
324
325
# Work with the model
326
x = np.linspace(0, 365, 100) # Days in year
327
temp_fdd = temp(x)
328
humidity_fdd = humidity(x)
329
pressure_fdd = pressure(x)
330
```
331
332
### Conditioning Entire Measures
333
334
```python
335
# Generate observations
336
temp_obs = temp_fdd.sample()
337
humidity_obs = humidity_fdd.sample()
338
339
# Condition entire measure on all observations
340
posterior_climate = climate.condition(
341
stheno.Observations(temp_fdd, temp_obs),
342
stheno.Observations(humidity_fdd, humidity_obs)
343
)
344
345
# All processes now conditioned
346
posterior_temp = posterior_climate["temperature"]
347
posterior_humidity = posterior_climate["humidity"]
348
posterior_pressure = posterior_climate["pressure"]
349
```
350
351
### Multi-Process Sampling
352
353
```python
354
# Sample from multiple processes simultaneously
355
x_pred = np.linspace(0, 365, 50)
356
357
# Individual FDDs
358
temp_pred = posterior_temp(x_pred)
359
humidity_pred = posterior_humidity(x_pred)
360
pressure_pred = posterior_pressure(x_pred)
361
362
# Joint sampling maintains correlations
363
samples = climate.sample(5, temp_pred, humidity_pred, pressure_pred)
364
365
# Access individual process samples
366
temp_samples = samples[0] # First process samples
367
humidity_samples = samples[1] # Second process samples
368
pressure_samples = samples[2] # Third process samples
369
```
370
371
### Model Comparison with LogPDF
372
373
```python
374
# Create competing models
375
model1 = stheno.Measure()
376
model2 = stheno.Measure()
377
378
with model1:
379
gp1 = stheno.GP(kernel=stheno.EQ())
380
381
with model2:
382
gp2 = stheno.GP(kernel=stheno.Matern52())
383
384
# Test data
385
x_test = np.linspace(0, 1, 20)
386
y_test = np.sin(x_test) + 0.1 * np.random.randn(len(x_test))
387
388
# Compute log marginal likelihoods
389
fdd1 = gp1(x_test, noise=0.1)
390
fdd2 = gp2(x_test, noise=0.1)
391
392
logpdf1 = model1.logpdf(fdd1, y_test)
393
logpdf2 = model2.logpdf(fdd2, y_test)
394
395
print(f"Model 1 log likelihood: {logpdf1}")
396
print(f"Model 2 log likelihood: {logpdf2}")
397
print(f"Model {'1' if logpdf1 > logpdf2 else '2'} is preferred")
398
```
399
400
### Advanced Measure Operations
401
402
```python
403
# Create measure with custom operations
404
advanced_measure = stheno.Measure()
405
406
# Manually add GPs with custom kernel relationships
407
gp_base = stheno.GP()
408
gp_derived = stheno.GP()
409
410
# Add to measure with custom rules
411
advanced_measure.add_independent_gp(gp_base, stheno.ZeroMean(), stheno.EQ())
412
413
# Create derived process through measure operations
414
advanced_measure.sum(gp_derived, gp_base, gp_base) # gp_derived = 2 * gp_base
415
416
# Use derived relationships
417
x = np.linspace(0, 1, 50)
418
base_samples = gp_base(x).sample()
419
derived_samples = gp_derived(x).sample()
420
421
print(f"Derived should be ~2x base: {np.allclose(derived_samples, 2 * base_samples)}")
422
```