def test(self, test_solution): """Test that the boundary value problem definition is self consistent, and tests whether test_solution is consistent with the bvp definition. This requires some legal values for the parameters and thus requires a test solution. Parameters ---------- test_solution : :class:`Solution` solution to be tested with """ if self._num_parameters > 0: tools.argShapeTest(test_solution.parameters, (self._num_parameters,), "parameter array", "Should be (num_parameters,)") tools.argShapeTest(test_solution.solution, (self._num_ODE,len(test_solution.mesh)), "solution array", "Should be (num_ODE,length(mesh))") if not (self._boundary_points.shape == (2,)): raise ValueError("This boundary value problem definition must be given exactly two boundary points, but got: " + self._boundary_points +" as the boundary values") #at this point we want to check the call backs to make sure they take the right arguments and return the right things if self._num_parameters ==0: f = self._function(test_solution.mesh[0], test_solution.solution[:,0]) tools.argShapeTest(f, (self._num_ODE,), "function callback return", "Should be (num_ODE,)") bca,bcb = self._boundary_conditions(test_solution.solution[:,0], test_solution.solution[:,-1]) tools.argShapeTest(bca, (self._num_left_boundary_conditions,), "Boundary conditions callback first return", "Should be (num_left_boundary_conditions,)") tools.argShapeTest(bcb, (self._num_ODE + self._num_parameters - self._num_left_boundary_conditions,), "Boundary conditions callback second return", "Should be (num_ODE + num_parameters - num_left_boundary_conditions,)") if self.has_function_derivative: df = self._function_derivative(test_solution.mesh[0], test_solution.solution[:,0]) tools.argShapeTest(df, (self._num_ODE, self._num_ODE), "function derivative callback first return", "Should be (num_ODE, num_ODE)") if self.has_boundary_conditions_derivative: dbca, dbcb = self._boundary_conditions_derivative(test_solution.solution[:,0], test_solution.solution[:,0]) tools.argShapeTest(dbca, (self._num_left_boundary_conditions, self._num_ODE), "Boundary conditions derivative callback first return", "Should be (num_left_boundary_conditions, num_ODE)") tools.argShapeTest(dbcb, (self._num_ODE + self._num_parameters - self._num_left_boundary_conditions, self._num_ODE), "Boundary conditions derivative callback second return", "Should be (num_ODE + num_parameters - num_left_boundary_conditions, num_ODE)") else: ## if unknown parameters are used, things should be a little different f = self._functionp(test_solution.mesh[0], test_solution.solution[:,0], test_solution.parameters) tools.argShapeTest(f, (self._num_ODE,), "function callback return", "Should be (num_ODE,)") bca, bcb = self._boundary_conditionsp(test_solution.solution[:,0], test_solution.solution[:,0], test_solution.parameters) tools.argShapeTest(bca, (self._num_left_boundary_conditions,), "Boundary conditions callback first return", "Should be (num_left_boundary_conditions,)") tools.argShapeTest(bcb, (self._num_ODE + self._num_parameters - self._num_left_boundary_conditions,), "Boundary conditions callback second return", "Should be (num_ODE + num_parameters - num_left_boundary_conditions,)") if self.has_function_derivative: df, dfp = self._function_derivativep(test_solution.mesh[0], test_solution.solution[:,0], test_solution.parameters) tools.argShapeTest(df, (self._num_ODE, self._num_ODE), "function derivative callback first return", "Should be (num_ODE, num_ODE)") tools.argShapeTest(dfp, (self._num_ODE, self._num_parameters), "function derivative callback second return", "Should be (num_ODE ,num_parameters)") if self.has_boundary_conditions_derivative: dbca, dbcb, dbcap, dbcbp = self._boundary_conditions_derivativep(test_solution.solution[:,0], test_solution.solution[:,0], test_solution.parameters) tools.argShapeTest(dbca, (self._num_left_boundary_conditions, self._num_ODE), "boundary conditions derivative callback first return", "Should be (num_left_boundary_conditions, num_ODE)") tools.argShapeTest(dbcb, (self._num_ODE + self._num_parameters - self._num_left_boundary_conditions, self._num_ODE), "boundary conditions derivative callback second return", "Should be (num_ODE + num_parameters - num_left_boundary_conditions, num_ODE)") tools.argShapeTest(dbcap, (self._num_left_boundary_conditions, self._num_parameters), "boundary conditions derivative callback third return", "Should be (num_left_boundary_conditions, num_parameters)") tools.argShapeTest(dbcbp, (self._num_ODE + self._num_parameters - self._num_left_boundary_conditions, self._num_parameters), "boundary conditions derivative callback fourth return", "Should be (num_ODE + num_parameters - num_left_boundary_conditions, num_ODE, num_parameters)") # test derivatives step = 1e-8 places = 4 # chose the point near the middle of the test_solution to check the derivatives middlePoint = numpy.round(test_solution.mesh.size * .61, 0) if self.has_function_derivative: if self._num_parameters > 0: func_derivative_calc, func_param_derivative_calc = self._function_derivativep(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint], test_solution.parameters) func_derivative_num = numpy.zeros((self._num_ODE,self._num_ODE)) func_param_derivative_num = numpy.zeros( (self._num_ODE, self._num_parameters)) for i in range(self._num_ODE): delta = numpy.zeros(self._num_ODE) delta[i] += step point1 = self._functionp(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint], test_solution.parameters) point2 = self._functionp(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint] + delta, test_solution.parameters) func_derivative_num[:, i] = (point2 - point1)/step for i in range(self._num_parameters): delta = numpy.zeros(self._num_parameters) delta[i] += step point1 = self._functionp(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint], test_solution.parameters) point2 = self._functionp(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint], test_solution.parameters + delta) func_param_derivative_num[:, i] = (point2 - point1)/step # now compare the actual derivatives with the calculated ones difference = func_derivative_calc - func_derivative_num if not (numpy.round(difference, places) == 0).all(): raise ValueError("analytical derivative matrix does not match numerical derivative matrix.\n Analytical is:\n" + str(func_derivative_calc) + "\n Numerical is:\n" + str(func_derivative_num)) parameterDifference = func_param_derivative_calc - func_param_derivative_num if not (numpy.round(parameterDifference, places) == 0).all(): raise ValueError("analytical derivative (with respect to parameters) matrix does not match numerical derivative matrix.\n Analytical is:\n" + str(func_param_derivative_calc) + "\n Numerical is:\n" + str(func_param_derivative_num)) else: func_derivative_calc = self._function_derivative(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint]) func_derivative_num = numpy.zeros((self._num_ODE,self._num_ODE)) for i in range(self._num_ODE): delta = numpy.zeros(self._num_ODE) delta[i] += step point1 = self._function(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint]) point2 = self._function(test_solution.mesh[middlePoint], test_solution.solution[:, middlePoint] + delta) func_derivative_num[:, i] = (point2 - point1)/step # now compare the actual derivatives with the calculated ones difference = func_derivative_calc - func_derivative_num if not (numpy.round(difference, places) == 0).all(): raise ValueError("analytical derivative matrix does not match numerical derivative matrix.\n Analytical is:\n" + str(func_derivative_calc) + "\n Numerical is:\n" + str(func_derivative_num)) if self.has_boundary_conditions_derivative: endPoint = test_solution.mesh.size - 1 if self._num_parameters > 0: bcA_derivative_calc,bcB_derivative_calc, bcA_param_derivative_calc, bcB_param_derivative_calc = self._boundary_conditions_derivativep(test_solution.solution[:, 0], test_solution.solution[:,endPoint], test_solution.parameters) bcA_derivative_num = numpy.zeros((self._num_left_boundary_conditions, self._num_ODE)) bcB_derivative_num = numpy.zeros((self._num_ODE + self._num_parameters - self._num_left_boundary_conditions, self._num_ODE)) bcA_param_derivative_num = numpy.zeros((self._num_left_boundary_conditions, self._num_parameters)) bcB_param_derivative_num = numpy.zeros( (self._num_ODE + self._num_parameters - self._num_left_boundary_conditions, self._num_parameters)) # calculate analytic derivatives for for i in range(self._num_ODE): delta = numpy.zeros(self._num_ODE) delta[i] += step point1A, point1B = self._boundary_conditionsp(test_solution.solution[:, 0],test_solution.solution[:, endPoint], test_solution.parameters) point2A, point2B = self._boundary_conditionsp(test_solution.solution[:, 0] + delta ,test_solution.solution[:, endPoint] + delta, test_solution.parameters) bcA_derivative_num[:, i] = (point2A - point1A)/step bcB_derivative_num[:, i] = (point2B - point1B)/step for i in range(self._num_parameters): delta = numpy.zeros(self._num_parameters) delta[i] += step point1A, point1B = self._boundary_conditionsp(test_solution.solution[:, 0],test_solution.solution[:, endPoint], test_solution.parameters) point2A, point2B = self._boundary_conditionsp(test_solution.solution[:, 0] ,test_solution.solution[:, endPoint], test_solution.parameters + delta) bcA_param_derivative_num[:, i] = (point2A - point1A)/step bcB_param_derivative_num[:, i] = (point2B - point1B)/step # now compare the actual derivatives with the calculated ones if not (numpy.round(bcA_derivative_calc - bcA_derivative_num, places) == 0).all(): raise ValueError("analytical derivative matrix for the left boundary condition does not match numerical derivative matrix.\n Analytical is:\n" + str(bcA_derivative_calc) + "\n Numerical is:\n" + str(bcA_derivative_num)) if not (numpy.round(bcB_derivative_calc - bcB_derivative_num, places) == 0).all(): raise ValueError("analytical derivative matrix for the right boundary condition does not match numerical derivative matrix.\n Analytical is:\n" + str(bcB_derivative_calc) + "\n Numerical is:\n" + str(bcB_derivative_num)) if not (numpy.round(bcA_param_derivative_calc - bcA_param_derivative_num, places) == 0).all(): raise ValueError("analytical derivative matrix (with respect to parameters) for the left boundary condition does not match numerical derivative matrix.\n Analytical is:\n" + str(bcA_param_derivative_calc) + "\n Numerical is:\n" + str(bcA_param_derivative_num)) if not (numpy.round(bcB_param_derivative_calc - bcB_param_derivative_num, places) == 0).all(): raise ValueError("analytical derivative matrix (with respect to parameters) for the left boundary condition does not match numerical derivative matrix.\n Analytical is:\n" + str(bcB_param_derivative_calc) + "\n Numerical is:\n" + str(bcB_param_derivative_num)) else: bcA_derivative_calc,bcB_derivative_calc = self._boundary_conditions_derivative(test_solution.solution[:, 0], test_solution.solution[:,endPoint]) bcA_derivative_num = numpy.zeros((self._num_left_boundary_conditions, self._num_ODE)) bcB_derivative_num = numpy.zeros((self._num_ODE - self._num_left_boundary_conditions, self._num_ODE)) # calculate analytic derivatives for for i in range(self._num_ODE): delta = numpy.zeros(self._num_ODE) delta[i] += step point1A, point1B = self._boundary_conditions(test_solution.solution[:, 0],test_solution.solution[:, endPoint]) point2A, point2B = self._boundary_conditions(test_solution.solution[:, 0] + delta ,test_solution.solution[:, endPoint] + delta) bcA_derivative_num[:, i] = (point2A - point1A)/step bcB_derivative_num[:, i] = (point2B - point1B)/step # now compare the actual derivatives with the calculated ones if not (numpy.round(bcA_derivative_calc - bcA_derivative_num, places) == 0).all(): raise ValueError("analytical derivative matrix for the left boundary condition does not match numerical derivative matrix.\n Analytical is:\n" + str(bcA_derivative_calc) + "\nNumerical is:\n" + str(bcA_derivative_num)) if not (numpy.round(bcB_derivative_calc - bcB_derivative_num, places) == 0).all(): raise ValueError("analytical derivative matrix for the right boundary condition does not match numerical derivative matrix.\n Analytical is:\n" + str(bcB_derivative_calc) + "\nNumerical is:\n" + str(bcB_derivative_num))
def solve(bvp_problem, solution_guess, initial_mesh = None, parameter_guess = None, max_subintervals = 300, singular_term = None, tolerance = 1.0e-6, method = 4, trace = 0, error_on_fail = True): """Attempts to solve the supplied boundary value problem starting from the user supplied guess for the solution using BVP_SOLVER. Parameters ---------- bvp_problem : :class:`ProblemDefinition` Defines the boundary value problem to be solved. solution_guess : :class:`Solution`, constant, array of values or function A guess for the solution. initial_mesh : castable to floating point ndarray Points on the x-axis to use for the supplied solution guess, default is 10 evenly spaced points. Must not be supplied if solution_guess is a :class:`Solution` object. parameter_guess : castable to floating point ndarray, shape (num_parameters) Guesses for the unknown parameters. Must not be supplied if solution_guess is a :class:`Solution` object. max_subintervals : int Maximum number of points on the mesh before an error is returned. singular_term : castable to floating point ndarray, shape(num_ODE, num_ODE) Matrix that defines the singular term for the problem if one exist. tolerance : positive float Tolerance for size of defect of approximate solution. method : {2, 4, 6} Order of Runge-Kutta to use. trace : {0, 1, 2} Indicates verbosity of output. 0 for no output, 1 for some output, 2 for full output. error_on_fail : logical Indicates whether an exception should be raised if solving fails. Returns ------- sol : :class:`Solution` Approximate problem solution. Raises ------ ValueError If bvp_problem failed validation in some way. ValueError If solving fails. """ init_solution = 0 if isinstance(solution_guess, Solution): if not (initial_mesh == None and parameter_guess == None): raise ValueError("values for initial mesh and parameter_guess must not be given if solution_guess is a Solution object") init_solution = solution_guess else: if initial_mesh == None: initial_mesh = numpy.linspace(bvp_problem.boundary_points[0],bvp_problem.boundary_points[1] , 10) # here we call one of the BVP_GUESS_i routines that make up BVP_INIT to set up the solution if ( not callable(solution_guess)): # in this case the initial solution passed was not a function #try to cast the solution to an array solution_guess = numpy.array(solution_guess) # if the solution_guess is just an array the size of ODE if solution_guess.shape == (bvp_problem.num_ODE,) or (solution_guess.shape == () and bvp_problem.num_ODE == 1): bvp_solverf.bvp.guess_1_wrap(nparam_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, x_in = tools.farg(initial_mesh), y_in = tools.farg(solution_guess), parameters_in = tools.farg(parameter_guess), mxnsub_in = max_subintervals, node_in = bvp_problem.num_ODE) init_solution = Solution.from_arg_list(bvp_solverf.bvp) else: tools.argShapeTest(solution_guess, (bvp_problem.num_ODE,initial_mesh.shape[0]), "solution guess") bvp_solverf.bvp.guess_2_wrap(nparam_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, x_in = tools.farg(initial_mesh), y_in = tools.farg(solution_guess), parameters_in = tools.farg(parameter_guess), mxnsub_in = max_subintervals, node_in = bvp_problem.num_ODE) init_solution = Solution.from_arg_list(bvp_solverf.bvp) else: bvp_solverf.bvp.guess_3_wrap(node_in = bvp_problem.num_ODE, nparam_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, x_in= tools.farg(initial_mesh), fcn = solution_guess, parameters_in = tools.farg(parameter_guess), mxnsub_in = max_subintervals) init_solution = Solution.from_arg_list(bvp_solverf.bvp) if not (method == 2 or method == 4 or method == 6 ): raise ValueError ("method must be either 2, 4 or 6 but got " + str(method) ) if (tolerance < 0): raise ValueError("tolerance must be nonnegative") singular = not (singular_term is None) # check to see if the singular term is of the right size singular_term = tools.preparg(singular_term) if singular and not (singular_term.shape == (bvp_problem.num_ODE, bvp_problem.num_ODE)): raise ValueError("singular_term has the wrong shape/size. Expected: " + (bvp_problem.num_ODE, bvp_problem.num_ODE)+ " but got :" + singular_term.shape) # test the problem specifications with the initial solution bvp_problem.test(init_solution) bvp_solverf.bvp.bvp_solver_wrap(node_in = bvp_problem.num_ODE, npar_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, npts_in = len(init_solution.mesh), info_in = init_solution.successIndicator, mxnsub_in = max_subintervals, x_in = tools.farg(init_solution.mesh), y_in = tools.farg(init_solution.solution), parameters_in = tools.farg(init_solution.parameters), work_in = tools.farg(init_solution.work), iwork_in = tools.farg(init_solution.iwork), fsub = bvp_problem._function, fsubp = bvp_problem._functionp, bcsub = bvp_problem._boundary_conditions, bcsubp = bvp_problem._boundary_conditionsp, singular = singular, hasdfdy = bvp_problem.has_function_derivative, dfdy = bvp_problem._function_derivative, dfdyp = bvp_problem._function_derivativep, hasdbcdy = bvp_problem.has_boundary_conditions_derivative, dbcdy = bvp_problem._boundary_conditions_derivative, dbcdyp = bvp_problem._boundary_conditions_derivativep, #optional arguments method = method, tol = tolerance, trace = trace, # we never want the actual program to shut down from the fortran side, we should throw an error stop_on_fail = False, singularterm = tools.farg(singular_term)) calculatedSolution = Solution.from_arg_list(bvp_solverf.bvp) # check to see if there was a problem with the solution if error_on_fail and calculatedSolution._successIndicator == -1: raise ValueError("Boundary value problem solving failed. Run with trace = 1 or 2 for more information.") return calculatedSolution
def solve(bvp_problem, solution_guess, initial_mesh = None, parameter_guess = None, max_subintervals = 300, singular_term = None, tolerance = 1.0e-6, method = 4, trace = 0, error_on_fail = True): """Attempts to solve the supplied boundary value problem starting from the user supplied guess for the solution using BVP_SOLVER. Parameters ---------- bvp_problem : :class:`ProblemDefinition` Defines the boundary value problem to be solved. solution_guess : :class:`Solution`, constant, array of values or function A guess for the solution. initial_mesh : castable to floating point ndarray Points on the x-axis to use for the supplied solution guess, default is 10 evenly spaced points. Must not be supplied if solution_guess is a :class:`Solution` object. parameter_guess : castable to floating point ndarray, shape (num_parameters) Guesses for the unknown parameters. Must not be supplied if solution_guess is a :class:`Solution` object. max_subintervals : int Maximum number of points on the mesh before an error is returned. singular_term : castable to floating point ndarray, shape(num_ODE, num_ODE) Matrix that defines the singular term for the problem if one exist. tolerance : positive float Tolerance for size of defect of approximate solution. method : {2, 4, 6} Order of Runge-Kutta to use. trace : {0, 1, 2} Indicates verbosity of output. 0 for no output, 1 for some output, 2 for full output. error_on_fail : logical Indicates whether an exception should be raised if solving fails. Returns ------- sol : :class:`Solution` Approximate problem solution. Raises ------ ValueError If bvp_problem failed validation in some way. ValueError If solving fails. """ init_solution = 0 if isinstance(solution_guess, Solution): if not (initial_mesh == None and parameter_guess == None): raise ValueError("values for initial mesh and parameter_guess must not be given if solution_guess is a Solution object") init_solution = solution_guess else: if initial_mesh == None: initial_mesh = numpy.linspace(bvp_problem.boundary_points[0],bvp_problem.boundary_points[1] , 10) # here we call one of the BVP_GUESS_i routines that make up BVP_INIT to set up the solution if ( not callable(solution_guess)): # in this case the initial solution passed was not a function #try to cast the solution to an array solution_guess = numpy.array(solution_guess) # if the solution_guess is just an array the size of ODE if solution_guess.shape == (bvp_problem.num_ODE,) or (solution_guess.shape == () and bvp_problem.num_ODE == 1): bvp_solverf.bvp.guess_1_wrap(nparam_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, x_in = tools.farg(initial_mesh), y_in = tools.farg(solution_guess), parameters_in = tools.farg(parameter_guess), mxnsub_in = max_subintervals, node_in = bvp_problem.num_ODE) init_solution = Solution.from_arg_list(bvp_solverf.bvp) else: tools.argShapeTest(solution_guess, (bvp_problem.num_ODE,initial_mesh.shape[0]), "solution guess") bvp_solverf.bvp.guess_2_wrap(nparam_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, x_in = tools.farg(initial_mesh), y_in = tools.farg(solution_guess), parameters_in = tools.farg(parameter_guess), mxnsub_in = max_subintervals, node_in = bvp_problem.num_ODE) init_solution = Solution.from_arg_list(bvp_solverf.bvp) else: y_in = numpy.zeros((bvp_problem.num_ODE,1)) bvp_solverf.bvp.guess_1_wrap(nparam_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, x_in = tools.farg(initial_mesh), y_in = y_in, parameters_in = tools.farg(parameter_guess), mxnsub_in = max_subintervals, node_in = bvp_problem.num_ODE) init_solution = Solution.from_arg_list(bvp_solverf.bvp) for i,v in enumerate(init_solution._mesh): init_solution._solution[:, i] = solution_guess(v) if not (method == 2 or method == 4 or method == 6 ): raise ValueError ("method must be either 2, 4 or 6 but got " + str(method) ) if (tolerance < 0): raise ValueError("tolerance must be nonnegative") singular = not (singular_term is None) # check to see if the singular term is of the right size singular_term = tools.preparg(singular_term) if singular and not (singular_term.shape == (bvp_problem.num_ODE, bvp_problem.num_ODE)): raise ValueError("singular_term has the wrong shape/size. Expected: " + (bvp_problem.num_ODE, bvp_problem.num_ODE)+ " but got :" + singular_term.shape) # test the problem specifications with the initial solution bvp_problem.test(init_solution) bvp_solverf.bvp.bvp_solver_wrap(node_in = bvp_problem.num_ODE, npar_in = bvp_problem.num_parameters, leftbc_in = bvp_problem.num_left_boundary_conditions, npts_in = len(init_solution.mesh), info_in = init_solution.successIndicator, mxnsub_in = max_subintervals, x_in = tools.farg(init_solution.mesh), y_in = tools.farg(init_solution.solution), parameters_in = tools.farg(init_solution.parameters), work_in = tools.farg(init_solution.work), iwork_in = tools.farg(init_solution.iwork), fsub = bvp_problem._function, fsubp = bvp_problem._functionp, bcsub = bvp_problem._boundary_conditions, bcsubp = bvp_problem._boundary_conditionsp, singular = singular, hasdfdy = bvp_problem.has_function_derivative, dfdy = bvp_problem._function_derivative, dfdyp = bvp_problem._function_derivativep, hasdbcdy = bvp_problem.has_boundary_conditions_derivative, dbcdy = bvp_problem._boundary_conditions_derivative, dbcdyp = bvp_problem._boundary_conditions_derivativep, #optional arguments method = method, tol = tolerance, trace = trace, # we never want the actual program to shut down from the fortran side, we should throw an error stop_on_fail = False, singularterm = tools.farg(singular_term)) calculatedSolution = Solution.from_arg_list(bvp_solverf.bvp) # check to see if there was a problem with the solution if error_on_fail and calculatedSolution._successIndicator == -1: raise ValueError("Boundary value problem solving failed. Run with trace = 1 or 2 for more information.") return calculatedSolution