or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

Files

docs

core-gp.mdgp-operations.mdindex.mdlazy.mdmeasure.mdmulti-output.mdobservations.mdrandom.md

lazy.mddocs/

0

# Lazy Computation

1

2

Efficient computation with lazy evaluation for vectors and matrices that build values on-demand using custom rules and caching. This enables scalable Gaussian process operations by avoiding explicit construction of large matrices until needed.

3

4

## Capabilities

5

6

### Lazy Tensor Base

7

8

Foundation class for lazy tensors that index by object identity and provide on-demand value construction through custom building rules.

9

10

```python { .api }

11

class LazyTensor:

12

def __init__(self, rank):

13

"""

14

Initialize lazy tensor with specified rank.

15

16

Parameters:

17

- rank: Tensor rank (1 for vectors, 2 for matrices)

18

"""

19

20

def __setitem__(self, key, value):

21

"""Set value at specified key."""

22

23

def __getitem__(self, key):

24

"""Get value at specified key, building if necessary."""

25

26

def _build(self, i):

27

"""

28

Abstract method for building values on-demand.

29

30

Parameters:

31

- i: Resolved index to build value for

32

33

Returns:

34

- Built value for the index

35

"""

36

```

37

38

### Lazy Vectors

39

40

One-dimensional lazy tensors that build values using custom rules based on index sets and builder functions.

41

42

```python { .api }

43

class LazyVector(LazyTensor):

44

def __init__(self):

45

"""Initialize lazy vector."""

46

47

def add_rule(self, indices, builder):

48

"""

49

Add building rule for specified indices.

50

51

Note: For performance, indices must already be resolved!

52

53

Parameters:

54

- indices: Set of indices this rule applies to

55

- builder: Function that takes index and returns corresponding element

56

"""

57

58

def _build(self, i):

59

"""

60

Build value for index using registered rules.

61

62

Parameters:

63

- i: Index tuple to build value for

64

65

Returns:

66

- Built value

67

68

Raises:

69

- RuntimeError: If no rule can build the requested index

70

"""

71

```

72

73

### Lazy Matrices

74

75

Two-dimensional lazy tensors supporting universal rules and dimension-specific rules for efficient matrix construction.

76

77

```python { .api }

78

class LazyMatrix(LazyTensor):

79

def __init__(self):

80

"""Initialize lazy matrix."""

81

82

def add_rule(self, indices, builder):

83

"""

84

Add universal building rule for specified indices.

85

86

Note: For performance, indices must already be resolved!

87

88

Parameters:

89

- indices: Set of indices this rule applies to

90

- builder: Function taking (left_index, right_index) returning element

91

"""

92

93

def add_left_rule(self, i_left, indices, builder):

94

"""

95

Add building rule for fixed left index.

96

97

Note: For performance, indices must already be resolved!

98

99

Parameters:

100

- i_left: Fixed left index for this rule

101

- indices: Set of right indices this rule applies to

102

- builder: Function taking right_index and returning element

103

"""

104

105

def add_right_rule(self, i_right, indices, builder):

106

"""

107

Add building rule for fixed right index.

108

109

Note: For performance, indices must already be resolved!

110

111

Parameters:

112

- i_right: Fixed right index for this rule

113

- indices: Set of left indices this rule applies to

114

- builder: Function taking left_index and returning element

115

"""

116

117

def _build(self, i):

118

"""

119

Build matrix element using registered rules.

120

121

Parameters:

122

- i: (left_index, right_index) tuple

123

124

Returns:

125

- Built matrix element

126

127

Raises:

128

- RuntimeError: If no rule can build the requested element

129

"""

130

```

131

132

### Index Resolution

133

134

Internal functions for converting various key types to resolved indices used by the lazy tensor system.

135

136

```python { .api }

137

def _resolve_index(key):

138

"""Resolve key to index using object identity."""

139

140

def _resolve_index(i):

141

"""Resolve integer index directly."""

142

143

def _resolve_index(x):

144

"""Resolve tuple or sequence of keys recursively."""

145

```

146

147

## Usage Examples

148

149

### Basic Lazy Vector Usage

150

151

```python

152

import stheno

153

from stheno.lazy import LazyVector

154

155

# Create lazy vector

156

lazy_vec = LazyVector()

157

158

# Create some objects to use as indices

159

gp1 = stheno.GP(kernel=stheno.EQ(), name="gp1")

160

gp2 = stheno.GP(kernel=stheno.Matern52(), name="gp2")

161

gp3 = stheno.GP(kernel=stheno.Linear(), name="gp3")

162

163

# Add rules for building values

164

indices_123 = {id(gp1), id(gp2), id(gp3)}

165

def builder_simple(i):

166

if i == id(gp1):

167

return "Value for GP1"

168

elif i == id(gp2):

169

return "Value for GP2"

170

elif i == id(gp3):

171

return "Value for GP3"

172

else:

173

return f"Default value for {i}"

174

175

lazy_vec.add_rule(indices_123, builder_simple)

176

177

# Access values - built on demand

178

print(f"GP1 value: {lazy_vec[gp1]}")

179

print(f"GP2 value: {lazy_vec[gp2]}")

180

print(f"GP3 value: {lazy_vec[gp3]}")

181

182

# Values are cached after first access

183

print(f"GP1 value (cached): {lazy_vec[gp1]}")

184

```

185

186

### Lazy Matrix with Multiple Rule Types

187

188

```python

189

from stheno.lazy import LazyMatrix

190

import numpy as np

191

192

# Create lazy matrix

193

lazy_mat = LazyMatrix()

194

195

# Create GP objects as indices

196

gps = [stheno.GP(kernel=stheno.EQ(), name=f"gp{i}") for i in range(4)]

197

gp_ids = {id(gp) for gp in gps}

198

199

# Universal rule: diagonal elements

200

def diagonal_builder(i_left, i_right):

201

if i_left == i_right:

202

return 1.0 # Diagonal element

203

return None # Let other rules handle off-diagonal

204

205

lazy_mat.add_rule(gp_ids, diagonal_builder)

206

207

# Left rule: first GP has special relationship with others

208

def first_gp_left_rule(i_right):

209

# When first GP is on the left, return correlation

210

return 0.5

211

212

lazy_mat.add_left_rule(id(gps[0]), gp_ids, first_gp_left_rule)

213

214

# Right rule: second GP has special relationship when on right

215

def second_gp_right_rule(i_left):

216

# When second GP is on the right, return different correlation

217

return 0.3

218

219

lazy_mat.add_right_rule(id(gps[1]), gp_ids, second_gp_right_rule)

220

221

# Access matrix elements

222

print(f"Diagonal (0,0): {lazy_mat[gps[0], gps[0]]}") # Uses universal rule

223

print(f"Off-diagonal (0,1): {lazy_mat[gps[0], gps[1]]}") # Uses left rule

224

print(f"Off-diagonal (2,1): {lazy_mat[gps[2], gps[1]]}") # Uses right rule

225

print(f"Off-diagonal (2,3): {lazy_mat[gps[2], gps[3]]}") # Uses universal rule (None->0)

226

```

227

228

### Lazy Computation in GP Measures

229

230

```python

231

# Lazy computation is used internally by Stheno measures

232

measure = stheno.Measure()

233

234

# Create GPs in the measure

235

with measure:

236

gp_a = stheno.GP(kernel=stheno.EQ(), name="gp_a")

237

gp_b = stheno.GP(kernel=stheno.Matern52(), name="gp_b")

238

gp_c = gp_a + gp_b

239

measure.name(gp_c, "gp_c")

240

241

# The measure uses lazy vectors and matrices internally

242

print(f"Measure processes: {len(measure.ps)}")

243

print(f"Lazy means type: {type(measure.means)}")

244

print(f"Lazy kernels type: {type(measure.kernels)}")

245

246

# Access mean functions (computed lazily)

247

mean_a = measure.means[gp_a]

248

mean_b = measure.means[gp_b]

249

mean_c = measure.means[gp_c] # Built from gp_a + gp_b rule

250

251

print(f"Mean A: {mean_a}")

252

print(f"Mean C: {mean_c}")

253

254

# Access kernel matrix elements (computed lazily)

255

kernel_aa = measure.kernels[gp_a, gp_a]

256

kernel_ab = measure.kernels[gp_a, gp_b]

257

kernel_cc = measure.kernels[gp_c, gp_c] # Built from sum rule

258

259

print(f"Kernel (A,A): {kernel_aa}")

260

print(f"Kernel (C,C) type: {type(kernel_cc)}")

261

```

262

263

### Custom Lazy Vector for Function Caching

264

265

```python

266

# Create custom lazy vector for expensive function evaluations

267

expensive_cache = LazyVector()

268

269

def expensive_function(x):

270

"""Simulate expensive computation."""

271

print(f"Computing expensive function for {x}")

272

import time

273

time.sleep(0.1) # Simulate computation time

274

return x ** 2 + np.sin(x)

275

276

# Set up objects and their indices

277

inputs = [0.5, 1.0, 1.5, 2.0, 2.5]

278

input_ids = {id(x): x for x in inputs} # Map id back to value

279

all_ids = set(input_ids.keys())

280

281

def expensive_builder(obj_id):

282

x = input_ids[obj_id]

283

return expensive_function(x)

284

285

expensive_cache.add_rule(all_ids, expensive_builder)

286

287

# First access computes and caches

288

print("First access:")

289

result1 = expensive_cache[inputs[0]]

290

result2 = expensive_cache[inputs[1]]

291

292

print("Second access (cached):")

293

result1_cached = expensive_cache[inputs[0]] # No computation

294

result2_cached = expensive_cache[inputs[1]] # No computation

295

296

print(f"Results match: {result1 == result1_cached and result2 == result2_cached}")

297

```

298

299

### Lazy Matrix for Kernel Computations

300

301

```python

302

# Create lazy matrix for kernel evaluations

303

kernel_matrix = LazyMatrix()

304

305

# Define inputs and kernel function

306

inputs = [np.array([i]) for i in range(5)]

307

input_ids = {id(x): x for x in inputs}

308

all_ids = set(input_ids.keys())

309

310

def kernel_function(x1, x2):

311

"""RBF kernel function."""

312

return np.exp(-0.5 * np.sum((x1 - x2)**2))

313

314

def kernel_builder(id1, id2):

315

x1 = input_ids[id1]

316

x2 = input_ids[id2]

317

print(f"Computing kernel between {x1.flatten()} and {x2.flatten()}")

318

return kernel_function(x1, x2)

319

320

kernel_matrix.add_rule(all_ids, kernel_builder)

321

322

# Build kernel matrix elements on demand

323

print("Building kernel matrix:")

324

K_00 = kernel_matrix[inputs[0], inputs[0]] # Diagonal

325

K_01 = kernel_matrix[inputs[0], inputs[1]] # Off-diagonal

326

K_10 = kernel_matrix[inputs[1], inputs[0]] # Symmetric element

327

328

print(f"K(0,0) = {K_00:.3f}")

329

print(f"K(0,1) = {K_01:.3f}")

330

print(f"K(1,0) = {K_10:.3f}")

331

print(f"Symmetry check: {np.allclose(K_01, K_10)}")

332

333

# Second access uses cached values

334

print("\nAccessing cached values:")

335

K_00_cached = kernel_matrix[inputs[0], inputs[0]] # No computation

336

print(f"Cached K(0,0) = {K_00_cached:.3f}")

337

```

338

339

### Mixed Rule Priorities

340

341

```python

342

# Demonstrate rule priority in lazy matrices

343

priority_matrix = LazyMatrix()

344

345

# Create test objects

346

objs = [f"obj_{i}" for i in range(3)]

347

obj_ids = {id(obj): obj for obj in objs}

348

all_ids = set(obj_ids.keys())

349

350

# Universal rule (lowest priority)

351

def universal_rule(id1, id2):

352

return f"Universal: {obj_ids[id1]} × {obj_ids[id2]}"

353

354

priority_matrix.add_rule(all_ids, universal_rule)

355

356

# Left rule for first object (higher priority)

357

def left_rule_obj0(id2):

358

return f"Left rule: obj_0 × {obj_ids[id2]}"

359

360

priority_matrix.add_left_rule(id(objs[0]), all_ids, left_rule_obj0)

361

362

# Right rule for second object (higher priority)

363

def right_rule_obj1(id1):

364

return f"Right rule: {obj_ids[id1]} × obj_1"

365

366

priority_matrix.add_right_rule(id(objs[1]), all_ids, right_rule_obj1)

367

368

# Test rule priorities

369

print(f"(0,0): {priority_matrix[objs[0], objs[0]]}") # Left rule wins

370

print(f"(0,1): {priority_matrix[objs[0], objs[1]]}") # Left rule wins over right

371

print(f"(2,1): {priority_matrix[objs[2], objs[1]]}") # Right rule wins

372

print(f"(2,2): {priority_matrix[objs[2], objs[2]]}") # Universal rule used

373

```

374

375

### Error Handling

376

377

```python

378

# Demonstrate error handling for missing rules

379

incomplete_vector = LazyVector()

380

381

# Add rule for only some indices

382

some_objs = ["a", "b"]

383

some_ids = {id(obj) for obj in some_objs}

384

385

def partial_builder(obj_id):

386

return f"Built for {id}"

387

388

incomplete_vector.add_rule(some_ids, partial_builder)

389

390

# This works

391

result = incomplete_vector["a"]

392

print(f"Success: {result}")

393

394

# This raises RuntimeError

395

try:

396

result = incomplete_vector["c"] # Not in rule set

397

except RuntimeError as e:

398

print(f"Expected error: {e}")

399

```