Step 1 : generate \(Q\)-coefficients

📜 We denote by \(Q\)-coefficients (or Butcher table) what fully describes a multi-stage time stepping scheme (or Runge-Kutta method):

\[\begin{split}Q\text{-coefficients : } \begin{array} {c|c} \tau & Q \\ \hline & w^\top \end{array} \quad \Leftrightarrow \quad \begin{array} {c|c} c & A \\ \hline & b^\top \end{array} \quad\text{(Butcher table)}\end{split}\]

where \(\tau\) are the nodes, \(w\) the weights and \(Q\) … well, the \(Q\) matrix.

There is two approaches in qmat to generate those coefficients for many time-stepping schemes, from which you can choose depending on your needs and preferences.

Use a generic function

First, you can simply import the following :

[1]:
from qmat import genQCoeffs

Then generate \(Q\)-coefficients like this :

[2]:
# Coefficients or a collocation method
nodes, weights, Q = genQCoeffs("Collocation", nNodes=4, nodeType="LEGENDRE", quadType="RADAU-RIGHT")

print("node : ", nodes)
print("weights : ", weights)
print("Q : ")
print(Q)
node :  [0.08858796 0.40946686 0.78765946 1.        ]
weights :  [0.22046221 0.38819347 0.32884432 0.0625    ]
Q :
[[ 0.11299948 -0.04030922  0.02580238 -0.00990468]
 [ 0.234384    0.20689257 -0.04785713  0.01604742]
 [ 0.21668178  0.40612326  0.18903652 -0.0241821 ]
 [ 0.22046221  0.38819347  0.32884432  0.0625    ]]
[3]:
# Coefficients of a Runge-Kutta method (Butcher table)
c, b, A = genQCoeffs("RK4")

print("c : ", c)
print("b : ", b)
print("A : ")
print(A)
c :  [0.  0.5 0.5 1. ]
b :  [0.16666667 0.33333333 0.33333333 0.16666667]
A :
[[0.  0.  0.  0. ]
 [0.5 0.  0.  0. ]
 [0.  0.5 0.  0. ]
 [0.  0.  1.  0. ]]

Depending on its first given argument, genQCoeffs uses the associated \(Q\)-generator, potentially passing keyword arguments to instantiate.

📜 Checkout the tutorial on nodes generation for details about nodeType and quadType.

If some generator arguments are missing or wrongly given, then a descriptive error is raised, for instance :

[4]:
try:
    nodes, weights, Q = genQCoeffs("Collocation", nNodes=4, node_type="LEGENDRE", quadType="RADAU-RIGHT")
except Exception as e:
    print(f"{e.__class__.__name__}: {e}")
TypeError: Collocation.__init__() got an unexpected keyword argument 'node_type'. Did you mean 'nodeType'?
[5]:
try:
    nodes, weights, Q = genQCoeffs("Collocation", nNodes=4, nodeType="LEGENDRE")
except Exception as e:
    print(f"{e.__class__.__name__}: {e}")
TypeError: Collocation.__init__() missing 1 required positional argument: 'quadType'

🔔 Note that different aliases exists for each generators. For instance :

[6]:
# alias for Collocation
nodes, weights, Q = genQCoeffs("coll", nNodes=4, nodeType="LEGENDRE", quadType="RADAU-RIGHT")

# alias for RK4
c, b, A = genQCoeffs("ERK4")

All those aliases are uniques among \(Q\)-generators, and if the requested alias does not correspond to any generator, an appropriate error is raised :

[7]:
try:
    genQCoeffs("collocation")
except Exception as e:
    print(f"{e.__class__.__name__}: {e}")
ValueError: qType='collocation' is not available

Don’t hesitate to look at the API documentation for a full list of available generators …

Use generator objects

You can also directly use the generator classes, either by

  • importing the generator directly from its submodule

[8]:
from qmat.qcoeff.collocation import Collocation
coll = Collocation(nNodes=4, nodeType="LEGENDRE", quadType="RADAU-RIGHT")
  • retrieving it with one of its aliases from the Q_GENERATORS dictionary

[9]:
from qmat import Q_GENERATORS
Generator = Q_GENERATORS["coll"]
coll = Generator(nNodes=4, nodeType="LEGENDRE", quadType="RADAU-RIGHT")

In both case, you’ll instantiate an object that provides properties to access each of the given coefficients :

[10]:
print("nodes :", coll.nodes)
print("weights :", coll.weights)
print("Q :")
print(coll.Q)
nodes : [0.08858796 0.40946686 0.78765946 1.        ]
weights : [0.22046221 0.38819347 0.32884432 0.0625    ]
Q :
[[ 0.11299948 -0.04030922  0.02580238 -0.00990468]
 [ 0.234384    0.20689257 -0.04785713  0.01604742]
 [ 0.21668178  0.40612326  0.18903652 -0.0241821 ]
 [ 0.22046221  0.38819347  0.32884432  0.0625    ]]

… or a genCoeffs method providing all coefficients, used similarly as the genQCoeffs function :

[11]:
nodes, weights, Q = coll.genCoeffs()

Finally, you can use those coefficients to build a Runge-Kutta type time-stepper…