Skip to content

Commit

Permalink
Added two features to speedup PintRun simulations: 1. make it possibl…
Browse files Browse the repository at this point in the history
…e to use larger existing run for smaller runs 2. Use Lookup table for "standard" algorithms"
  • Loading branch information
Jens Hahne committed Aug 7, 2023
1 parent 62aeb81 commit 4e31755
Show file tree
Hide file tree
Showing 18 changed files with 279 additions and 35 deletions.
14 changes: 11 additions & 3 deletions blockops/iteration.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
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


# -----------------------------------------------------------------------------
Expand Down Expand Up @@ -209,7 +211,7 @@ def getRuntime(self, N, K, nProc, schedulerType='BLOCK-BY-BLOCK'):
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):
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):
Expand All @@ -221,7 +223,13 @@ def getPerformances(self, N, K, nProc=None, schedulerType='BLOCK-BY-BLOCK', verb
K = self.checkK(N=N, K=K)
print(f' -- computing {schedulerType} cost for K={K}')

run = PintRun(blockIteration=self, nBlocks=N, kMax=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,
Expand Down Expand Up @@ -250,7 +258,7 @@ def getPerformances(self, N, K, nProc=None, schedulerType='BLOCK-BY-BLOCK', verb

speedup = runtimeTs / runtime
efficiency = speedup / nProc
return speedup, efficiency, 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 = ""):
Expand Down
Binary file added blockops/lookup/ApproxBlockGaussSeidel.pickle
Binary file not shown.
Binary file not shown.
Binary file added blockops/lookup/BlockJacobi.pickle
Binary file not shown.
Binary file added blockops/lookup/BlockJacobiNoPredictor.pickle
Binary file not shown.
Binary file added blockops/lookup/BlockJacobiPredictorI.pickle
Binary file not shown.
Binary file added blockops/lookup/PFASST.pickle
Binary file not shown.
Binary file added blockops/lookup/PararealFG.pickle
Binary file not shown.
Binary file added blockops/lookup/PararealFGNoPredictor.pickle
Binary file not shown.
Binary file added blockops/lookup/PararealPhiChi.pickle
Binary file not shown.
Binary file added blockops/lookup/PararealSCFGNoPredictor.pickle
Binary file not shown.
93 changes: 93 additions & 0 deletions blockops/lookup/lookupTable.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import os
import pickle

from blockops.utils.checkRun import checkRunParameters, reduceRun
#from blockops import BlockIteration, PintRun

# Lookup table of all lookups that can be used
# Key : Tuple of identifying components of a block iteration
# Value : Path to the lookup
lookupTable = {
((0, 0), '(\\phi**(-1) - \\phi_{\\Delta}**(-1))*\\chi', (0, 1), '\\phi_{\\Delta}**(-1)*\\chi', '\\phi**(-1)*\\chi',
'\\phi_{\\Delta}**(-1)*\\chi'): os.path.dirname(os.path.realpath(__file__)) + '/PararealPhiChi.pickle',
((0, 0), 'F - G', (0, 1), 'G', 'F', 'None'): os.path.dirname(
os.path.realpath(__file__)) + '/PararealFGNoPredictor.pickle',
((0, 0), 'F - G', (0, 1), 'G', 'F', 'G'): os.path.dirname(os.path.realpath(__file__)) + '/PararealFG.pickle',
((0, 0), 'F - P*G*R', (0, 1), 'P*G*R', 'F', 'None'): os.path.dirname(
os.path.realpath(__file__)) + '/PararealSCFGNoPredictor.pickle',
((0, 0), 'F - P*G*R', (0, 1), 'P*G*R', 'F', 'P*G*R'): os.path.dirname(
os.path.realpath(__file__)) + '/testPararealSCFG.pickle',
((0, 0), '\\phi_{\\Delta}**(-1)*\\chi', (1, 0), '1 - \\phi_{\\Delta}**(-1)*\\phi', '\\phi**(-1)*\\chi',
'1'): os.path.dirname(os.path.realpath(__file__)) + '/BlockJacobiPredictorI.pickle',
((0, 0), '\\phi_{\\Delta}**(-1)*\\chi', (1, 0), '1 - \\phi_{\\Delta}**(-1)*\\phi', '\\phi**(-1)*\\chi',
'None'): os.path.dirname(os.path.realpath(__file__)) + '/BlockJacobiNoPredictor.pickle',
((0, 0), '\\phi_{\\Delta}**(-1)*\\chi', (1, 0), '1 - \\phi_{\\Delta}**(-1)*\\phi', '\\phi**(-1)*\\chi',
'\\phi_{\\Delta}**(-1)*\\chi'): os.path.dirname(os.path.realpath(__file__)) + '/BlockJacobi.pickle',
((0, 1), '\\phi_{\\Delta}**(-1)*\\chi', (1, 0), '1 - \\phi_{\\Delta}**(-1)*\\phi', '\\phi**(-1)*\\chi',
'None'): os.path.dirname(os.path.realpath(__file__)) + '/ApproxBlockGaussSeidelNoPredictor.pickle',
((0, 1), '\\phi_{\\Delta}**(-1)*\\chi', (1, 0), '1 - \\phi_{\\Delta}**(-1)*\\phi', '\\phi**(-1)*\\chi',
'\\phi_{\\Delta}**(-1)*\\chi'): os.path.dirname(os.path.realpath(__file__)) + '/ApproxBlockGaussSeidel.pickle',
((1, 0), '(1 - T_C^F*\\tilde{\\phi}_C**(-1)*T_F^C*\\phi)*(1 - \\tilde{\\phi}**(-1)*\\phi)', (0, 1),
'T_C^F*\\tilde{\\phi}_C**(-1)*T_F^C*\\chi', (0, 0),
'-T_C^F*\\tilde{\\phi}_C**(-1)*T_F^C*\\phi*\\tilde{\\phi}**(-1)*\\chi + \\tilde{\\phi}**(-1)*\\chi',
'\\phi**(-1)*\\chi', 'T_C^F*\\tilde{\\phi}_C**(-1)*T_F^C*\\chi'): os.path.dirname(
os.path.realpath(__file__)) + '/PFASST.pickle'
}


def picklePintRun(fileName: str, run: object, blockIteration: object) -> None:
"""
Pickles an existing PinTRun and saves it to "fileName"
:param fileName: Where to save
:param run: PintRun to pickle
:param blockIteration: Associated block iteration
"""
with open(os.path.dirname(os.path.realpath(__file__)) + '/' + fileName, "wb") as file_:
pickle.dump(run, file_, -1)
print('To add the block Iteration to the lookup table add the following part in the lookupTable dictionary:')
print(createTupleFromBlockIteration(blockIteration), ':',
'os.path.dirname(os.path.realpath(__file__))' + f"+ '/{fileName}'")


def createTupleFromBlockIteration(blockIteration: object) -> tuple:
"""
Creates tuple from identifying settings of a BlockIteration
:param blockIteration: Block iteration
:return: Tuple of identifying settings
"""
tmp = []
for key, value in blockIteration.blockCoeffs.items():
tmp.append(key)
tmp.append(value.name)
tmp.append(blockIteration.propagator.name)
if blockIteration.predictor is not None:
tmp.append(blockIteration.predictor.name)
else:
tmp.append('None')
return tuple(tmp)


def findEntry(blockIteration: object, N: int, K: list) -> tuple:
"""
Checks if loopup entry exists for given block iteration.
If it exists, checks if the lookup entry computed enough iterations and blocks
:param blockIteration: Blockiteration
:param N: Number of blocks
:param K: List of number of iterations per block
:return: Found and PintRun if found
"""
path = lookupTable.get(createTupleFromBlockIteration(blockIteration), None)

if path is None or not os.path.isfile(path):
return False, None

load = pickle.load(open(path, "rb", -1))

if not checkRunParameters(load, N, K):
return False, None

reduceRun(load, N, K, useCopy=False)
return True, load
Binary file added blockops/lookup/testPararealSCFG.pickle
Binary file not shown.
16 changes: 12 additions & 4 deletions blockops/run.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

# BlockOps import
from blockops.utils.expr import Generator, getFactorizedRule
from blockops.lookup.lookupTable import findEntry


class PintRun:
Expand All @@ -17,7 +18,7 @@ class PintRun:
block iteration.
"""

def __init__(self, blockIteration, nBlocks: int, kMax: list) -> None:
def __init__(self, blockIteration, nBlocks: int, kMax: list, useLookup: bool = True) -> None:
"""
Constructor to initialize a parallel-in-time run.
Expand Down Expand Up @@ -60,10 +61,17 @@ def __init__(self, blockIteration, nBlocks: int, kMax: list) -> None:
newValue = self.createSymbolForUnk(n=i + 1, k=z)
self.multiStepRule[newKey] = newValue

# Iterate over all expression
self.createExpressions()
# Create blockRules and facBlockRules if no lookup entry exists
# Otherwise load both from the lookup entry
lookUp, res = findEntry(blockIteration, nBlocks, kMax)
if not lookUp or not useLookup:
# Iterate over all expression
self.createExpressions()

self.factorizeBlockRules()
self.factorizeBlockRules()
else:
self.blockRules = res.blockRules
self.facBlockRules = res.facBlockRules

def createSymbolForUnk(self, n: int, k: int) -> sy.Symbol:
"""
Expand Down
24 changes: 12 additions & 12 deletions blockops/tests/test_blockMethods.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ def testPararealPhiChiNoPredictor(self):
update="(phi**(-1)-phiD**(-1))*chi*u_{n}^k + phiD**(-1)*chi* u_{n}^{k+1}",
propagator=phi ** (-1) * chi,
**blockOps)
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PararealPhiChiNoPredictor', run=run, pool=pool)

Expand All @@ -54,7 +54,7 @@ def testPararealPhiChi(self):
update="(phi**(-1)-phiD**(-1))*chi*u_{n}^k + phiD**(-1)*chi* u_{n}^{k+1}",
propagator=phi ** (-1) * chi, predictor=phiD ** (-1) * chi,
**blockOps)
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PararealPhiChi', run=run, pool=pool)

Expand All @@ -65,7 +65,7 @@ def testPararealFGNoPredictor(self):
propagator=f,
rules=rules, # list of rules (optional)
f=f, g=g)
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PararealFGNoPredictor', run=run, pool=pool)

Expand All @@ -76,7 +76,7 @@ def testPararealFG(self):
propagator=f, predictor=g,
rules=rules, # list of rules (optional)
f=f, g=g)
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PararealFG', run=run, pool=pool)

Expand All @@ -86,7 +86,7 @@ def testPararealSCFGNoPredictor(self):
propagator=f,
rules=rules, # list of rules (optional)
f=f, g=g, p=p, r=r)
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PararealSCFGNoPredictor', run=run, pool=pool)

Expand All @@ -97,7 +97,7 @@ def testPararealSCFG(self):
propagator=f, predictor="p*g*r",
rules=rules, # list of rules (optional)
f=f, g=g, p=p, r=r)
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=parareal, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PararealSCFG', run=run, pool=pool)

Expand All @@ -106,7 +106,7 @@ def testBlockJacobiPredictorI(self):
"phiD**(-1)*chi*u_{n}^k + (I-phiD**(-1)*phi)* u_{n+1}^{k}",
propagator=phi ** (-1) * chi, predictor=I,
**blockOps)
run = PintRun(blockIteration=blockJacobi, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=blockJacobi, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='BlockJacobiPredictorI', run=run, pool=pool)

Expand All @@ -115,7 +115,7 @@ def testBlockJacobiNoPredictor(self):
"phiD**(-1)*chi*u_{n}^k + (I-phiD**(-1)*phi)* u_{n+1}^{k}",
propagator=phi ** (-1) * chi,
**blockOps)
run = PintRun(blockIteration=blockJacobi, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=blockJacobi, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='BlockJacobiNoPredictor', run=run, pool=pool)

Expand All @@ -125,7 +125,7 @@ def testBlockJacobi(self):
"phiD**(-1)*chi*u_{n}^k + (I-phiD**(-1)*phi)* u_{n+1}^{k}",
propagator=phi ** (-1) * chi, predictor=phiD ** (-1) * chi,
**blockOps)
run = PintRun(blockIteration=blockJacobi, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=blockJacobi, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='BlockJacobi', run=run, pool=pool)

Expand All @@ -135,7 +135,7 @@ def testApproxBlockGaussSeidelNoPredictor(self):
update="phiD**(-1)*chi*u_{n}^{k+1} + (I-phiD**(-1)*phi)* u_{n+1}^{k}",
propagator=phi ** (-1) * chi,
**blockOps)
run = PintRun(blockIteration=approxBlockGaussSeidel, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=approxBlockGaussSeidel, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='ApproxBlockGaussSeidelNoPredictor', run=run, pool=pool)

Expand All @@ -145,7 +145,7 @@ def testApproxBlockGaussSeidel(self):
update="phiD**(-1)*chi*u_{n}^{k+1} + (I-phiD**(-1)*phi)* u_{n+1}^{k}",
propagator=phi ** (-1) * chi, predictor=phiD ** (-1) * chi,
**blockOps)
run = PintRun(blockIteration=approxBlockGaussSeidel, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=approxBlockGaussSeidel, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='ApproxBlockGaussSeidel', run=run, pool=pool)

Expand All @@ -154,6 +154,6 @@ def testPFASST(self):
prob.setApprox('RungeKutta', rkScheme='BE')
prob.setCoarseLevel(2)
algo = prob.getBlockIteration('PFASST')
run = PintRun(blockIteration=algo, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4])
run = PintRun(blockIteration=algo, nBlocks=nBlocks, kMax=[0, 4, 4, 4, 4], useLookup=False)
pool = TaskPool(run=run)
checkResults(method='PFASST', run=run, pool=pool)
54 changes: 54 additions & 0 deletions blockops/utils/checkRun.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import numpy as np
import copy

#from blockops import PintRun


def checkRunParameters(run: object, N: int, K: list) -> bool:
"""
Check if the run has been computed for enough iterations and blocks
:param run: PintRun to check
:param N: Number of blocks
:param K: List of number of iterations per block
:return: True if check passes, else false
"""
if N > run.nBlocks or len(K) > len(run.kMax) or np.max(np.array(K) > np.array(run.kMax)[:len(K)]):
return False
else:
return True


def reduceRun(run: object, N: int, K: list, useCopy: bool = True) -> object:
"""
Reduces an existing run to a smaller or equal number of iterations and/or blocks.
Uses a copy if "useCopy" is True, else the object is used.
:param run: PintRun to reduce
:param N: Number of blocks
:param K: List of number of iterations per block
:param useCopy: Use a copy or work on the object
:return: reduced PintRun
"""
if useCopy:
newRun = copy.deepcopy(run) # Can be expensive
else:
newRun = run

tmpBlockRules = {}
tmpFacBlockRules = {}
for n in range(N + 1):
if (n, 0) in run.blockRules:
tmpBlockRules[(n, 0)] = run.blockRules[(n, 0)]
tmpFacBlockRules[(n, 0)] = run.facBlockRules[(n, 0)]

for k in range(max(K)):
for n in range(N):
if (n + 1, k + 1) in run.blockRules:
tmpBlockRules[(n + 1, k + 1)] = run.blockRules[(n + 1, k + 1)]
tmpFacBlockRules[(n + 1, k + 1)] = run.facBlockRules[(n + 1, k + 1)]

newRun.blockRules = tmpBlockRules
newRun.facBlockRules = tmpFacBlockRules

return newRun
Loading

0 comments on commit 4e31755

Please sign in to comment.