Coverage for pySDC/projects/RDC/equidistant_RDC.py: 91%
81 statements
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
« prev ^ index » next coverage.py v7.6.9, created at 2024-12-20 14:51 +0000
1import numpy as np
2from scipy.special import roots_legendre
3from scipy.interpolate import BarycentricInterpolator
5from pySDC.core.errors import CollocationError, ParameterError
6from pySDC.core.collocation import CollBase
9class MyBarycentricInterpolator(BarycentricInterpolator):
10 """
11 Overwrite BarycentricInterolator to inject custom weights
12 """
14 def __init__(self, xi, yi=None, weights=None, axis=0):
15 super(MyBarycentricInterpolator, self).__init__(xi, yi, axis)
16 self.wi = weights
19class Equidistant_RDC(CollBase):
20 """
21 Implements equidistant nodes with blended barycentric interpolation
23 Attributes:
24 fh_weights: blended FH weights for barycentric interpolation
25 """
27 def __init__(self, num_nodes, tleft=0, tright=1, **kwargs):
28 """
29 Initialization
31 Args:
32 num_nodes: number of nodes
33 tleft (float): left interval boundary (usually 0)
34 tright (float): right interval boundary (usually 1)
35 """
37 if type(num_nodes) is int:
38 max_d = 15
39 nnodes = num_nodes
40 else:
41 if type(num_nodes) is not tuple:
42 raise ParameterError('Expecting int or tuple for num_nodes parameter, got %s' % type(num_nodes))
43 if len(num_nodes) != 2:
44 raise ParameterError('Expecting 1 or 2 arguments for num_nodes, got %s' % num_nodes)
45 if type(num_nodes[0]) is not int:
46 raise ParameterError('Expecting int type for first num_nodes argument, got %s' % type(num_nodes[0]))
47 if type(num_nodes[1]) is not int:
48 raise ParameterError('Expecting int type for second num_nodes argument, got %s' % type(num_nodes[1]))
49 max_d = num_nodes[1]
50 nnodes = num_nodes[0]
52 if nnodes < 2:
53 raise CollocationError("Number of nodes should be at least 2 for equidistant, but is %d" % num_nodes)
55 try:
56 super(Equidistant_RDC, self).__init__(num_nodes=nnodes, node_type='EQUID', quad_type='LOBATTO', **kwargs)
57 except AttributeError:
58 pass
60 self.order = self.num_nodes
61 self.nodes = self._getNodes
63 d = min(self.num_nodes - 1, max_d)
64 self.fh_weights = self._getFHWeights(d)
65 self.weights = self._getWeights(tleft, tright)
67 self.Qmat = self._gen_Qmatrix
68 self.Smat = self._gen_Smatrix
69 self.delta_m = self._gen_deltas
70 self.left_is_node = True
71 self.right_is_node = True
73 def _getFHWeights(self, d):
74 """
75 Computes blended FH weights for barycentric interpolation
77 This method is ported from Georges Klein's matlab function
79 Args:
80 d (int): blending parameter
82 Returns:
83 numpy.ndarray: weights
84 """
86 n = self.num_nodes - 1
87 w = np.zeros(n + 1)
89 for k in range(0, n + 1):
90 ji = max(k - d, 0)
91 jf = min(k, n - d)
92 sumcoeff = []
93 for i in range(ji, jf + 1):
94 prodterm = []
95 for j in range(i, i + d + 1):
96 if j == k:
97 prodterm.append(1)
98 else:
99 prodterm.append(self.nodes[k] - self.nodes[j])
100 product = 1.0 / np.prod(prodterm)
101 sumcoeff.append((-1) ** (i - 1) * product)
102 y = sorted(sumcoeff, key=abs)
103 w[k] = np.sum(y)
105 return w
107 def _getWeights(self, a, b):
108 """
109 Computes weights using custom barycentric interpolation
111 Args:
112 a (float): left interval boundary
113 b (float): right interval boundary
115 Returns:
116 numpy.ndarray: weights of the collocation formula given by the nodes
117 """
118 if self.nodes is None:
119 raise CollocationError("Need nodes before computing weights, got %s" % self.nodes)
121 circ_one = np.zeros(self.num_nodes)
122 circ_one[0] = 1.0
123 tcks = []
124 for i in range(self.num_nodes):
125 tcks.append(MyBarycentricInterpolator(self.nodes, np.roll(circ_one, i), self.fh_weights))
127 # Generate evaluation points for quadrature
128 tau, omega = roots_legendre(self.num_nodes)
129 phi = (b - a) / 2 * tau + (b + a) / 2
131 weights = [np.sum((b - a) / 2 * omega * p(phi)) for p in tcks]
132 weights = np.array(weights)
134 return weights
136 @property
137 def _gen_Qmatrix(self):
138 """
139 Compute tleft-to-node integration matrix for later use in collocation formulation
141 Returns:
142 numpy.ndarray: matrix containing the weights for tleft to node
143 """
144 if self.nodes is None:
145 raise CollocationError(f"Need nodes before computing weights, got {self.nodes}")
146 M = self.num_nodes
147 Q = np.zeros([M + 1, M + 1])
149 # Generate Lagrange polynomials associated to the nodes
150 circ_one = np.zeros(self.num_nodes)
151 circ_one[0] = 1.0
152 tcks = []
153 for i in range(M):
154 tcks.append(MyBarycentricInterpolator(self.nodes, np.roll(circ_one, i), self.fh_weights))
156 # Generate evaluation points for quadrature
157 a, b = self.tleft, self.nodes[:, None]
158 tau, omega = roots_legendre(self.num_nodes)
159 tau, omega = tau[None, :], omega[None, :]
160 phi = (b - a) / 2 * tau + (b + a) / 2
162 # Compute quadrature
163 intQ = np.array([np.sum((b - a) / 2 * omega * p(phi), axis=-1) for p in tcks])
165 # Store into Q matrix
166 Q[1:, 1:] = intQ.T
168 return Q