SolverLike._compute_residuals()   A
last analyzed

Complexity

Conditions 1

Size

Total Lines 20

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 1
c 1
b 0
f 0
dl 0
loc 20
rs 9.4285
1
import functools
2
3
import numpy as np
4
from scipy import optimize
5
6
from . import solutions
7
8
9
class SolverLike(object):
10
    """
11
    Class describing the protocol the all SolverLike objects should satisfy.
12
13
    Notes
14
    -----
15
    Subclasses should implement `solve` method as described below.
16
17
    """
18
19
    @property
20
    def basis_functions(self):
21
        r"""
22
        Functions used to approximate the solution to a boundary value problem.
23
24
        :getter: Return the current basis functions.
25
        :type: `basis_functions.BasisFunctions`
26
27
        """
28
        return self._basis_functions
29
30
    @staticmethod
31
    def _array_to_list(coefs_array, indices_or_sections, axis=0):
32
        """Split an array into a list of arrays."""
33
        return np.split(coefs_array, indices_or_sections, axis)
34
35
    @staticmethod
36
    def _evaluate_functions(funcs, points):
37
        """Evaluate a list of functions at some points."""
38
        return [func(points) for func in funcs]
39
40
    @classmethod
41
    def _evaluate_rhs(cls, funcs, nodes, problem):
42
        """
43
        Compute the value of the right-hand side of the system of ODEs.
44
45
        Parameters
46
        ----------
47
        basis_funcs : list(function)
48
        nodes : numpy.ndarray
49
        problem : TwoPointBVPLike
50
51
        Returns
52
        -------
53
        evaluated_rhs : list(float)
54
55
        """
56
        evald_funcs = cls._evaluate_functions(funcs, nodes)
57
        evald_rhs = problem.rhs(nodes, *evald_funcs, **problem.params)
58
        return evald_rhs
59
60
    @classmethod
61
    def _lower_boundary_residual(cls, funcs, problem, ts):
62
        evald_funcs = cls._evaluate_functions(funcs, ts)
63
        return problem.bcs_lower(ts, *evald_funcs, **problem.params)
64
65
    @classmethod
66
    def _upper_boundary_residual(cls, funcs, problem, ts):
67
        evald_funcs = cls._evaluate_functions(funcs, ts)
68
        return problem.bcs_upper(ts, *evald_funcs, **problem.params)
69
70
    @classmethod
71
    def _compute_boundary_residuals(cls, boundary_points, funcs, problem):
72
        boundary_residuals = []
73
        if problem.bcs_lower is not None:
74
            residual = cls._lower_boundary_residual_factory(funcs, problem)
75
            boundary_residuals.append(residual(boundary_points[0]))
76
        if problem.bcs_upper is not None:
77
            residual = cls._upper_boundary_residual_factory(funcs, problem)
78
            boundary_residuals.append(residual(boundary_points[1]))
79
        return boundary_residuals
80
81
    @classmethod
82
    def _compute_interior_residuals(cls, derivs, funcs, nodes, problem):
83
        interior_residuals = cls._interior_residuals_factory(derivs, funcs, problem)
84
        residuals = interior_residuals(nodes)
85
        return residuals
86
87
    @classmethod
88
    def _interior_residuals(cls, derivs, funcs, problem, ts):
89
        evaluated_lhs = cls._evaluate_functions(derivs, ts)
90
        evaluated_rhs = cls._evaluate_rhs(funcs, ts, problem)
91
        return [lhs - rhs for lhs, rhs in zip(evaluated_lhs, evaluated_rhs)]
92
93
    @classmethod
94
    def _interior_residuals_factory(cls, derivs, funcs, problem):
95
        return functools.partial(cls._interior_residuals, derivs, funcs, problem)
96
97
    @classmethod
98
    def _lower_boundary_residual_factory(cls, funcs, problem):
99
        return functools.partial(cls._lower_boundary_residual, funcs, problem)
100
101
    @classmethod
102
    def _upper_boundary_residual_factory(cls, funcs, problem):
103
        return functools.partial(cls._upper_boundary_residual, funcs, problem)
104
105
    def _assess_approximation(self, boundary_points, derivs, funcs, nodes, problem):
106
        """
107
        Parameters
108
        ----------
109
        basis_derivs : list(function)
110
        basis_funcs : list(function)
111
        problem : TwoPointBVPLike
112
113
        Returns
114
        -------
115
        resids : numpy.ndarray
116
117
        """
118
        interior_residuals = self._compute_interior_residuals(derivs, funcs,
119
                                                              nodes, problem)
120
        boundary_residuals = self._compute_boundary_residuals(boundary_points,
121
                                                              funcs, problem)
122
        return np.hstack(interior_residuals + boundary_residuals)
123
124
    def _compute_residuals(self, coefs_array, basis_kwargs, boundary_points, nodes, problem):
125
        """
126
        Return collocation residuals.
127
128
        Parameters
129
        ----------
130
        coefs_array : numpy.ndarray
131
        basis_kwargs : dict
132
        problem : TwoPointBVPLike
133
134
        Returns
135
        -------
136
        resids : numpy.ndarray
137
138
        """
139
        coefs_list = self._array_to_list(coefs_array, problem.number_odes)
140
        derivs, funcs = self._construct_approximation(basis_kwargs, coefs_list)
141
        resids = self._assess_approximation(boundary_points, derivs, funcs,
142
                                            nodes, problem)
143
        return resids
144
145
    def _construct_approximation(self, basis_kwargs, coefs_list):
146
        """
147
        Construct a collection of derivatives and functions that approximate
148
        the solution to the boundary value problem.
149
150
        Parameters
151
        ----------
152
        basis_kwargs : dict(str: )
153
        coefs_list : list(numpy.ndarray)
154
155
        Returns
156
        -------
157
        basis_derivs : list(function)
158
        basis_funcs : list(function)
159
160
        """
161
        derivs = self._construct_derivatives(coefs_list, **basis_kwargs)
162
        funcs = self._construct_functions(coefs_list, **basis_kwargs)
163
        return derivs, funcs
164
165
    def _construct_derivatives(self, coefs, **kwargs):
166
        """Return a list of derivatives given a list of coefficients."""
167
        return [self.basis_functions.derivatives_factory(coef, **kwargs) for coef in coefs]
168
169
    def _construct_functions(self, coefs, **kwargs):
170
        """Return a list of functions given a list of coefficients."""
171
        return [self.basis_functions.functions_factory(coef, **kwargs) for coef in coefs]
172
173
    def _solution_factory(self, basis_kwargs, coefs_array, nodes, problem, result):
174
        """
175
        Construct a representation of the solution to the boundary value problem.
176
177
        Parameters
178
        ----------
179
        basis_kwargs : dict(str : )
180
        coefs_array : numpy.ndarray
181
        problem : TwoPointBVPLike
182
        result : OptimizeResult
183
184
        Returns
185
        -------
186
        solution : SolutionLike
187
188
        """
189
        soln_coefs = self._array_to_list(coefs_array, problem.number_odes)
190
        soln_derivs = self._construct_derivatives(soln_coefs, **basis_kwargs)
191
        soln_funcs = self._construct_functions(soln_coefs, **basis_kwargs)
192
        soln_residual_func = self._interior_residuals_factory(soln_derivs,
193
                                                              soln_funcs,
194
                                                              problem)
195
        solution = solutions.Solution(basis_kwargs, soln_funcs, nodes, problem,
196
                                      soln_residual_func, result)
197
        return solution
198
199
    def solve(self, basis_kwargs, boundary_points, coefs_array, nodes, problem,
200
              **solver_options):
201
        """
202
        Solve a boundary value problem using the collocation method.
203
204
        Parameters
205
        ----------
206
        basis_kwargs : dict
207
            Dictionary of keyword arguments used to build basis functions.
208
        coefs_array : numpy.ndarray
209
            Array of coefficients for basis functions defining the initial
210
            condition.
211
        problem : bvp.TwoPointBVPLike
212
            A two-point boundary value problem (BVP) to solve.
213
        solver_options : dict
214
            Dictionary of options to pass to the non-linear equation solver.
215
216
        Return
217
        ------
218
        solution: solutions.SolutionLike
219
            An instance of the SolutionLike class representing the solution to
220
            the two-point boundary value problem (BVP)
221
222
        Notes
223
        -----
224
225
        """
226
        raise NotImplementedError
227
228
229
class Solver(SolverLike):
230
231
    def __init__(self, basis_functions):
232
        self._basis_functions = basis_functions
233
234
    def solve(self, basis_kwargs, boundary_points, coefs_array, nodes, problem,
235
              **solver_options):
236
        """
237
        Solve a boundary value problem using the collocation method.
238
239
        Parameters
240
        ----------
241
        basis_kwargs : dict
242
            Dictionary of keyword arguments used to build basis functions.
243
        coefs_array : numpy.ndarray
244
            Array of coefficients for basis functions defining the initial
245
            condition.
246
        problem : bvp.TwoPointBVPLike
247
            A two-point boundary value problem (BVP) to solve.
248
        solver_options : dict
249
            Dictionary of options to pass to the non-linear equation solver.
250
251
        Return
252
        ------
253
        solution: solutions.SolutionLike
254
            An instance of the SolutionLike class representing the solution to
255
            the two-point boundary value problem (BVP)
256
257
        Notes
258
        -----
259
260
        """
261
        result = optimize.root(self._compute_residuals,
262
                               x0=coefs_array,
263
                               args=(basis_kwargs, boundary_points, nodes, problem),
264
                               **solver_options)
265
        solution = self._solution_factory(basis_kwargs, result.x, nodes,
266
                                          problem, result)
267
        return solution
268