Code covered by the BSD License  

Highlights from
bvp5c2

from bvp5c2 by Qun HAN
Faster version of the MATLAB function bvp5c

bvp5c2(ode, bc, solinit, options)
function sol = bvp5c2(ode, bc, solinit, options)
%BVP5C  Solve boundary value problems for ODEs by collocation.
%   SOL = BVP5C(ODEFUN,BCFUN,SOLINIT) integrates a system of ordinary
%   differential equations of the form y' = f(x,y) on the interval [a,b],
%   subject to general two-point boundary conditions of the form
%   bc(y(a),y(b)) = 0. ODEFUN and BCFUN are function handles. For a scalar X
%   and a column vector Y, ODEFUN(X,Y) must return a column vector representing
%   f(x,y). For column vectors YA and YB, BCFUN(YA,YB) must return a column
%   vector representing bc(y(a),y(b)).  SOLINIT is a structure with fields
%       x -- ordered nodes of the initial mesh with
%            SOLINIT.x(1) = a, SOLINIT.x(end) = b
%       y -- initial guess for the solution with SOLINIT.y(:,i)
%            a guess for y(x(i)), the solution at the node SOLINIT.x(i)
%
%   BVP5C produces a solution that is continuous on [a,b] and has a
%   continuous first derivative there. The solution is evaluated at points
%   XINT using the output SOL of BVP5C and the function DEVAL:
%   YINT = DEVAL(SOL,XINT). The output SOL is a structure with
%       SOL.x  -- mesh selected by BVP5C
%       SOL.y  -- approximation to y(x) at the mesh points of SOL.x
%       SOL.yp -- approximation to y'(x) at the mesh points of SOL.x
%       SOL.solver -- 'bvp5c'
%
%   SOL = BVP5C(ODEFUN,BCFUN,SOLINIT,OPTIONS) solves as above with default
%   parameters replaced by values in OPTIONS, a structure created with the
%   BVPSET function. To reduce the run time greatly, use OPTIONS to supply
%   a function for evaluating the Jacobian and/or vectorize ODEFUN.
%   See BVPSET for details and SHOCKBVP for an example that does both.
%
%   Some boundary value problems involve a vector of unknown parameters p
%   that must be computed along with y(x):
%       y' = f(x,y,p)
%       0  = bc(y(a),y(b),p)
%   For such problems the field SOLINIT.parameters is used to provide a guess
%   for the unknown parameters. On output the parameters found are returned
%   in the field SOL.parameters. The solution SOL of a problem with one set
%   of parameter values can be used as SOLINIT for another set. Difficult BVPs
%   may be solved by continuation: start with parameter values for which you can
%   get a solution, and use it as a guess for the solution of a problem with
%   parameters closer to the ones you want. Repeat until you solve the BVP
%   for the parameters you want.
%
%   The function BVPINIT forms the guess structure in the most common
%   situations:  SOLINIT = BVPINIT(X,YINIT) forms the guess for an initial
%   mesh X as described for SOLINIT.x, and YINIT either a constant vector
%   guess for the solution or a function handle. If YINIT is a function handle
%   then for a scalar X, YINIT(X) must return a column vector, a guess for
%   the solution at point x in [a,b]. If the problem involves unknown parameters
%   SOLINIT = BVPINIT(X,YINIT,PARAMS) forms the guess with the vector PARAMS of
%   guesses for the unknown parameters.
%
%   BVP5C solves a class of singular BVPs, including problems with
%   unknown parameters p, of the form
%       y' = S*y/x + f(x,y,p)
%       0  = bc(y(0),y(b),p)
%   The interval is required to be [0, b] with b > 0.
%   Often such problems arise when computing a smooth solution of
%   ODEs that result from PDEs because of cylindrical or spherical
%   symmetry. For singular problems the (constant) matrix S is
%   specified as the value of the 'SingularTerm' option of BVPSET,
%   and ODEFUN evaluates only f(x,y,p). The boundary conditions
%   must be consistent with the necessary condition S*y(0) = 0 and
%   the initial guess should satisfy this condition.
%
%   BVP5C can solve multipoint boundary value problems.  For such problems
%   there are boundary conditions at points in [a,b]. Generally these points
%   represent interfaces and provide a natural division of [a,b] into regions.
%   BVP5C enumerates the regions from left to right (from a to b), with indices
%   starting from 1.  In region k, BVP5C evaluates the derivative as
%   YP = ODEFUN(X,Y,K).  In the boundary conditions function,
%   BCFUN(YLEFT,YRIGHT), YLEFT(:,K) is the solution at the 'left' boundary
%   of region k and similarly for YRIGHT(:,K).  When an initial guess is
%   created with BVPINIT(XINIT,YINIT), XINIT must have double entries for
%   each interface point. If YINIT is a function handle, BVPINIT calls
%   Y = YINIT(X,K) to get an initial guess for the solution at X in region k.
%   In the solution structure SOL returned by BVP5C, SOL.x has double entries
%   for each interface point. The corresponding columns of SOL.y contain
%   the 'left' and 'right' solution at the interface, respectively.
%   See THREEBVP for an example of solving a three-point BVP.
%
%   Example
%         solinit = bvpinit([0 1 2 3 4],[1 0]);
%         sol = bvp5c(@twoode,@twobc,solinit);
%     solve a BVP on the interval [0,4] with differential equations and
%     boundary conditions computed by functions twoode and twobc, respectively.
%     This example uses [0 1 2 3 4] as an initial mesh, and [1 0] as an initial
%     approximation of the solution components at the mesh points.
%         xint = linspace(0,4);
%         yint = deval(sol,xint);
%     evaluate the solution at 100 equally spaced points in [0 4]. The first
%     component of the solution is then plotted with
%         plot(xint,yint(1,:));
%   For more examples see FSBVP, SHOCKBVP, MAT4BVP, EMDENBVP. To use the
%   BVP5C solver, you must pass 'bvp5c' as input argument:
%         fsbvp('bvp5c')
%
%   BVP5C is used exactly like BVP4C, but error tolerances do not mean the
%   same in the two solvers. If S(x) approximates the solution y(x), BVP4C
%   controls the residual |S'(x) - f(x,S(x))|. This controls indirectly the
%   true error |y(x) - S(x)|.  BVP5C controls the true error directly.
%   BVP5C is more efficient than BVP4C for small error tolerances.
%
%   See also BVP4C, BVPSET, BVPGET, BVPINIT, BVPXTEND, DEVAL, FUNCTION_HANDLE.

%   BVP5C is a finite difference code that implements the 4-stage Lobatto
%   IIIa formula.  This is a collocation formula and the collocation
%   polynomial provides a C1-continuous solution that is fifth order
%   accurate uniformly in [a,b]. The formula is implemented as an implicit
%   Runge-Kutta formula.  BVP5C solves the algebraic equations directly;
%   BVP4C uses analytical condensation. BVP4C handles unknown parameters
%   directly; BVP5C augments the system with trivial differential equations
%   for unknown parameters.

%   Jacek Kierzenka and Lawrence F. Shampine
%   Copyright 1984-2008 The MathWorks, Inc.
%   $Revision: 1.1.6.2 $  $Date: 2008/05/23 15:35:06 $
%	Improved sparse matrix operation in colloc_JacODE_region - junziyang $Date: 2009/06/07

solver_name = 'bvp5c';

% Check input arguments
if nargin < 3
	error('MATLAB:bvp5c:NotEnoughInputs', 'Not enough input arguments.')
elseif nargin < 4
	options = [];
end

% Validate arguments and options
[neqn,nparam,nregions,atol,rtol,Nmax,xyVectorized,printstats] = ...
	bvparguments(solver_name,ode,bc,solinit,options);

% Modify equations to accommodate unknown parameters
[ode,bc,jac,bcjac,Joptions,dBCoptions] = ...
	bvpfunctions(solver_name,ode,bc,options,neqn,nparam,nregions);

% Deal with a singular BVP.
[singularBVP,ode,jac,solinit,PBC] = ...
	bvpsingular(solver_name,solinit,ode,jac,options,neqn,nparam,nregions);

% Recognize a multipoint BVP
ismbvp = (nregions > 1);
MBVP = [];

% Adjust the problem to accommodate unknown parameters
if nparam > 0
	neqn = neqn + nparam;
	ptol = rtol(ones(nparam,1));
	atol = [atol;ptol];
end

threshold = atol/rtol;

% Initialize counters (count test calls in BVPARGUMENTS)
nODEeval = 1;
nBCeval = 1;

% Four-stage Lobatto IIIa collocation formula (non-trivial coefficients only.)
sq5 = sqrt(5);
c = [ (5 - sq5)/10, (5 + sq5)/10];
A = [[(11 + sq5)/120,    (25 - sq5)/120, (25 - 13*sq5)/120, (-1 + sq5)/120]; ...
	[(11 - sq5)/120, (25 + 13*sq5)/120,    (25 + sq5)/120, (-1 - sq5)/120]; ...
	[         1/12,              5/12,              5/12            1/12 ]];
B = [[-(5+17*sq5)/10,   (5+sq5)/2,   (-5+3*sq5)/2, (5-3*sq5)/10]; ...
	[(-5+17*sq5)/10, -(5+3*sq5)/2,   (5-sq5)/2,   (5+3*sq5)/10]; ...
	[    -7,             5*sq5,      -5*sq5,           7      ]];
nstages = 3;  % The number of points per mesh subinterval.

% Constant matrices for the collocation Jacobian
bigA = kron(A,ones(neqn));
JacI = [1,-1, 0, 0;
	1, 0,-1, 0;
	1, 0, 0,-1];
bigI = kron(JacI,eye(neqn));

% Interpolate solution at collocation points
[X,Y] = interpGuess(solinit);
if ismbvp
	MBVP = updateMBVP(X);
end

% Algebraic solver parameters
maxNewtIter = 4;
maxProbes = 4;    % weak line search
needGlobalJacobian = true;
refinedMesh = false;

meshHistory = [0,0];         % Keep track of [N, maxerr],
errorReductionGuard = 1e-4;  % to prevent mesh oscillations.

% Sample the residual at a single point while adapting the mesh,
% but use three samples during final solution verification stage.
solutionVerificationStage = false;

done = false;

% THE MAIN LOOP:
while ~done
	
	for iter = 1 : maxNewtIter
		
		if ~refinedMesh
			F = odeFcn(X,Y);
		end
		refinedMesh = false;
		[RHS,BCval] = colloc_RHS(X,Y,F);
		
		if needGlobalJacobian
			% setup and factor the global Jacobian
			dPHIdy = colloc_Jac(X,Y,F,BCval);
			needGlobalJacobian = false;
			
			% explicit row scaling
			wt = max(abs(dPHIdy),[],2);
			if any(wt == 0) || ~all(isfinite(nonzeros(dPHIdy)))
				singJac = true;
			else
				scalMatrix = spdiags( 1 ./ wt, 0,numel(wt),numel(wt));
				dPHIdy = scalMatrix * dPHIdy;
				[L,U,P,Q,R] = lu(dPHIdy);
				singJac = bvpcheckmatrix(dPHIdy,L,U,P,Q,R);
			end
			if singJac
				error('MATLAB:bvp5c:SingJac', '%s -- %s', ...
					'Unable to solve the collocation equations', ...
					'a singular Jacobian encountered');
			end
		end
		
		% the Newton direction
		delY = Q * (U \ (L \ (P * (R \ (scalMatrix * RHS)))));
		distY = norm(delY);
		
		% weak line search with an affine-invariant stopping criterion
		lambda = 1;
		for probe = 1 : maxProbes
			Ynew = Y - lambda*reshape(delY,neqn,[]);
			if singularBVP     % Impose necessary BC, Sy(0) = 0.
				Ynew(1:neqn-nparam,1) = PBC*Ynew(1:neqn-nparam,1);
			end
			Fnew = odeFcn(X,Ynew);
			RHSnew = colloc_RHS(X,Ynew,Fnew);
			distYnew = norm( U \ (L \ (P * (R \ (scalMatrix * RHSnew)))));
			
			if (distYnew < 0.9*distY)
				break
			else
				lambda = 0.5*lambda;
			end
		end
		
		needGlobalJacobian = (distYnew > 0.1*distY);
		
		if distYnew < 0.1*rtol
			maxInterpResidual = colloc_maxresidual(X,Ynew,Fnew);
			if maxInterpResidual < 0.1*rtol
				break
			end
		end
		
		Y = Ynew;  % Continue Newton iterations.
		
	end  % Newton iterations
	
	Y = Ynew;
	F = Fnew;
	
	ymid = interpolateYmid(X,Y,F);
	
	if solutionVerificationStage
		% Use three error samples per mesh interval.
		nsamples = 3;
		res = residualEstimate(X,Y,ymid,F,nsamples);
	else
		% Use single error sample per mesh interrval.
		nsamples = 1;
		res = residualEstimate(X,Y,ymid,F,nsamples);
		if max(res) < rtol
			% Enter the final verification stage.
			solutionVerificationStage = true;
			% Compute the remainig two samples.
			nsamples = 2;
			res2 = residualEstimate(X,Y,ymid,F,nsamples);
			res = max(res,res2);
		end
	end
	
	if solutionVerificationStage && ( max(res) <= rtol)
		done = true;
	else
		[XX,YY,FF] = newSolutionProfile(X,Y,ymid,F,res);
		if numel(XX) > Nmax
			warning('MATLAB:bvp5c:RelTolNotMet', ...
				[ 'Unable to meet the tolerance without using more than %d '...
				'mesh points. \n The last mesh of %d points and ' ...
				'the solution are available in the output argument. \n ', ...
				'The maximum error is %g, while requested accuracy is %g.'], ...
				Nmax,numel(res)+1,max(res),rtol);
			done = true;
		else
			refinedMesh = true;
			X = XX;
			Y = YY;
			F = FF;
			if ismbvp
				MBVP = updateMBVP(X);
			end
			needGlobalJacobian = true;
		end
	end
	
end  % while

% Output solution structure
sol = outputSol(X,Y,F,ymid);

% Stats
if printstats
	fprintf('The solution was obtained on a mesh of %g points.\n',numel(sol.x));
	fprintf('The maximum error is %10.3e. \n',max(res));
	fprintf('There were %g calls to the ODE function. \n',nODEeval);
	fprintf('There were %g calls to the BC function. \n',nBCeval);
end

%---------------------------------------------------------------------------
% Nested functions
%---------------------------------------------------------------------------

	function [X,Y] = interpGuess(sol)
		%INTERP_GUESS  Evaluate/interpolate the initial guess at collocation points.
		
		if ~isfield(sol,'solver')
			sol.solver = 'unknown';
		end
		
		if ismbvp
			X = []; Y = [];
			mbcidx = find(diff(sol.x) == 0);
			Lidx = [1, mbcidx+1];
			Ridx = [mbcidx, length(sol.x)];
			for region = 1 : nregions
				xidx = Lidx(region):Ridx(region);
				xreg = sol.x(xidx);
				yreg = sol.y(:,xidx);
				[Xreg,Yreg] = interpGuess_region(sol,xreg,yreg,{region});
				X = cat(2,X,Xreg);
				Y = cat(2,Y,Yreg);
			end
		else
			[X,Y] = interpGuess_region(sol,sol.x,sol.y,{});
		end
	end  % interpGuess

%---------------------------------------------------------------------------

	function [X,Y] = interpGuess_region(sol,sol_xreg,sol_yreg,region)
		% In a region, evaluate/interpolate the initial guess at collocation points.
		h = diff(sol_xreg);
		x0  = sol_xreg(1:end-1);  % left subinerval boundaries
		xc1 = x0 + c(1)*h;
		xc2 = x0 + c(2)*h;
		X = [x0; xc1; xc2];
		X = [X(:); sol_xreg(end)];
		X = reshape(X,1,[]);
		
		y0 = sol_yreg(:,1:end-1);  % solution at left subinterval boundaries
		
		switch sol.solver
			case 'unknown'
				% linear interpolation between mesh points
				dely = diff(sol_yreg,1,2);
				yc1 = y0 + c(1)*dely;
				yc2 = y0 + c(2)*dely;
			case 'bvpinit'
				yinit = sol.yinit;
				if isa(yinit,'function_handle')
					% evaluate the initial guess at collocation points
					yc1 = zeros(size(y0));
					yc2 = zeros(size(y0));
					for i = 1 : size(y0,2)
						yc1(:,i) = yinit(xc1(i),region{:});
						yc2(:,i) = yinit(xc2(i),region{:});
					end
				else % yinit is a constant guess
					yc1 = repmat(yinit(:),1,size(y0,2));
					yc2 = yc1;
				end
			otherwise
				% evaluate solution in SOL
				yc1 = deval(sol,xc1);
				yc2 = deval(sol,xc2);
		end
		yend = sol_yreg(:,end);
		
		if nparam > 0 % augment y with unknown parameters
			params = repmat(sol.parameters(:),1,size(y0,2));
			y0  = [y0;params];
			yc1 = [yc1;params];
			yc2 = [yc2;params];
			yend = [yend; sol.parameters(:)];
		end
		
		Y = [y0; yc1; yc2];
		Y = [Y(:); yend];
		Y = reshape(Y,neqn,[]);
	end  % interpGuess_region

%---------------------------------------------------------------------------

	function F = odeFcn(X,Y)
		%ODE_FCN  Evaluate the ODE function for all points in X,Y.
		if ismbvp
			F = zeros(size(Y));
			for region = 1 : nregions
				xidx = MBVP.LBCidx(region):MBVP.RBCidx(region);
				xreg = X(xidx);
				yreg = Y(:,xidx);
				F(:,xidx) = odeFcn_region(xreg,yreg,{region});
			end
		else
			F = odeFcn_region(X,Y,{});
		end
	end  % odeFcn

%---------------------------------------------------------------------------

	function F = odeFcn_region(X,Y,region)
		% In a region, evaluate the ODE function for all points in X,Y.
		if ~ismbvp
			region = {};
		end
		if xyVectorized
			F = ode(X,Y,region{:});
			nODEeval = nODEeval + 1; % stats
		else
			F = zeros(size(Y));
			for i = 1 : numel(X)
				F(:,i) = ode(X(i),Y(:,i),region{:});
			end
			nODEeval = nODEeval + numel(X); % stats
		end
	end  % odeFcn_region

%---------------------------------------------------------------------------

	function [Jn,Jnc1,Jnc2,Jnp1] = odeJac_region(x,y,f,Jpropagated,region)
		% In a region, compute the ODE Jacobian at points in a mesh interval.
		if isempty(jac)
			if isempty(Jpropagated)
				[Jn,Joptions.fac,ignored,nF] = ...
					odenumjac(ode,{x(1),y(:,1),region{:}},f(:,1),Joptions);
				nODEeval = nODEeval + nF;
			else
				Jn = Jpropagated;
			end
			[Jnp1,Joptions.fac,ignored,nF] = ...
				odenumjac(ode,{x(4),y(:,4),region{:}},f(:,4),Joptions);
			nODEeval = nODEeval + nF;
			if norm(Jnp1 - Jn,1) <= 0.25*(norm(Jnp1,1) + norm(Jn,1))
				Jnc1 = (1 - c(1))*Jn + c(1)*Jnp1;
				Jnc2 = (1 - c(2))*Jn + c(2)*Jnp1;
			else
				[Jnc1,Joptions.fac,ignored,nF1] = ...
					odenumjac(ode,{x(2),y(:,2),region{:}},f(:,2),Joptions);
				[Jnc2,Joptions.fac,ignored,nF2] = ...
					odenumjac(ode,{x(3),y(:,3),region{:}},f(:,3),Joptions);
				nODEeval = nODEeval + nF1 + nF2;
			end
		elseif isnumeric(jac)
			Jn   = jac;
			Jnc1 = jac;
			Jnc2 = jac;
			Jnp1 = jac;
		else
			if isempty(Jpropagated)
				Jn = jac(x(1),y(:,1),region{:});
			else
				Jn = Jpropagated;
			end
			Jnc1 = jac(x(2),y(:,2),region{:});
			Jnc2 = jac(x(3),y(:,3),region{:});
			Jnp1 = jac(x(4),y(:,4),region{:});
		end
	end  % odeJac_region

%---------------------------------------------------------------------------

	function res = bcaux(Ya,Yb)
		%BCAUX  Reshape the arguments for the BC function.
		local_ya = reshape(Ya,neqn,[]);
		local_yb = reshape(Yb,neqn,[]);
		res = bc(local_ya,local_yb);
	end  % bcaux

%---------------------------------------------------------------------------

	function [dBCdya,dBCdyb] = bcJac(ya,yb,bcVal)
		%BC_JAC  Compute the BC Jacobian.
		if isempty(bcjac)
			dBCoptions.diffvar = 1;  % d(bc(ya,yb))/dya
			dBCoptions.fac = dBCoptions.fac_dya;
			[dBCdya,dBCoptions.fac_dya,ignored,nF] = ...
				odenumjac(@bcaux,{ya(:),yb(:)},bcVal,dBCoptions);
			nBCeval = nBCeval + nF;
			dBCoptions.diffvar = 2;  % d(bc(ya,yb))/dyb
			dBCoptions.fac = dBCoptions.fac_dyb;
			[dBCdyb,dBCoptions.fac_dyb,ignored,nF] = ...
				odenumjac(@bcaux,{ya(:),yb(:)},bcVal,dBCoptions);
			nBCeval = nBCeval + nF;
		elseif iscell(bcjac)
			dBCdya = bcjac{1};
			dBCdyb = bcjac{2};
		else
			[dBCdya,dBCdyb] = bcjac(ya,yb);
		end
	end  % bcJac

%---------------------------------------------------------------------------

	function [RHS,RHSbc]= colloc_RHS(X,Y,F)
		%COLLOC_RHS  Evaluate the system of collocation equations.
		%   Separately return the residual in the boundary conditions.
		
		% boundary conditions
		if ismbvp
			ya = Y(:,MBVP.LBCidx);
			yb = Y(:,MBVP.RBCidx);
		else
			ya = Y(:,1);
			yb = Y(:,end);
		end
		RHSbc = bc(ya,yb);
		nBCeval = nBCeval + 1;
		
		% collocation equations
		[xreg,yreg,freg] = getRegionData(1,X,Y,F);
		RHSode = colloc_RHS_region(xreg,yreg,freg);
		
		for region = 2 : nregions  % MBVP
			[xreg,yreg,freg] = getRegionData(region,X,Y,F);
			RHSreg = colloc_RHS_region(xreg,yreg,freg);
			RHSode = cat(1,RHSode,RHSreg);
		end
		
		RHS = [RHSbc(:); RHSode(:)];
	end  % colloc_RHS

%---------------------------------------------------------------------------

	function RHSode = colloc_RHS_region(X,Y,F)
		% In a region, evaluate the system of collocation equations.
		h = diff(X(1:nstages:end));
		RHSode = zeros(neqn,numel(X)-1);
		
		idx = 0;
		for i = 1 : numel(h)
			ynn = Y(:, idx + ones(1,nstages));
			ync = Y(:, idx+2 : idx+nstages+1);
			
			hi = h(i);
			hA = hi*A;
			K = F(:, idx+1 : idx+nstages+1);
			
			% Form the residual in the Runge-Kutta formulas (collocation
			% equations) in terms of the intermediate solution values.
			RHSode(:, idx+1 : idx+nstages) = ynn + K*hA' - ync;
			
			idx = idx + nstages;
		end
		
		RHSode = RHSode(:);
	end  % colloc_RHS_region

%---------------------------------------------------------------------------

	function Jac = colloc_Jac(X,Y,F,bcVal)
		%COLLOC_JAC  Form the global Jacobian of the collocation equations.
		
		if isempty(jac)
			Joptions.fac = [];
		end
		
		if ismbvp
			
			% BC Jacobian
			JACbc  = spalloc( neqn*nregions, numel(Y), 2*nregions*neqn*neqn);
			ya = Y(:,MBVP.LBCidx);
			yb = Y(:,MBVP.RBCidx);
			[JACbcL, JACbcR] = bcJac(ya,yb,bcVal);
			for region = 1 : nregions
				cols_side = (region-1)*neqn+(1:neqn);
				% Left BC
				cols_glob = (MBVP.LBCidx(region) - 1)*neqn + (1:neqn);
				JACbc(:,cols_glob) = JACbcL(:,cols_side);
				% Right BC
				cols_glob = (MBVP.RBCidx(region) - 1)*neqn + (1:neqn);
				JACbc(:,cols_glob) = JACbcR(:,cols_side);
			end
			
			% ODE Jacobian -- estimate storage requirements.
			JACode = spalloc( numel(Y) - neqn*nregions, numel(Y), ...
				neqn*nstages*neqn*(nstages+1)*ceil(numel(X)/nstages));
			rowJACode = 0;
			colJACode = 0;
			for region = 1 : nregions
				[xreg,yreg,freg] = getRegionData(region,X,Y,F);
				JACreg = colloc_JacODE_region(xreg,yreg,freg,{region});
				JACode(rowJACode + 1 : rowJACode + size(JACreg,1),...
					colJACode + 1 : colJACode + size(JACreg,2) ) = JACreg;
				rowJACode = rowJACode + size(JACreg,1);
				colJACode = colJACode + size(JACreg,2);
			end
			
		else  % two-point BVP
			
			% BC Jacobian
			JACbc  = spalloc( neqn, numel(Y), 2*neqn*neqn);
			ya = Y(:,1);
			yb = Y(:,end);
			[JACbcL, JACbcR] = bcJac(ya,yb,bcVal);
			JACbc(:,1:neqn) = JACbcL;
			JACbc(:,end-neqn+1 : end) = JACbcR;
			
			% ODE Jacobian
			JACode = colloc_JacODE_region(X,Y,F,{});
			
		end
		
		% the collocation Jacobian
		Jac = [JACbc; JACode];
		
	end  % colloc_Jac

%---------------------------------------------------------------------------

	function JacODE = colloc_JacODE_region(X,Y,F,region)
		% In a region, form the Jacobian of collocation equations.
		h = diff(X(1:nstages:end));
		nh = numel(h); %-------------------------------------------------------------------------junziyang
		nY = numel(Y); %-------------------------------------------------------------------------junziyang
		intyp = uinttype(nY); %Determine the proper int type for cow,rol ------------------------junziyang
		nval = neqn*nstages*neqn*(nstages+1)*nh; %Max number of non-zeros
		row = zeros(nval,1,intyp); %Row index of non-zeros --------------------------------------junziyang
		col = zeros(nval,1,intyp); %Collumn index of non-zeros ----------------------------------junziyang
		val = zeros(nval,1); %Non-zero element values -------------------------------------------junziyang
		jidx = zeros(1,1,intyp);
		xidx = zeros(1,1,intyp);
		Jnp1 = [];
		rows = neqn*nstages; %Number of rows of non-zeros generated in each FOR loop -----------junziyang
		cols = neqn*(nstages+1); %Number of cols of non-zeros generated in each FOR loop -------junziyang
		stepnum = rows*cols; %Number of non-zeros generated each for step ----------------------junziyang
		for i = 1 : nh
			
			x = X(  xidx+1 : xidx+nstages+1);
			y = Y(:,xidx+1 : xidx+nstages+1);
			f = F(:,xidx+1 : xidx+nstages+1);
			
			[Jn,Jnc1,Jnc2,Jnp1] = odeJac_region(x,y,f,Jnp1,region);
			
			hJ = h(i)*[Jn,Jnc1,Jnc2,Jnp1];
			bigJ = [hJ;hJ;hJ];
			row((i-1)*stepnum+1:i*stepnum) = repmat(jidx+1:jidx+rows,1,cols);
			tmp = repmat(jidx+1:jidx+cols,rows,1);
			col((i-1)*stepnum+1:i*stepnum) = tmp(:);
			temp = bigI + bigA.*bigJ;
			val((i-1)*stepnum+1:i*stepnum) = temp(:);
			jidx = jidx + neqn*nstages;
			xidx = xidx + nstages;
		end
		JacODE = sparse2(row,col,val);
	end  % colloc_JacODE_region

%---------------------------------------------------------------------------

	function maxInterpResidual = colloc_maxresidual(X,Y,F)
		%COLLOC_MAXRESIDUAL  Compute max residual in the collocation equations.
		
		[xreg,yreg,freg] = getRegionData(1,X,Y,F);
		maxInterpResidual = colloc_maxresidual_region(xreg,yreg,freg);
		
		for region = 2 : nregions  % MBVP
			[xreg,yreg,freg] = getRegionData(region,X,Y,F);
			regionInterpResidual = colloc_maxresidual_region(xreg,yreg,freg);
			maxInterpResidual = max(maxInterpResidual,regionInterpResidual);
		end
		
	end  % colloc_maxresidual

%---------------------------------------------------------------------------

	function maxInterpResidual = colloc_maxresidual_region(X,Y,F)
		% In a region, compute max residual in the collocation equations.
		
		maxInterpResidual = 0;
		
		h = diff(X(1:nstages:end));
		thresh = threshold(:,ones(1,nstages));
		
		idx = 0;
		for i = 1 : numel(h)
			ync = Y(:,  idx+2 : idx+nstages+1);
			K   = F(:,  idx+1 : idx+nstages+1);
			
			% Form the residual in the collocation equations in terms of the
			% intermediate derivative values and compute a weighted norm scaled
			% by the mesh spacing.
			K1 = K(:,1);
			Boh = B/h(i);
			K2_4 = [ -sq5/5*K1, sq5/5*K1, -K1] + Y(:,idx+1:idx+4)*Boh';
			
			residual = K2_4 - K(:,2:4);
			
			wtdRes = residual ./ max(abs(ync),thresh);
			normRes = max(max(abs(wtdRes)));
			scaledRes = abs(h(i)) * normRes;
			
			maxInterpResidual = max(maxInterpResidual,scaledRes);
			
			idx = idx + nstages;
		end
		
	end  % colloc_maxresidual_region

%---------------------------------------------------------------------------

	function ymid = interpolateYmid(X,Y,F)
		%INTERPOLATE_YMID  Interpolate Y at the midpoints of mesh subintervals.
		
		[xreg,yreg,freg] = getRegionData(1,X,Y,F);
		ymid = interpolateYmid_region(xreg,yreg,freg);
		
		for region = 2 : nregions  % MBVP
			[xreg,yreg,freg] = getRegionData(region,X,Y,F);
			ymidreg = interpolateYmid_region(xreg,yreg,freg);
			ymid = cat(2,ymid,zeros(neqn,1),ymidreg);   % pad with zeros
		end
		
	end  % interpolateYmid

%---------------------------------------------------------------------------

	function ymid = interpolateYmid_region(X,Y,F)
		% In a region, interpolate Y at the midpoints of mesh subintervals.
		h = diff(X(1:nstages:end));
		y = Y(:,1:nstages:end);
		K1 = F(:,1:nstages:end);
		K2 = F(:,2:nstages:end);
		K3 = F(:,3:nstages:end);
		ymid = y(:,1:end-1) + ...
			( 17/192*K1(:,1:end-1) + (40+15*sq5)/192*K2 + ...
			(40-15*sq5)/192*K3 - 1/192*K1(:,2:end)) * ...
			spdiags(h(:),0,numel(h),numel(h));
	end  % interpolateYmid_region

%---------------------------------------------------------------------------

	function res = residualEstimate(X,Y,Ymid,F,nsamples)
		%RESIDUAL_ESTIMATE  Estimate the residual in each mesh subinterval.
		
		[xreg,yreg,freg,ymidreg] = getRegionData(1,X,Y,F,Ymid);
		res = residualEstimate_region(xreg,yreg,ymidreg,freg,nsamples,{1});
		
		for region = 2 : nregions  % MBVP
			[xreg,yreg,freg,ymidreg] = getRegionData(region,X,Y,F,Ymid);
			res_reg = residualEstimate_region(xreg,yreg,ymidreg,freg,nsamples,{region});
			res = cat(2,res,0,res_reg);  % pad with 0
		end
		
	end  % residualEstimate

%---------------------------------------------------------------------------

	function res = residualEstimate_region(X,Y,ymid,F,nsamples,region)
		% In a region, estimate the residual in each mesh subinterval.
		
		% Compute the max norm of the residual at Cres points in each subinterval.
		% The norm is computed with the value of the solution (and thresh) as weights.
		% When we trust the asymptotic behavior, the residual is only sampled at
		% the midpoint. Otherwise, the residual is sampled at its three local max.
		% ymid is the solution at midpoints.
		switch nsamples
			case 1
				cres = 1/2;
			case 2
				cres = [ 1/2 - sqrt(15)/10, 1/2 + sqrt(15)/10];
			case 3
				cres = [ 1/2 - sqrt(15)/10, 1/2, 1/2 + sqrt(15)/10];
		end
		
		x = X(1:nstages:end);
		y = Y(:,1:nstages:end);
		yp = F(:,1:nstages:end);
		
		h = diff(x);
		
		thresh = threshold(:,ones(1,numel(cres)));
		res = zeros(size(h));
		
		for i = 1 : numel(h)
			xres = x(i) + cres*h(i);
			[yres,ypres] = ntrp4h(xres,x(i),y(:,i),x(i+1),y(:,i+1),...
				ymid(:,i),yp(:,i),yp(:,i+1));
			residual = ypres - odeFcn_region(xres,yres,region);
			
			wtdRes = residual ./ max(abs(yres),thresh);
			normRes = max(max(abs(wtdRes)));
			scaledRes = abs(h(i)) * normRes;
			
			res(i) = scaledRes;
		end
	end  % residualEstimate_region

%---------------------------------------------------------------------------

	function [XX,YY,FF] = newSolutionProfile(X,Y,Ymid,F,errEst)
		%NEW_SOLUTION_PROFILE  Redistribute mesh points and approximate the solution.
		
		% Detect mesh oscillations: Was there a mesh with
		% the same number of nodes and a similar residual?
		% If so, only allow for adding mesh points.
		nintervals = length(errEst);
		maxerr = max(errEst);
		idx = find(meshHistory(:,1) == nintervals);  % idx could be empty
		errorReduction = abs(meshHistory(idx,2) - maxerr)/maxerr;
		oscLikely = any( errorReduction < errorReductionGuard);
		
		meshHistory(end+1,:) = [nintervals,maxerr];
		canRemovePoints = ~oscLikely;
		
		% modify the mesh, interpolate the solution
		[xreg,yreg,freg,ymidreg,errEstReg] = getRegionData(1,X,Y,F,Ymid,errEst);
		[XX,YY,FF] = newSolutionProfile_region(xreg,yreg,ymidreg,freg,errEstReg,...
			{1},canRemovePoints);
		
		for region = 2 : nregions  % MBVP
			[xreg,yreg,freg,ymidreg,errEstReg] = getRegionData(region,X,Y,F,Ymid,errEst);
			[xxreg,yyreg,ffreg] = ...
				newSolutionProfile_region(xreg,yreg,ymidreg,freg,errEstReg,...
				{region},canRemovePoints);
			XX = cat(2,XX,xxreg);
			YY = cat(2,YY,yyreg);
			FF = cat(2,FF,ffreg);
		end
		
	end  % newSolutionProfile

%---------------------------------------------------------------------------

	function [XX,YY,FF] = newSolutionProfile_region(X,Y,ymid,F,errEst,region,...
			canRemovePoints)
		% In a region, redistribute mesh points and approximate the solution.
		
		pow = 5;
		
		% extract the derivative at mesh points
		yp = F(:,1:nstages:end);
		
		XX(1) = X(1);
		YY(:,1) = Y(:,1);
		FF(:,1) = F(:,1);
		
		dirx = sign(X(end)-X(1));
		xidx = 1;
		i = 1;
		
		while i <= numel(errEst)
			
			if errEst(i) <= rtol
				
				if canRemovePoints && ...
						((i+2) <= numel(errEst)) && all(errEst(i:i+2) < 0.1*rtol)
					% consider consolidating mesh intervals
					
					xi = X(xidx);
					yi = Y(:,xidx);
					xip1 = X(xidx+nstages);
					yip1 = Y(:,xidx+nstages);
					xip2 = X(xidx+2*nstages);
					yip2 = Y(:,xidx+2*nstages);
					xip3 = X(xidx+3*nstages);
					yip3 = Y(:,xidx+3*nstages);
					fip3 = F(:,xidx+3*nstages);
					hnew = (xip3 - xi)/2;
					
					% predict new residuals
					C1 = errEst(i)   / abs(xip1-xi)^pow;
					C2 = errEst(i+1) / abs(xip2-xip1)^pow;
					C3 = errEst(i+2) / abs(xip3-xip2)^pow;
					predEst = max([C1,C2,C3])*abs(hnew)^pow;
					
					if predEst < 0.5 * rtol  % replace 3 intervals with 2
						xx = xi + hnew*[c,1,(1+c)];
						yy = zeros(neqn,numel(xx));
						% interpolate xx from xi,xip1; then from xip1,xip2; then from xip2,xip3
						idx = find(dirx*(xx - xip1) <= 0);
						if ~isempty(idx)
							yy(:,idx) = ntrp4h(xx(idx),xi,yi,xip1,yip1,...
								ymid(:,i),yp(:,i),yp(:,i+1));
						end
						idx = find( (dirx*(xx -xip1) > 0) & (dirx*(xx - xip2) <= 0));
						if ~isempty(idx)
							yy(:,idx) = ntrp4h(xx(idx),xip1,yip1,xip2,yip2,...
								ymid(:,i+1),yp(:,i+1),yp(:,i+2));
						end
						idx = find( dirx*(xx - xip2) > 0);
						if ~isempty(idx)
							yy(:,idx) = ntrp4h(xx(idx),xip2,yip2,xip3,yip3,...
								ymid(:,i+2),yp(:,i+2),yp(:,i+3));
						end
						ff = odeFcn_region(xx,yy,region);
						
						xx(  end+1) = xip3;
						yy(:,end+1) = yip3;
						ff(:,end+1) = fip3;
						
						i = i + 3;
						xidx = xidx + 3*nstages;
						
					else
						xx = X(   xidx+1 : xidx+nstages);
						yy = Y(:, xidx+1 : xidx+nstages);
						ff = F(:, xidx+1 : xidx+nstages);
						i = i + 1;
						xidx = xidx + nstages;
						
					end
					
				else
					xx = X(   xidx+1 : xidx+nstages);
					yy = Y(:, xidx+1 : xidx+nstages);
					ff = F(:, xidx+1 : xidx+nstages);
					i = i + 1;
					xidx = xidx + nstages;
					
				end
				
			else  % errEst(i) > rtol
				% split mesh interval
				xi = X(xidx);
				yi = Y(:,xidx);
				xip1 = X(  xidx+nstages);
				yip1 = Y(:,xidx+nstages);
				fip1 = F(:,xidx+nstages);
				
				if errEst(i) > 250 * rtol
					% split into three -- introduce two points
					hnew = (xip1 - xi)/3;
					xx = xi + hnew*[c,1,(1+c),2,(2+c)];
				else
					% split into two -- introduce one point
					hnew = (xip1 - xi)/2;
					xx = xi + hnew*[c,1,(1+c)];
				end
				yy = ntrp4h(xx,xi,yi,xip1,yip1,ymid(:,i),yp(:,i),yp(:,i+1));
				ff =  odeFcn_region(xx,yy,region);
				xx(  end+1) = xip1;
				yy(:,end+1) = yip1;
				ff(:,end+1) = fip1;
				i = i + 1;
				xidx = xidx + nstages;
			end
			XX(   end+1 : end+numel(xx)) = xx;
			YY(:, end+1 : end+numel(xx)) = yy;
			FF(:, end+1 : end+numel(xx)) = ff;
		end
		
	end  % newSolutionProfile_region

%---------------------------------------------------------------------------

	function sol = outputSol(X,Y,F,ymid)
		%OUTPUT_SOL  Assembly the solution structure.
		sol.solver = solver_name;
		
		x = reshape(X,1,[]);
		y = reshape(Y,neqn,[]);
		
		if nparam > 0
			neqn = neqn - nparam;
			parameters = y( neqn+1 : neqn+nparam, 1);
			sol.parameters = parameters;
		end
		
		[xreg,yreg,freg,ymidreg] = getRegionData(1,x,y,F,ymid);
		[xout,yout,ypout,ymidout] = outputData(xreg,yreg,freg,ymidreg);
		
		for region = 2 : nregions   % MBVP
			[xreg,yreg,freg,ymidreg] = getRegionData(region,x,y,F,ymid);
			[xoutreg,youtreg,ypoutreg,ymidoutreg] = outputData(xreg,yreg,freg,ymidreg);
			xout = cat(2,xout,xoutreg);
			yout = cat(2,yout,youtreg);
			ypout = cat(2,ypout,ypoutreg);
			% add zeros for internal interface intervals
			ymidout = cat(2,ymidout,zeros(neqn,1),ymidoutreg);
		end
		
		sol.x = xout;
		sol.y = yout;
		
		sol.idata.ymid = ymidout;
		sol.idata.yp = ypout;
	end  % outputSol

%---------------------------------------------------------------------------

	function [xout,yout,ypout,ymidout] = outputData(x,y,f,ymid)
		%OUTPUT_DATA  Prepare data for the solution structure.
		xout = x(1:nstages:end);
		yout = y(1:neqn,1:nstages:end);
		ypout   = f(1:neqn,1:nstages:end);
		ymidout = ymid(1:neqn,:);
	end  % outputData

%---------------------------------------------------------------------------

	function MBVP = updateMBVP(X)
		%UPDATE_MBVP  Update indices of the internal boundary points for MBVPs.
		
		idx = find(diff(X) == 0);
		MBVP.LBCidx = [1, idx+1];        % Index of left boundary points in X
		MBVP.RBCidx = [idx, length(X)];  % Index of right boundary points in X
		
		% Index of mesh intervals corresponding to the internal interfaces
		MBVP.MIDidx = [0, (MBVP.RBCidx - (1:nregions))/nstages + (1:nregions)];
	end  % updateMBVP

%---------------------------------------------------------------------------

	function [xreg,yreg,freg,ymidreg,errest] = getRegionData(region,X,Y,F,Ymid,ErrEst)
		%GET_REGION_DATA  Extract mesh points and solution data for a given region.
		if ismbvp
			xidx = MBVP.LBCidx(region):MBVP.RBCidx(region);
			xreg = X(xidx);
			yreg = Y(:,xidx);
			freg = F(:,xidx);
			if nargout > 3
				mididx = MBVP.MIDidx(region)+1:MBVP.MIDidx(region+1)-1;
				ymidreg = Ymid(:,mididx);
				if nargout > 4
					errest = ErrEst(:,mididx);
				end
			end
		else  % pass through for two-point BVPs
			xreg = X;
			yreg = Y;
			freg = F;
			if nargout > 3
				ymidreg = Ymid;
				if nargout > 4
					errest = ErrEst;
				end
			end
		end
		
	end  % getRegionData

%---------------------------------------------------------------------------

	function uintyp = uinttype(N)
		%UINTTYPE Returns the approprate uint** type according to N
		% junziyang 2009-05-30
		if N<0
			error('N<0. Please use inttype instead.');
		end
		if N>intmax('uint32')
			uintyp = 'unit64';
		elseif N>intmax('uint16')
			uintyp = 'unit32';
		elseif N>intmax('uint8')
			uintyp = 'uint16';
		else
			uintyp = 'uint8';
		end
	end

%---------------------------------------------------------------------------

end  % bvp5c

Contact us at files@mathworks.com