Step 3 : generate \(Q_\Delta\) coefficientsο
π We denote by \(Q_\Delta\) the approximation of a given \(Q\) matrix related to any \(Q\)-coefficients (from collocation, RK, β¦) :
There is two approaches to generate \(Q_\Delta\) approximations, from which you can choose in function of your needs and preferences.
Use a generic functionο
The quick easy way, simply import :
[1]:
from qmat import genQDeltaCoeffs
Then considering, e.g, the following collocation method :
[2]:
from qmat.qcoeff.collocation import Collocation
coll = Collocation.getInstance() # use default parameters : 4 LEGENDRE RADAU-RIGHT nodes
we can generate the \(Q_\Delta\) matrix from a Backward Euler discretization between the nodes (as in the original SDC method) :
[3]:
QDelta = genQDeltaCoeffs("BE", nodes=coll.nodes)
print(QDelta)
[[0.08858796 0. 0. 0. ]
[0.08858796 0.3208789 0. 0. ]
[0.08858796 0.3208789 0.3781926 0. ]
[0.08858796 0.3208789 0.3781926 0.21234054]]
β¦ or get the LU approximation from [Weiser, 2015] :
[4]:
QDelta = genQDeltaCoeffs("LU", Q=coll.Q)
print(QDelta)
[[0.11299948 0. 0. 0. ]
[0.234384 0.29050213 0. 0. ]
[0.21668178 0.48341808 0.30825766 0. ]
[0.22046221 0.46683684 0.44141588 0.11764706]]
β¦ or the diagonal approximation from [van der Houwen & Sommeijer, 1991] :
[5]:
QDelta = genQDeltaCoeffs("VDHS", nNodes=coll.nNodes, nodeType=coll.nodeType, quadType=coll.quadType)
print(QDelta)
[[0.32049937 0. 0. 0. ]
[0. 0.08915379 0. 0. ]
[0. 0. 0.18173956 0. ]
[0. 0. 0. 0.2333628 ]]
β¦ or even some magical diagonal coefficients obtained with a black box optimizer that does not exists anymore from [Speck, 2021]:
[6]:
QDelta = genQDeltaCoeffs("MIN3", nNodes=coll.nNodes, nodeType=coll.nodeType, quadType=coll.quadType)
print(QDelta)
[[0.31987868 0. 0. 0. ]
[0. 0.08887606 0. 0. ]
[0. 0. 0.18123663 0. ]
[0. 0. 0. 0.23273925]]
π β¦ and yes, the
MIN3coefficients are almost the same as those fromVDHS, even if both where obtained independently ! But who really never ever re-invented the wheel in research π
Note that depending on the requested approximation, different arguments may be required for the \(Q_\Delta\)-generator called in the background. If not provided, a descriptive exception is raised, for instance :
[7]:
try:
QDelta = genQDeltaCoeffs("BE")
except Exception as e:
print(f"{e.__class__.__name__}: {e}")
TypeError: TimeStepping.__init__() missing 1 required positional argument: 'nodes'
To allow more generic call, it is possible to give keyword arguments that will be used only for some \(Q_\Delta\) matrices :
[8]:
for qdType in ["BE", "LU", "VDHS", "MIN3"]:
QDelta = genQDeltaCoeffs(qdType,
Q=coll.Q, nodes=coll.nodes, nNodes=coll.nNodes, nodeType=coll.nodeType, quadType=coll.quadType)
And to make it more simple, it is also possible to only give a QGenerator object as qGen keyword argument only, and qmat will extract all required arguments from it :
[9]:
for qdType in ["BE", "LU", "VDHS", "MIN3"]:
QDelta = genQDeltaCoeffs(qdType, qGen=coll)
π As for \(Q\)-generators, different unique aliases exists for each \(Q_\Delta\)-generators. For instance :
[10]:
QDelta = genQDeltaCoeffs("IE", nodes=coll.nodes) # equivalent to BE
Donβt hesitate to look at the API documentation for a complete list of available generators β¦
Use generator objectsο
As for \(Q\)-generators, you can also retrieve the \(Q_\Delta\)-generators with their classes, using one of the following approaches :
import the generator directly from its submodule
[11]:
from qmat.qdelta.timestepping import BE
approx = BE(nodes=coll.nodes)
retrieve it with one of its aliases from the
QDELTA_GENERATORSdictionary
[12]:
from qmat import QDELTA_GENERATORS
Generator = QDELTA_GENERATORS["IE"]
approx = Generator(nodes=coll.nodes)
In both case, youβll instantiate an object that provides \(Q_\Delta\) matrix through a getQDelta method :
[13]:
print(approx.getQDelta())
[[0.08858796 0. 0. 0. ]
[0.08858796 0.3208789 0. 0. ]
[0.08858796 0.3208789 0.3781926 0. ]
[0.08858796 0.3208789 0.3781926 0.21234054]]
β οΈ While the \(Q_\Delta\) generators do have a
QDeltaattribute, it is usually zero-initialized before calling thegetQDeltamethod !
The reason behind the use of a method here (and not a property) is that some approximations can vary depending on a given iteration number, e.g :
[14]:
approx = QDELTA_GENERATORS["MIN-SR-FLEX"](nNodes=coll.nNodes, nodeType=coll.nodeType, quadType=coll.quadType)
print("Approximation for k=1 :")
print(approx.getQDelta(k=1))
print("Approximation for k=2 :")
print(approx.getQDelta(k=2))
Approximation for k=1 :
[[0.08858796 0. 0. 0. ]
[0. 0.40946686 0. 0. ]
[0. 0. 0.78765946 0. ]
[0. 0. 0. 1. ]]
Approximation for k=2 :
[[0.04429398 0. 0. 0. ]
[0. 0.20473343 0. 0. ]
[0. 0. 0.39382973 0. ]
[0. 0. 0. 0.5 ]]
However this corresponds to some very specific methods, in practice most of the time any \(k\) argument given to the getQDelta method will be ignored. Finally, note that all \(Q_\Delta\)-generator objects have a genCoeffs method, which per default provides the same result as the getQDelta method, but has some more functionalities (covered in a later tutorial).
π As for the
genQDeltaCoeffsfunction before, you can also use theqGenkeyword argument for generic calls that will take only the name of theQDeltaGeneratoras parameter.
Now, you can use those \(Q\)-coefficients and \(Q_\Delta\) approximations to build a Spectral Deferred Correction type time-stepper β¦