-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathiteration.py
420 lines (356 loc) · 15.4 KB
/
iteration.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Created on Mon Nov 7 15:40:41 2022
@author: cpf5546
"""
import numpy as np
import sympy as sy
from typing import Dict
import time
from blockops.block import BlockOperator, I
from blockops.run import PintRun
from blockops.taskPool import TaskPool
from blockops.utils.expr import getCoeffsFromFormula
from blockops.graph import PintGraph
from blockops.scheduler import getSchedule
from blockops.utils.checkRun import checkRunParameters, reduceRun
# -----------------------------------------------------------------------------
# Base class
# -----------------------------------------------------------------------------
class BlockIteration(object):
"""DOCTODO"""
needApprox = False
needCoarse = False
def __init__(self, update, propagator,
predictor=None, rules=None, name=None, **blockOps):
"""
DOCTODO
Parameters
----------
update : TYPE
DESCRIPTION.
predictor : TYPE
DESCRIPTION.
rules : TYPE, optional
DESCRIPTION. The default is None.
**blockOps : TYPE
DESCRIPTION.
"""
# Store name for the block iteration (optional)
self.name = name
# Store block coefficients from the iteration update formula
self.blockCoeffs = getCoeffsFromFormula(update, blockOps)
# Store block coefficient for the (fine) propagator
if isinstance(propagator, BlockOperator):
self.propagator = propagator
else:
self.propagator = eval(propagator, blockOps)
# Store block coefficients from the predictor
if isinstance(predictor, BlockOperator):
self.predictor = predictor
else:
self.predictor = eval(str(predictor), blockOps)
# Stores the generated symbols for the rules
# TODO : check if the rules hold with the given matrices
rules = [] if rules is None else rules
def condEval(x):
if isinstance(x, BlockOperator):
return x.symbol
else:
try:
e = eval(x, blockOps)
except TypeError:
raise ValueError(f'cannot evaluate {x}')
if hasattr(e, 'symbol'):
return e.symbol
else:
return e
self.rules = {condEval(a): condEval(b) for a, b in rules}
# Saving the BlockOps to make it easier to get the cost later on
self.blockOps = {v.name: v for v in blockOps.values()
if isinstance(v, BlockOperator)}
# Saving update formula, just in case ...
self.update = update
# Variable to store eventual associated problem
self.prob = None
@property
def coeffs(self):
"""Return an iterator on the (key, values) of blockCoeffs"""
return self.blockCoeffs.items()
@property
def M(self):
return max(op.M for op in self.blockCoeffs.values())
@property
def nLam(self):
return max(op.nLam for op in self.blockCoeffs.values())
@property
def nBlocks(self):
return np.inf if self.prob is None else self.prob.nBlocks
@property
def u0(self):
return None if self.prob is None else self.prob.u0
def __call__(self, nIter, nBlocks=None, u0=None, initSol=False, predSol=None):
"""
Evaluate the block iteration from given initial solution, number of
blocks and number of iteration.
Parameters
----------
nIter : int or N-sequence of int
Number of iteration, for each block (if int) or each block
separately.
nBlocks : int, optional
Number of blocks. The default takes the nBlocks value of the
associated problem.
u0 : M-sequence of floats or complex
Initial solution used for numerical evaluation
(not required if M==0).
initSol : bool, optional
Wether or not return the initial solution in the solution array.
The default is False.
predSol : N-sequence of vector, optional
Prediction solution used if the block iteration has no prediction
rule. The default is None.
Returns
-------
np.array of size (nIter+1, nBlocks or nBlocks+1)
The solution fo each block and each iteration. If initSol==True,
includes the initial solution u0 and has size (nIter+1, nBlocks+1).
If not, has size (nIter+1, nBlocks).
"""
nBlocks = self.nBlocks if nBlocks is None else nBlocks
if nBlocks == np.inf:
raise ValueError('need to specify a number of blocks somehow')
if self.M == 0:
# Symbolic evaluation
u0 = sy.symbols('u0', commutative=False)
u = np.zeros((nIter + 1, nBlocks + 1), dtype=sy.Expr)
u[:, 0] = u0
# Prediction
if len(self.predCoeffs) != 0:
pred = self.predictor
for n in range(nBlocks):
u[0, n + 1] = pred.symbol * u[0, n]
else:
u[0, 1:] = [sy.symbols(f'u_{n + 1}^0', commutative=False)
for n in range(nBlocks)]
# Iterations
for k in range(nIter):
for n in range(nBlocks):
for (nMod, kMod), b in self.coeffs:
u[k + 1, n + 1] += b.symbol * u[k + kMod, n + nMod]
u[k + 1, n + 1] = u[k + 1, n + 1].simplify()
else:
# Numerical evaluation
u0 = self.u0 if u0 is None else u0
if u0 is None:
raise ValueError(
'u0 must be provided for numerical evaluation'
' of a block iteration')
u0 = np.asarray(u0)
if self.nLam > 1:
u = np.zeros((nIter + 1, nBlocks + 1, self.nLam, self.M), dtype=u0.dtype)
else:
u = np.zeros((nIter + 1, nBlocks + 1, self.M), dtype=u0.dtype)
u[:, 0] = u0
# Prediction
if self.predictor is not None:
for n in range(nBlocks):
u[0, n + 1] = self.predictor(u[0, n])
else:
if predSol is None:
raise ValueError(
'evaluating block iteration without prediction rule'
' requires predSol list given as argument')
for n in range(nBlocks):
u[0, n + 1] = predSol[n]
# Iterations
for k in range(nIter):
for n in range(nBlocks):
for (nMod, kMod), blockOp in self.coeffs:
u[k + 1, n + 1] += blockOp(u[k + kMod, n + nMod])
if initSol:
return u
else:
return u[:, 1:]
def getRuntime(self, N, K, nProc, schedulerType='BLOCK-BY-BLOCK'):
K = self.checkK(N=N, K=K)
run = PintRun(blockIteration=self, nBlocks=N, kMax=K)
pool = TaskPool(run=run)
schedule = getSchedule(taskPool=pool, nProc=nProc, nPoints=N + 1, schedulerType=schedulerType)
return schedule.getRuntime()
def getPerformances(self, N, K, nProc=None, schedulerType='BLOCK-BY-BLOCK', verbose=False, run=None):
seqPropCost = self.propagator.cost
if (seqPropCost is None) or (seqPropCost == 0):
raise ValueError(
'no cost given for fine propagator,'
' cannot measure performances')
runtimeTs = seqPropCost * N
K = self.checkK(N=N, K=K)
print(f' -- computing {schedulerType} cost for K={K}')
if run is None:
run = PintRun(blockIteration=self, nBlocks=N, kMax=K)
elif not checkRunParameters(run, N, K):
run = PintRun(blockIteration=self, nBlocks=N, kMax=K)
elif checkRunParameters(run, N, K):
run = reduceRun(run, N, K)
pool = TaskPool(run=run)
schedule = getSchedule(
taskPool=pool, nProc=nProc, nPoints=N + 1,
schedulerType=schedulerType)
runtime = schedule.getRuntime()
nProc = schedule.nProc
if verbose:
graph = PintGraph(N, max(K), pool)
optimalRuntime = graph.longestPath()
print('=============================')
if self.name is None:
print(f'Block iteration: {self.update}')
else:
print(f'Block iteration: {self.name}')
print(f'Update: {self.update}')
print(f'Predictor: {self.predictor}')
print(f'N={N}, K={K} \n')
print(f'Runtime of schedule={schedule.NAME} for nProc={nProc}: {runtime}')
print(f'Runtime time-stepping: {runtimeTs} (This is currently not the correct value)')
print(f'Speedup of schedule={schedule.NAME} for nProc={nProc}: {(runtimeTs / runtime):.2f} \n')
print(f'Theoretical lower runtime bound: {optimalRuntime}')
print(
f'Theoretical maximum speedup compared to time stepping: {(runtimeTs / optimalRuntime):.2f} (This is currently not the correct value)')
print('=============================')
speedup = runtimeTs / runtime
efficiency = speedup / nProc
return speedup, efficiency, nProc, run
def plotGraphForOneBlock(self, N: int, K: int, plotBlock: int, plotIter: int, figSize: tuple = (6.4, 4.8),
saveFig: str = ""):
K = self.checkK(N=N, K=K)
run = PintRun(blockIteration=self, nBlocks=N, kMax=K)
pool = TaskPool(run=run)
pintGraph = PintGraph(N, max(K), pool)
pintGraph.plotGraphForOneBlockPlotly(k=plotIter, n=plotBlock)
# pintGraph.plotGraphForOneBlock(k=plotIter, n=plotBlock,
# figName=None if self.name is None else self.name + ' (graph)',
# figSize=figSize, saveFig=saveFig)
return run, pool, pintGraph
def plotGraph(self, N: int, K, figSize: tuple = (6.4, 4.8), saveFig: str = ""):
K = self.checkK(N=N, K=K)
run = PintRun(blockIteration=self, nBlocks=N, kMax=K)
pool = TaskPool(run=run)
pintGraph = PintGraph(N, max(K), pool)
pintGraph.plotGraphPlotly()
# pintGraph.plotGraph(figName=None if self.name is None else self.name + ' (graph)',
# figSize=figSize, saveFig=saveFig)
return run, pool, pintGraph
def plotSchedule(self, N: int, K, nProc: int, schedulerType: str = 'BLOCK-BY-BLOCK', figSize: tuple = (8, 4.8),
saveFig: str = ""):
K = self.checkK(N=N, K=K)
run = PintRun(blockIteration=self, nBlocks=N, kMax=K)
pool = TaskPool(run=run)
schedule = getSchedule(taskPool=pool, nProc=nProc, nPoints=N + 1, schedulerType=schedulerType)
return schedule.plotPlotly()
# schedule.plot(figName=None if self.name is None else self.name + f' ({schedule.NAME} schedule)',
# figSize=figSize, saveFig=saveFig)
def checkK(self, N, K):
if isinstance(K, int):
return [0] + [K for _ in range(N)]
elif isinstance(K, list) and len(K) == N:
return [0] + K
else:
raise Exception('K must be a list of length N or an integer.')
# -----------------------------------------------------------------------------
# Inherited specialized class
# -----------------------------------------------------------------------------
ALGORITHMS: Dict[str, BlockIteration] = {}
def register(cls):
ALGORITHMS[cls.__name__] = cls
return cls
DEFAULT_PROP = {
'implicit': 'phi**(-1)*chi',
'explicit': 'F'}
@register
class Parareal(BlockIteration):
needApprox = True
def __init__(self, implicitForm=True, approxPred=True, **blockOps):
if implicitForm:
B00 = "(phi**(-1)*chi-phiApprox**(-1)*chi) * u_{n}^k"
B01 = "phiApprox**(-1)*chi * u_{n}^{k+1}"
predictor = "phiApprox**(-1)*chi" if approxPred else None
else:
B00 = "(F-G) * u_{n}^k"
B01 = "G * u_{n}^{k+1}"
predictor = "G" if approxPred else None
update = f"{B00} + {B01}"
propagator = DEFAULT_PROP['implicit'] if implicitForm \
else DEFAULT_PROP['explicit']
super().__init__(update, propagator, predictor,
rules=None, name='Parareal', **blockOps)
@register
class ABJ(BlockIteration):
needApprox = True
def __init__(self, implicitForm=True, approxPred=True, **blockOps):
if implicitForm:
B00 = "phiApprox**(-1)*chi * u_{n}^k"
B10 = "(I-phiApprox**-1 * phi) * u_{n+1}^{k}"
predictor = "phiApprox**(-1)*chi" if approxPred else None
else:
B00 = "G * u_{n}^k"
B10 = "(I-G*F**(-1)) * u_{n}^{k+1}"
predictor = "G" if approxPred else None
update = f"{B10} + {B00}"
blockOps['I'] = I
propagator = DEFAULT_PROP['implicit'] if implicitForm \
else DEFAULT_PROP['explicit']
super().__init__(update, propagator, predictor,
rules=None, name='ABJ', **blockOps)
@register
class ABGS(BlockIteration):
needApprox = True
def __init__(self, implicitForm=True, approxPred=True, **blockOps):
if implicitForm:
B01 = "phiApprox**(-1)*chi * u_{n}^{k+1}"
B10 = "(I-phiApprox**-1 * phi) * u_{n+1}^{k}"
predictor = "phiApprox**(-1)*chi" if approxPred else None
else:
B01 = "G * u_{n}^{k+1}"
B10 = "(I-G*F**(-1)) * u_{n}^{k+1}"
predictor = "G" if approxPred else None
update = f"{B10} + {B01}"
blockOps['I'] = I
propagator = DEFAULT_PROP['implicit'] if implicitForm \
else DEFAULT_PROP['explicit']
super().__init__(update, propagator, predictor,
rules=None, name='ABGS', **blockOps)
@register
class TMG(BlockIteration):
needCoarse = True
def __init__(self, coarsePred=True, **blockOps):
omega = blockOps.get('omega', 1)
blockOps.update({'omega': omega})
phiC = "TCtoF * phiCoarse**(-1) * TFtoC"
B01 = f"{phiC}*chi"" * u_{n}^{k+1}"
B00 = f"omega*(phi**(-1)*chi - {phiC}*chi)"" * u_{n}^{k}"
B10 = f"(1-omega)*(I-{phiC}*phi)"" * u_{n+1}^{k}"
predictor = f"{phiC}*chi" if coarsePred else None
update = f"{B10} + {B01} + {B00}"
blockOps['I'] = I
rules = [("TFtoC * TCtoF", I)]
propagator = DEFAULT_PROP['implicit']
super().__init__(update, propagator, predictor,
rules=rules, name='TMG', **blockOps)
self.omega = omega
@register
class PFASST(BlockIteration):
needApprox = True
needCoarse = True
def __init__(self, coarsePred=True, **blockOps):
phiC = "TCtoF * phiCoarseApprox**(-1) * TFtoC"
B01 = f"{phiC}*chi"" * u_{n}^{k+1}"
B00 = f"(phiApprox**(-1)*chi - {phiC}*phi*phiApprox**(-1)*chi)"" * u_{n}^{k}"
B10 = f"(I-{phiC}*phi)*(I-phiApprox**(-1)*phi)"" * u_{n+1}^{k}"
predictor = f"{phiC}*chi" if coarsePred else None
update = f"{B10} + {B01} + {B00}"
blockOps['I'] = I
rules = [("TFtoC * TCtoF", I)]
propagator = DEFAULT_PROP['implicit']
super().__init__(update, propagator, predictor,
rules=rules, name='PFASST', **blockOps)