Source code for pycollocation.solvers

import numpy as np

from . import boundary_value_problems


[docs]class Solver(object): """Base class for all Solvers.""" def __init__(self, model): """ Create an instance of the Solver class. Parameters ---------- model : models.Model An instance of models.Model to solve. """ self.model = model @property
[docs] def coefficients(self): """ Coefficients to use when constructing the approximating polynomials. :getter: Return the `coefficients` attribute. :type: dict """ return self._coefs_array_to_dict(self.result.x, self.degrees)
@property
[docs] def derivatives(self): """ Derivatives of the approximating basis functions. :getter: Return the `derivatives` attribute. :type: dict """ return self._construct_basis_derivs(self.coefficients, self.kind, self.domain)
@property
[docs] def functions(self): """ The basis functions used to approximate the solution to the model. :getter: Return the `functions` attribute. :type: dict """ return self._construct_basis_funcs(self.coefficients, self.kind, self.domain)
@property def model(self): """ Symbolic representation of the model to solve. :getter: Return the current model. :setter: Set a new model to solve. :type: models.Model """ return self._model @model.setter
[docs] def model(self, model): """Set a new model to solve.""" self._model = self._validate_model(model)
@property
[docs] def result(self): """ Result object :getter: Return the current result object. :type: optimize.Result """ return self._result
@property
[docs] def residual_functions(self): """ Residual functions :getter: Return the current residual functions. """ return self._construct_residual_funcs(self.derivatives, self.functions)
@classmethod def _basis_derivative_factory(cls, *args, **kwargs): """Factory method for constructing derivatives of basis functions.""" raise NotImplementedError @classmethod def _basis_function_factory(cls, *args, **kwargs): """Factory method for constructing basis functions.""" raise NotImplementedError @staticmethod def _coefs_array_to_dict(coefs_array, degrees): """Split array of coefs into dict mapping symbols to coef arrays.""" precondition = coefs_array.size == sum(degrees.values()) + len(degrees) assert precondition, "The coefs array must conform with degree list!" coefs_dict = {} for var, degree in degrees.items(): coefs_dict[var] = coefs_array[:degree+1] coefs_array = coefs_array[degree+1:] postcondition = len(coefs_dict) == len(degrees) assert postcondition, "Length of coefs and degree lists must be equal!" return coefs_dict def _coefs_dict_to_array(self, coefs_dict): """Cast dict mapping symbol to coef arrays into array of coefs.""" coefs_list = [] for var in coefs_dict.keys(): coef_array = coefs_dict[var] coefs_list.append(coef_array) return np.hstack(coefs_list) def _construct_basis_funcs(self, coefs, *args, **kwargs): """Return dict of basis functions given coefficients.""" basis_funcs = {} for var, coef in coefs.items(): basis_funcs[var] = self._basis_function_factory(coef, *args, **kwargs) return basis_funcs def _construct_basis_derivs(self, coefs, *args, **kwargs): """Return dict of basis function derivatives given coefficients.""" basis_derivs = {} for var, coef in coefs.items(): basis_derivs[var] = self._basis_derivative_factory(coef, *args, **kwargs) return basis_derivs def _construct_residual_funcs(self, basis_derivs, basis_funcs): """Return dict of residual functions for given basis funcs and derivs.""" residual_funcs = {} for var, basis_deriv in basis_derivs.items(): residual_funcs[var] = self._residual_function_factory(var, basis_derivs, basis_funcs) return residual_funcs def _evaluate_basis_funcs(self, basis_funcs, points): """Return a list of basis functions evaluated at some points.""" return [basis_funcs[var](points) for var in self.model.dependent_vars] def _evaluate_boundary_residuals(self, basis_funcs, domain): """Return a list of boundary conditions evaluated on the domain.""" lower_residual = self._evaluate_lower_boundary_residual(basis_funcs, domain[0]) upper_residual = self._evaluate_upper_boundary_residual(basis_funcs, domain[1]) residuals = [] if lower_residual is not None: residuals.append(lower_residual) if upper_residual is not None: residuals.append(upper_residual) return residuals def _evaluate_lower_boundary_residual(self, basis_funcs, lower_bound): """Return the lower boundary condition evaluated on the domain.""" if self.model._lower_boundary_condition is not None: args = (self._evaluate_basis_funcs(basis_funcs, lower_bound) + list(self.model.params.values())) return self.model._lower_boundary_condition(lower_bound, *args) def _evaluate_residual_funcs(self, residual_funcs, nodes): """Return a list of residual functions evaluated at collocation nodes.""" return [residual_funcs[var](nodes[var]) for var in self.model.dependent_vars] def _evaluate_upper_boundary_residual(self, basis_funcs, upper_bound): """Return the upper boundary condition evaluated on the domain.""" if self.model._upper_boundary_condition is not None: args = (self._evaluate_basis_funcs(basis_funcs, upper_bound) + list(self.model.params.values())) return self.model._upper_boundary_condition(upper_bound, *args) def _residual_function_factory(self, var, basis_derivs, basis_funcs): """Generate the residual function for a given variable.""" def residual_function(t): """Residual function evaluated at array of points t.""" args = (self._evaluate_basis_funcs(basis_funcs, t) + list(self.model.params.values())) return basis_derivs[var](t) - self.model._rhs_functions(var)(t, *args) return residual_function @staticmethod def _validate_model(model): """Validate the dictionary of parameters.""" if not isinstance(model, boundary_value_problems.BoundaryValueProblem): mesg = "Attribute 'model' must have type BoundaryValueProblem, not {}" raise AttributeError(mesg.format(model.__class__)) else: return model