Code covered by the BSD License  

Highlights from
GAFFE A toolbox for solving evolutionary nonlinear PDEs

image thumbnail
from GAFFE A toolbox for solving evolutionary nonlinear PDEs by Edward Grace
This toolbox implements the well known split-step Fourier technique for solving nonlinear PDEs.

[z,u1,X,KX]=evolve(varargin)
%EVOLVE Evolve a generalised nonlinear wave equation.
%
%   Given an initial (sampled) field and its coordinate system, this code
%   efficiently evolves the signal (usually a pulse or beam) according to
%   the specified operators up to the maximum evolutionary value.
%
%   The initial field, assumed to be fft zero center ordered U0 and a
%   corresponding vector describing the coordinate system are (at minimum)
%   all that are required.  The propagation engine will then propagate this
%   field over the default distance returning the final field in U1, its
%   coordinate system vector (which may differ due to sampling changes) and the
%   Z distance.
%
%   If the initial field is a 1D vector the algorithm assumes you are
%   modeling the 1D NLSE.  This is typically used to model fibre
%   propagation.
%
%     [Z, U1, T1, BETA1] = EVOLVE(U0, T);
%
%   If the initial condition is a 2D matrix the algorithm assumes you wish
%   to model the 2D NLSE.  This is typically used to model nonlinear (Kerr
%   type) beam propagation in the paraxial approximation. If just a single
%   coordinate vector is supplied the field is assumed to have a
%   symmetrical coordinate system.
%
%     [Z,U1,X1,KX1,Y1,KY1] = EVOLVE(U0,X);
%
%   To propagate fields that are asymmetrical with respect to X and Y,
%   either because they are inherently asymmetrical or they are sampled on
%   an asymmetric grid you should supply the coordinate vector for the
%   other dimension (Y).
%
%     [Z,U1,X1,KX1,Y1,KY1] = EVOLVE(U0,X,Y);
%
%   You can alter the internal behaviour by modifying the options
%   structure, described below and passing it as the last argument, either
%   by calling with.
%
%     [Z,U1,X1,KX1,Y1,KY1] = EVOLVE(U0,X,OPTIONS);
%
%   or, for asymmetrical fields.
%
%     [Z,U1,X1,KX1,Y1,KY1] = EVOLVE(U0,X,Y,OPTIONS);
%
%
%OPTIONS STRUCTURE
%
%   The default options for the engine can be determined by calling
%   SOLVENLSE2 with the single argument 'defaults', which will return a
%   structure containing the default options.
%
%     OPTIONS = EVOLVE('defaults');
%
%   The option fields are described below.
%
%     TolGlobal          The global error tolerance to aim for.  This is a
%                        measure of how good the 4th order split-step
%                        method approximates commutation of the linear and
%                        nonlinear operators.
%
%     TolAliased         A measure of how closely we approximate the
%                        finitely represented function before we consider
%                        it to be aliased, either in real space or in
%                        frequency space.
%
%     FracRAliased       When more a fraction of amplitude TolAliased
%                        greater than the peak field amplitude or a
%                        fraction of power greater than TolAliased is found
%                        outside of the normalised radius R, the field is
%                        considered to be aliased.
%
%                        When the field is aliased the grid size is
%                        increased.
%
%     FracROverSpecified When the field is well contained (not considered
%                        to be aliased) within a small fraction of the
%                        space it is considered to be over specified.  When
%                        this occurs the grid is reduced to accomodate the
%                        new field.
%
%     MaxZ               The maximum propagation distance to allow.
%
%     MinDZ              The minimum z step to allow.
%
%     MaxDZ              The maximum z step to allow. If the z step that
%                        is permitted by the global error exceeds this
%                        value it dz is clamped to MaxDZ.
%
%     InitialDZ          The first z step to try.
%
%     MaxIterations      The maximum number of iterations to allow.
%
%     MaxN               The maximum size of a grid in samples along one
%                        edge, i.e. MaxN^2 total points.
%
%     Trace              If set on prints a verbose trace of the progress
%                        of the propagation engine.
%
%     Callback           A structure consisting of function handles that
%                        are called at certain points during the
%                        propagation process.
%
% CALLBACK STRUCTURE
%
%    The callback option is a structure of callback functions which should
%    take no arguments.  By default the called functions should use
%    'evalin' to access the variables in the caller workspace (inside
%    EVOLVE).
%
%      BeginIteration    Called at the beginning of an iteration.
%
%      EndIteration      Called at the end of an iteration.
%
%
%See also: EVOLVEMESHSET, DefaultEndIteration, GAFFE_DEMO_LINEAR,
%GAFFE_DEMO_SOLITON, GAFFE_DEMO_STEEPEN, GAFFE_DEMO_GAUSSIAN

% $Author: graceej $ $Date: 2010/05/24 08:23:06 $
% $Revision: 1.10 $
% Copyright (c) 2009, Edward J. Grace
% All rights reserved.
 
% Redistribution and use in source and binary forms, with or 
% without modification, are permitted provided that the following 
% conditions are met:
 
%     * Redistributions of source code must retain the above 
%       copyright notice, this list of conditions and the following 
%       disclaimer.
%     * Redistributions in binary form must reproduce the above 
%       copyright notice, this list of conditions and the following 
%       disclaimer in the documentation and/or other materials 
%       provided with the distribution.
%     * Neither the name of the Imperial College London nor the 
%       names of its contributors may be used to endorse or 
%       promote products derived  this software without specific 
%       prior written permission.
 
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 
% CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 
% INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 
% DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS 
% BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 
% EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 
% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
% DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON 
% ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 
% TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF 
% THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
% SUCH DAMAGE.

function [z,u1,X,KX]=evolve(varargin)

% Initial fudge value.  This should be expunged properly.  Its purpose is
% to prevent trying to down-size a field that has been upscaled too soon.
%
% In practice we should have a more careful set of checks that defaults to
% the virgin field (before being propagated).
%Fudge1 = 2; % Moved to options.

%% Setup options
% Define the default options, these are options which appear reasonable for
% general problems that I have encountered. They are by no means
% authoritative.
% FracROverSpecified is (1 - 1/G)^2 where G is the golden ratio.
G=(1+sqrt(5))/2;
defaultopt = struct(...
    'TolGlobal',1E-6,...
    'TolAliased1',1E-8,...
    'TolAliased2',1E-9,...
    'Fudge1',2,...
    'FracRAliased',1/G,...
    'FracROverSpecified',1/G/G/G,...
    'MaxZ',100,...
    'MinDZ',10^ceil(log10(eps*1E4)),...
    'MaxDZ',10,...
    'InitialDZ',[],...
    'MaxIterations',1E4,...
    'MaxN',6718464,...
    'Trace',0,...
    'User1',[],...
    'Callback',struct(...
        'BeginIteration',[],...
        'EndIteration',[],...
        'OperatorLinear',@DefaultDispersion,...
        'OperatorNonlinear',@DefaultKerr,...
        'EvolveStep',@DefaultEvolveStep,...
        'Mesh',evolvemeshset('default')...
        )...
    );
% If we are called with just "defaults" and are expected to return just one argument, 
% return the default options.  This is the format expected by 'optimget'.
if nargin==1 && nargout <= 1 && isequal(varargin{1},'defaults')
    z = defaultopt;
    return
end



%% Parse calling arguments
options = struct();
% Obtain initial field.
u0 = varargin{1};
% Make sure that the field is square (same number of samples along each
% edge) and set the size variable N.
N = size(u0);
% Check that there is no leading singleton dimension.
if N(1) == 1
    throw(MException('SanityCheck:LeadingSingletonDimension','Please reshape the input field and coordinates to avoid leading singleton dimensions'));
end
% Determine number of dimensions (a 1D signal has two dimenions, one is a
% singleton).
NDim = ndims(u0);
% Get a list of non singleton dimensions
NonSingletonDimensions = find(N>1);
x = cell(NDim,1);
for idx = 2:nargin
    if isstruct(varargin{idx})
        options = varargin{idx};
    else
        x{NonSingletonDimensions(idx-1)}=varargin{idx};
    end
end
for d = 1:length(x)
    if isempty(x{d})
        x{d} = [0];
    end
end




%% Merge options with existing options.
o=structmerge(defaultopt,options);

%% Sanity check options.
if o.FracROverSpecified > 1 || o.FracROverSpecified < 0
    throw(MException('SanityCheck:FracROverSpecified','FracROverSpecified should be < 1.0 and > 0'));
end
if o.FracRAliased > 1 || o.FracRAliased < 0
    throw(MException('SanityCheck:FracRAliased','FracRAliased should be < 1 and > 0.'));
end
if o.FracROverSpecified > o.FracRAliased
    throw(MException('SanityCheck:FracROverSpecified','FracROverSpecified should be less than FracRAliased'));
end
if ~isempty(o.InitialDZ) && o.InitialDZ < o.MinDZ
    throw(MException('SanityCheck:InitialDZ','InitialDZ should be greater than MinDZ'));
end

% Check callbacks.  If they are not empty check if they are callback
% functions.  If not, then complain!
if ~isempty(o.Callback.BeginIteration) && ~isa(o.Callback.BeginIteration,'function_handle')
    throw(MException('SanityCheck:CallbackNotFunction','Callback.BeginIteration should be a function handle.'));
end
if ~isempty(o.Callback.EndIteration) && ~isa(o.Callback.EndIteration,'function_handle')
    throw(MException('SanityCheck:CallbackNotFunction','Callback.EndIteration should be a function handle.'));
end




if prod(N)^(1/NDim) > o.MaxN
    throw(MException('SanityCheck:FieldTooBig',sprintf('The field size exceeds the maximum dimension MaxN=%i',MaxN')));
end

%% Setup supporting coordinate systems and transforms.
% U0 should always contain the most recent fft of the field.
U0 = fftn(u0); 


%% Initialise the field according to the initialisation callback.
N_new = o.Callback.Mesh.SetInitialSize(N);
if any(N_new ~= N)
    u0 = fftnpad(u0,N_new); 
    U0 = fftn(u0);
    if o.Trace
        fprintf('Resizing initial field from %s to %s.\n',sprintsize(N),sprintsize(N_new));
    end
end
for d = 1:NDim
    [x{d},dx{d},kx{d},dkx{d}] = fftspace(x{d}*(N_new(d)/N(d)),N_new(d));
end
N = N_new;

%% Grid the spaces
X = cell(size(x)); KX = cell(size(x));
[X{:}]  = ndgrid(x{:});
[KX{:}] = ndgrid(kx{:});
N_mesh = N;

%% Initialise the callback to be executed at the beginning and end of each iteration.



% Call the begin iteration callback, if it is defined.
if ~isempty(o.Callback.BeginIteration)
    is_terminated_by_callback = feval(o.Callback.BeginIteration,'begin');
end

% The callback should, by default, expect no arguments.  If there is an
% argument called 'init' the callback should internally clear all its
% persistent variables.
if ~isempty(o.Callback.EndIteration)
    feval(o.Callback.EndIteration,'begin');
end
    
%% Select initial dz.
if isempty(o.InitialDZ)
    % The step size should be the minimum of a step size that offers a
    % maximum nonlinear phase shift of (say) pi/8 and a step size that offers a
    % maximum linear phase shift in the spectrum of (say) pi/4 or a tenth of the
    % total maximum propagation distance (to make it worthwhile simulating
    % something).
    
    % Step size based on sampling maximum z distance.
    dz_max_Z = o.MaxZ/10.0;
    
    % Step size if we assume a maximum nonlinear phase shift.
    dz_max_NL = (pi/8)./abs(maximumvalue(u0)).^2;
        
    % Step size based on maximum spectral phase rotations.
    dz_max_L = dz_max_Z*ones(1,NDim);
    for d = NonSingletonDimensions
        dz_max_L(d) = 2*(pi/4)./(width(U0,KX{d})/2);
    end
    
    % Initial dz should be the minumum of these.
    dz = min([dz_max_NL dz_max_L dz_max_Z]);
    
    % If this dz is too small then the initial dz should be set to several
    % orders of magnitude more than the minimum allowed step size.
    dz=max([dz o.MinDZ*100]);
    
    % Since we know how the error should behave as we scale dz, assuming we
    % are in a sensible region, it should be possible to rapidly find the
    % ideal step size by simple root finding.
    % @bug Following function should probably involve a reentrant call to
    % solvenlse.
    if o.Trace
        fprintf('Initial estimate for dz=%e, refining.\n',dz);
    end
    % Root find to determine the dz for an error that is half the tolerated
    % global error.  
    dz = estimate_dz(o.TolGlobal*0.5,u0,U0,dz,X,KX,o.Callback.EvolveStep,o.Callback.OperatorLinear,o.Callback.OperatorNonlinear);
    if o.Trace
        fprintf('Initial stepsize refined to dz=%e.\n',dz);
    end
else
    dz = o.InitialDZ;
end


%% Main loop.  
%
% We iterate until any of the conditions for exit have been met, for
% example exceeding the maximum number of iterations, maximum sample size
% etc.
n = 0; % Iteration counter.
z = 0; % Current z position @bug Calculated by addition of floating point.
% Size of the resized, new field.  
is_terminated_by_callback = 0; % Flag to indicate that a callback function requests exit.
% True for each dimension of the field that requires enlarging in real
% space.
is_too_big_x = zeros(1,NDim);
% True for each dimension of the field that requires enlarging in real
% space.
is_too_big_kx = zeros(1,NDim);
% True for each dimension of the field that requires shrinking in
% real space.
is_too_small_x = zeros(1,NDim);
% True for each dimension of th efield that requires shrinking in frequency
% space.
is_too_small_kx = zeros(1,NDim);
% The step length of the last propagation.
dz_try=dz;
dz_next=dz;
dz_last=dz_try;


% Briefly the algorithm works like this.
%
% Check to see if the field is over represented in real space or frequency
% space and did not end up aliased because of the last attempted propagation.
% If it is over specified, then reduce the number of samples appropriately and
% recalculate the associated coordinate systems.
%
% Propagate the field over the current chosen test step length dz_try.
%
% If the field ends up aliased in real or k space, i.e. it's expanded too
% much because of propagation or too much in k space due to large
% nonlinearity then enlarge the mesh and retry the propagation.
%

% Force N_mesh to be zero so that the mesh is made.
N_mesh = 0*N;
n_timeout=0;
% Used to control the restarting of an iteration. 
is_retry_iteration=0;
is_final_step=0;
is_ended=0;
while n < o.MaxIterations && ...
        z < o.MaxZ && ...
        ~is_terminated_by_callback && ...
        ~is_ended
    % Start this iteration by assuming we won't retry the iteration.
    is_retry_iteration=0;

    if(prod(N) > o.MaxN)
        throw(MException('SolveNLSE:Bailout','Grid size exceeds maximum'));
    end
    if (dz_try < o.MinDZ & ~is_final_step)
        throw(MException('SolveNLSE:Bailout','Step size is smaller than minimum'));
    end

    % Call the begin iteration callback, if it is defined.
    if ~isempty(o.Callback.BeginIteration)
        is_terminated_by_callback = feval(o.Callback.BeginIteration);
    end

    if o.Trace
        fprintf('Start attempt of step %d.\n',n);
    end
    
    % Only allow attempted signal truncation after a certain number of
    % iterations since the last signal truncation.
    % @bug This is a bug -- there is clearly a complex and subtle interplay
    % between the noise floor and signal resizing when up/down scaling.
    if n >= n_timeout
        % Check to see if the field is too narrow in real space and truncate it.
        % This is only attempted if previously it was not too big in real
        % space.
        if any(~is_too_big_x)
            for d = NonSingletonDimensions
                if ~is_too_big_x(d)
                    [u0, N_new(d)] = truncate_field(u0, o, d);
                    [kx{d},dkx{d},x{d},dx{d}] = fftspace(kx{d},N_new(d));
                end
            end
            if any(N_new ~= N)
                U0 = fftn(u0);
                is_too_small_x = N_new ~= N;
                N=N_new;
            end
        end
        % Repeat for spatial frequency representation.
        if any(~is_too_big_kx)
            for d = NonSingletonDimensions
                if ~is_too_big_kx(d)
                    [U0, N_new(d)] = truncate_field(U0, o, d);
                    [x{d},dx{d},kx{d},dkx{d}] = fftspace(x{d},N_new(d));
                end
            end
            if any(N_new ~= N)
                % N.B. MATLAB fft does not scale by 1/N.  By default we must scale
                % the spectral power to conserve energy.
                U0 = U0*prod(N_new./N);
                u0 = ifftn(U0);
                is_too_small_kx = N_new ~= N;
                N=N_new;
            end
        end
        n_timeout = n + o.Fudge1;
    end

    % Check to see if the new field size differs from the previous field
    % size.  If so, regenerate the coordinate meshes.
    if any(N ~= N_mesh)
        if o.Trace
            fprintf('Field size has changed from %s to %s, recalculating coordinate meshes.\n',sprintsize(N_mesh),sprintsize(N));
        end
        [X{:}] = ndgrid(x{:});
        [KX{:}] = ndgrid(kx{:});
        N_mesh=N;
    end

    % Check to see if the trial propagation distance dz_try exceeds the
    % maximum dz we have permitted (if any).  If so then cap it to the
    % upper limit.
    if dz_try > o.MaxDZ
        if o.Trace
            fprintf('Trial dz step (%e) exceeds MaxDZ (%e) capping to upper limit. \n',dz_try,o.MaxDZ);
        end
        dz_try = min(dz_try,o.MaxDZ);
    end

    % Propagate forwards one step of length dz and determine error
    % information using the supplied forward propagator.
    [local_error,u1,U1,uc,UC,uf,UF,U0] = ...
        o.Callback.EvolveStep(u0, U0, dz_try, X, KX, ...
        o.Callback.OperatorLinear, ...
        o.Callback.OperatorNonlinear);
    if o.Trace
        fprintf('Trial propagation over a distance dz=%0.4e \n',dz_try);
    end
    
    % If TolGlobal is NaN then the step size is not allowed to dynamically
    % change.
    if ~isnan(o.TolGlobal)
        % Determine the next step size to use depending on the current error
        % and global error tolerance.
        [dz_next, isok] = select_dz(dz_try, local_error, o.TolGlobal);
        % Check to see if we are within the error bound.  If not, discard this
        % step.
        % @bug The error could also be because the mesh is too small.
        if ~isok
            if o.Trace
                fprintf('Propagation step exceeds error limit, local error (%e) > TolGlobal (%e), discarding step.\n',local_error,o.TolGlobal);
            end
            % Try the new (smaller) step size.
            if dz_next > dz_try
                throw(MException('SanityCheck:StepSize','Next expected propagation step (%e) is larger than previous trial (%e).'),dz_next,dz_try);
            end
            dz_try = dz_next;
            % Make sure that we retry the iteration.  Don't do it immediately
            % however, first check to see if there are any other problems.
            is_retry_iteration = 1;
        else
            % The error bound was acceptable, report on new step size if it
            % differs.
            if o.Trace && dz_next ~= dz_try
                if dz_next > dz_last
                    fprintf('Increasing next step size to %0.4e .\n',dz_next);
                else
                    fprintf('Decreasing next step size to %0.4e .\n',dz_next);
                end
            end
        end
    end

    % Test to see if the field is aliased as a result of the propagation
    % step. If it is then we wish to rescale the initial field.
    for d = NonSingletonDimensions
        is_too_big_x(d)  = o.Callback.Mesh.IsAliasDown1(u1, o, d);
        is_too_big_kx(d) = o.Callback.Mesh.IsAliasDown1(U1, o, d);
    end


    % If the field is aliased in real space AND k space, nothing can be
    % done, the field is rubbish.  Since the field before starting
    % propagation should be fine the reason the field is now aliased in
    % real space and k space is because the propagation has been too
    % extreme.  An example would be something that has experienced
    % significant linear and nonlinear effects.  The only way to avoid this
    % without enlarging the spaces is to propagate over a smaller step.
    %
    % @bug Perhaps we should also increase the spectral and spatial
    % representation.
    if any(is_too_big_x & is_too_big_kx) 
        if o.Trace
            fprintf('Field aliased in both real space and k space, reducing step size.\n');
        end
        dz_try = dz_try/2;
        is_retry_iteration = 1;
        continue
    end
    
    
    %
    big_small_x = is_too_big_x & is_too_small_x;
    big_small_kx = is_too_big_kx & is_too_small_kx;
    

    % If the field has, for example, propagated a long way it will have expanded outside
    % the boundary of the computational window.  If this is the case we
    % should enlarge the real space (interpolate k space) and try
    % propagating again.
    if any(is_too_big_x) % Only test if there is a dimension that's aliased.
        for d = NonSingletonDimensions
            if is_too_big_x(d) 
                % Get the size of the newly upscaled field along this dimension.
                N_new(d) = o.Callback.Mesh.GetUpSize(N(d),o);
                % Adjust the coordinate systems for this dimension based on
                % the new sample size.
                [kx{d},dkx{d},x{d},dx{d}] = fftspace(kx{d},N_new(d));
                if o.Trace
                    fprintf('Propagation caused aliasing in real space along dimension %d.  Increasing x space from %s to %s samples.\n',d,sprintsize(N),sprintsize(N_new));
                end
            end
        end
        if any(N_new ~= N) % @bug this should be redundent.
            % Pad the signal based upon the new dimensions.
            u0 = fftnpad(u0,N_new);
            % Make sure the current transform does not lie!
            U0 = fftn(u0); % @bug This should not be strictly needed.
            % Retry the propagation.
            N = N_new;
            is_retry_iteration = 1;
        end
    end

    % If the field has, for example, experienced a lot of nonlinearity its spectrum can
    % expand outside the computational window.  As with the real space
    % window this means we should expand the spectral representation
    % (interpolate real space).
    if any(is_too_big_kx)
        for d = NonSingletonDimensions
            if is_too_big_kx(d) 
                N_new(d) = o.Callback.Mesh.GetUpSize(N(d),o);
                [x{d},dx{d},kx{d},dkx{d}] = fftspace(x{d},N_new(d));
                if o.Trace
                    fprintf('Propagation caused aliasing in frequency space along dimension %d.  Increasing kx space from %s to %s samples.\n',d,sprintsize(N),sprintsize(N_new));
                end
            end
        end
        if any(N_new ~= N) % @bug this should be redundent.
            % N.B. Scale by the number of samples to conserve power.
            U0 = fftnpad(U0,N_new)*prod(N_new./N);
            u0 = ifftn(U0);
            N = N_new;
            is_retry_iteration=1;
        end
    end
    
    % Determine if we should retry the iteration.  If so, use the samller
    % of the current step and the suggested next step.
    if is_retry_iteration
        dz_try = min([dz_try dz_next]);
        continue;
    end


    % At this point we have successfully completed an iteration, stepping
    % forward a distance dz_try.
    z = z + dz_try;  
    
    
    % Call the begin iteration callback, if it is defined.
    if ~isempty(o.Callback.EndIteration)
        is_terminated_by_callback = feval(o.Callback.EndIteration);
    end
    
    % The propagated field u1 should now be propagated forward.  So the
    % input field becomes the previous output field.
    u0 = u1;
    U0 = U1;
    
    
    % Check to see if the next step over a distance of dz_next will take us
    % beyond the maximum propagation distance of the simulation. If so we
    % override the next step length to make sure it takes us exactly to the
    % end.
    if is_final_step
        is_ended=1;
    elseif z + dz_next > o.MaxZ
        % If we are close to machine precision o.MaxZ - z could reverse
        % sign and unwittingly cause us to try and propagate in reverse.
        % Formally make certain it has a consistent sign.
        sn = sign(o.MaxZ);
        if ~sn; sn=1; end
        dz_try = sn*abs(o.MaxZ - z);
        is_final_step=1;
        if o.Trace
            fprintf('Overriding step length for final step to %e.\n',dz_try);
        end
    else
        dz_last = dz_try;
        dz_try = dz_next;
    end
    
    if o.Trace
        fprintf('Iteration %i, z=%f complete.\n\n',n,z);
    end
    

    % Increment iteration counter.
    n = n + 1;
end

%% Finalise the begin / end of iteration callback if it exists.


% Call the begin iteration callback, if it is defined.
if ~isempty(o.Callback.BeginIteration)
    is_terminated_by_callback = feval(o.Callback.BeginIteration,'end');
end

% Call the begin iteration callback, if it is defined.
%
% This gives the callback an opportunity to finalise its internal state so
% that when it is called with 'get' it can return what is wanted.  For
% example the callback may have preallocated over-sized buffers -- this
% gives it an opportunity to resize them correctly.

if ~isempty(o.Callback.EndIteration)
    feval(o.Callback.EndIteration,'end');
end

end



%TRUNCATE_FIELD Optionally truncate a representation of a field.
%
%   This helper function checks to see if a field is aliased, if not it
%   attempts to truncate the field so that it fits in between being too
%   small (over specified) and too large (aliased).
%
function [u,N_new] = truncate_field(u,o,dim)
N=size(u,dim);
N_new=N;

% Check to see if we are working along a trivial (singleton) dimension.  If
% so just exit.
if N == 1
    too_big = 0; too_small = 0; u=u; 
    return
end


% If the field before we try to propagate it forwards is aliased
% (by this measure) we have irrevocably lost information and
% subsequent propagation will produce rubbish -- so we *must* bail
% out at this point.
is_too_big = o.Callback.Mesh.IsAliasDown1(u, o, dim);
if is_too_big
    throw(MException('SanityCheck:FieldAliased',sprintf('The initial representation appears to be aliased along dimension %d.',dim)));
end

% Check to see if the field is over represented.  This is equivalent to it
% not being aliased on the smaller grid.
is_too_small  = ~o.Callback.Mesh.IsAliasDown2(u, o, dim); 
if is_too_small
    if o.Trace
        fprintf('Field representation over specified, attempting truncation.\n');
    end
    %Determine the new number of samples to use for the function u.
    N_new = o.Callback.Mesh.GetDownSize(size(u,dim),o);
    
    % Under certain circumstances it is possible for N_new > N i.e. 
    % due to rounding error when we have a small number of samples. It is 
    % dependent on the (unknown) callback so anything is possible.
    %
    % We check to see if the proposed number of samples *is* smaller.  If
    % so we down sample.  If not we leave things as they are.
    if N_new < N
        % Pad u1, since N < size(u) it will "unpad", remove a section from
        % the centre.
        N_all = size(u);
        N_all(dim) = N_new;
        u1 = fftnpad(u, N_all);
        
        % Truncating the field, effectively filtering it with a
        % rectangular window, will raise the noise floor (wings)
        % of the associated domain.  We should therefore check that
        % truncating the field as we intend does not end up causing 
        % aliasing in the other domain 
        %
        % N.B. it could be either
        % fft2 or ifft2, it doesn't matter as we are only interested in the
        % magnitude of the result.
        U1 = fftn(u1);
        if o.Callback.Mesh.IsAliasDown1(U1, o, dim)
            if o.Trace
                fprintf('Truncated representation is aliased in complement space along dimension %d, reverting field.\n',dim);
            end
            N_new = N;
        else
            u = u1;
        end
    else
        N_new = N;
        if o.Trace
            fprintf('Truncating representation would not significantly reduce field size, reverted.\n');
        end
    end
end
end

%SPRINTSIZE Given a size vector generate a string representing the size.
function S = sprintsize(SZ)
S = sprintf('%dx',SZ(1:end-1));
S = [S sprintf('%d',SZ(end))];
end

%MAXIMUMVALUE Given an N dimensional array, determine the maximum value.
function V = maximumvalue(A)
ND = ndims(A);
V = A;
for dim = 1:ND
    V = max(V);
end
end


%SELECT_DZ Determine the stepsize and if it is within the error bound.
%
%    Given the prevailing step size DZ, the local error DELTA and the
%    required target global error DELTA_G determine the new stepsize and if
%    it is suitable for use in the next propagation step.
%
%    [DZ_NEW,ISOK] = SELECT_DZ(DZ,DELTA,DELTA_G);
%
%    When ISOK is zero (false) the returned step size is too large and any
%    result obtained propagating over that distance should be discarded.
function [dz,isok] = select_dz(dz,delta,delta_G) 
isok=1;
% Determine new step size based on current step size and error targets.
if delta >= 2*delta_G
    dz=dz/2.0;
    isok=0; % Not ok, should discard solution.
elseif delta > delta_G && delta < 2*delta_G
    dz=dz/2.0^(1/3);
elseif delta < 1/2*delta_G
    dz=dz*2.0^(1/3);
else
    dz=dz;
end
end


%ESTIMATE_DZ Determine an initial dz based on 4th order error behaviour.
%
%   Given an initial field, its Fourier representation and coordinate 
%   system and a (reasonable) estimate for dz functions describing the
%   linear and nonlinear operators and the associated coordinate systems,
%   this function attempts to determine a value of dz that satisfies the
%   target error. 
%
%   Providing the initial guess is within the range of dz values over which
%   we expect to see cubic error behavior this works well.  If we are
%   unwittingly outside this area the gradient of the line is likely to be
%   significantly different from 3 and the goodness of fit (standard
%   deviation of residuals) will be poor. 
%
%   We then pick the dz which produces an error that is closest to the
%   target error as the staring point.
%   
%     DZ = ESTIMATE_DZ(TARGET_ERROR,U0, FFT_U0, DZ_0,X,KX, funLinear, funNonlinear);
function [dz, actual_error] = estimate_dz(target_error, u0, U0, dz, ...
    X, ...
    KX, ...
    funStep, ...
    funLinear, ...
    funNonlinear)

%% Secant root finding method to obtain a good estimate of dz.

% Since we expect the error properties of the propagation technique to
% follow a cubic relationship with respect to dz (linear on log log plot)
% by root finding for the log(dz) that gives the log(target_error) we can
% expect to find the appropriate dz very quickly.  The secant method is
% highly effective for functions with smooth first and second derivatives
% and a simple single root.  We expect the error curve to meet both these
% criteria.
%
% Typically this determines dz to better than 0.1% within 2 iterations.
%
% When the method fails (which can occur in the case of no nonlinearity) the initial dz is used instead.
x_prior = log(dz);
x_now   = log(dz./2);

f = @(zeta)  log(funStep(u0, U0, exp(zeta), X, KX, funLinear, funNonlinear)) - log(target_error);

% Bootstrap method
f_prior = f(x_prior);
f_now   = f(x_now);

% First test
err_first = f_prior.^2;

% We expect the root to be determined to the precision below within typically 3 iterations.  
% If it requires more than 6 iterations we should bail out -- we are probably in
% a pathalogical region.
TolRoot = 1E-9;
for n=1:6
    x_next = x_now - (x_now - x_prior)./(f_now - f_prior) * f_now;
    f_prior = f_now;
    x_prior = x_now;
    x_now = x_next;
    f_now = f(x_now);
    % Convergence test, if we are calculating rubbish (e.g. NaN Inf) then exit.
    if ~isfinite(f_now) 
        break
        % If the error is less than the tolerance and better than the
        % initial guess then we have found the root.
    elseif f_now.^2 < TolRoot && f_now.^2 < err_first
        dz = exp(x_now);
        break
    end
end
end



%% CVS log information
% 
% $Log: evolve.m,v $
% Revision 1.10  2010/05/24 08:23:06  graceej
% * Improved documentation.
%
% Revision 1.9  2010/05/23 13:17:15  graceej
% * Added new demonstration and improved performance.  Corrected bug in callback and rationalised forms of callbacks.
%
% Revision 1.8  2010/05/22 20:50:06  graceej
% * Fixed a bug where the spectrum wasn't being updated from the previous run.
%
% Revision 1.7  2009/10/24 11:04:53  graceej
% * Modified to BSD license.
% * Modified so that a value of NaN for the TolGlobal prevents the dynamic step sizing from being carried out.
%
% Revision 1.6  2009/05/06 20:09:45  graceej
% * Updated make package target to include distribution of the license file and README.
%
% Revision 1.5  2009/05/06 16:30:34  graceej
% * Moved demos to main branch.
%
% Revision 1.4  2009/05/06 09:13:57  graceej
% * Use fftspace instead of makefftspace.
%
% Revision 1.3  2009/04/21 12:36:09  graceej
% * Corrected minor typo in initial grid sizing.
% * TODO Modify so that the input coordinate systems are simple linear vectors rather than of the same dimension as u.
%
% Revision 1.2  2009/04/20 17:38:09  graceej
% * Simplify treatment of final step.
%
% Revision 1.1  2009/04/17 11:42:32  graceej
% * Brought over auto_checking/BPC-LIB tag=end and checked in.
% * This old repository is now defunct.
%
% Revision 1.2  2009/04/17 11:37:25  graceej
% * Wholescale modification of the entire library.
%
% Revision 1.1.2.8  2008/11/06 17:41:12  graceej
% * Removed breakpoint line.
%
% Revision 1.1.2.7  2008/11/06 15:31:29  graceej
% * Added (fudge) User1 option.  This is undocumented.
%
% Revision 1.1.2.6  2008/09/16 15:35:10  graceej
% * Reduce default maximum number of iterations.
%
% Revision 1.1.2.5  2008/09/16 14:53:08  graceej
% * Forward integration carried out by callback.
% * Callback is by default the standard forward propagation.
%
% Revision 1.1.2.4  2008/09/16 14:37:10  graceej
% * Changed function names to reflect new names.
% * Fixed function end error.
%
% Revision 1.1.2.3  2008/09/16 14:32:18  graceej
% * Moved estimate_dz to local function.
%
% Revision 1.1.2.2  2008/09/16 14:30:59  graceej
% * Moved select_dz to local function.
%
% Revision 1.1.2.1  2008/09/16 14:28:47  graceej
% * Initial checkin.
%
% Revision 1.5.2.10  2008/09/15 18:18:42  graceej
% * Made mesh resizing / stepsize selection more robust.
% * Prevented bailout if minimum stepsize reached on last iteration.
% * Increased mesh maximum.
%
% Revision 1.5.2.9  2008/09/11 17:30:32  graceej
% * Asymmetric precision requirements for up/down scaling.
% * Uses humble fibonacci number mesh as default.
% * Fudge1 used to prevent mesh oscillation for at least 2 iterations.  Non ideal -- but appears to work well.
%
% Revision 1.5.2.8  2008/09/11 16:45:33  graceej
% * Added Fudge1 option to options.  Not ideal, but need to think more carefully about how to handle the case of oscillating mesh sizes.
%
% Revision 1.5.2.7  2008/09/11 14:39:03  graceej
% * Intermediate modification - intend to solve problem of oscillating mesh size.
%
% Revision 1.5.2.6  2008/09/09 14:17:17  graceej
% * Removed legacy code and improved error messages.
%
% Revision 1.5.2.5  2008/09/08 14:28:46  graceej
% * Modified to generalise the number of dimensions.
%
% Revision 1.5.2.4  2008/09/05 20:41:58  graceej
% * Use fftpadn instead of obsolete pad2.  TODO - modify pad2, to operate along a given dimension.
%
% Revision 1.5.2.3  2008/09/02 13:52:37  graceej
% * Modified to use power of two length signals by default.
%
% Revision 1.5.2.2  2008/08/29 14:00:37  graceej
% * Using Fibonacci length signals.
%
% Revision 1.5.2.1  2008/08/28 11:17:42  graceej
% * Initial changes, use Fibonacci number (golden) ratios for determining if things are or are not aliased.
%
% Revision 1.5  2008/08/22 13:38:20  graceej
% * Fixed minor logical error in final step.
%
% Revision 1.4  2008/08/21 17:21:46  graceej
% * By default assume we are considering the 1D NLSE and use dispersion as the default linear operator.
%
% Revision 1.3  2008/08/20 17:18:42  graceej
% * Removed explicit IsNonlinear check.  This is managed by setting Callback.OperatorNonlinear to @DefaultIdentity.
% * Replaced default propagation kernels with arbitrary linear and nonlinear function callbacks.
% * Modified to use improved initial dz function for arbitrary operators.
% * Call "step" instead of "propagate4" to carry out evolution with arbitrary operator functions.
% * Moved caching of linear operators to "step" function.
% * Corrected minor bug in final step at z = o.MaxZ
% * Corrected bug when aliasing occurs, reverting step size to the previous step size.
%
% Revision 1.2  2008/08/19 15:23:54  graceej
% * Corrected output arguments.
% * Corrected help documentation.
%
% Revision 1.1  2008/08/19 14:09:13  graceej
% * Modification of solvenlse2 to allow 1D signals.
% * Caught a few minor bugs.
%
% Revision 1.3  2008/08/16 16:33:28  graceej
% * Bug fix when aborting signal truncation.
%
% Revision 1.2  2008/08/15 21:50:02  graceej
% * Modified to use new isaliased which relies purely on power.
% * Modified to use alias_edge which determines the edge of aliasing using purely power.
% * Changed alias tolerance to reflect useage of power (approximately squares requried tolearance).
%
% Revision 1.1  2008/08/15 15:51:08  graceej
% * Renamed propagation_engine.m to solvenlse2.m
%
% Revision 1.22  2008/08/15 15:48:56  graceej
% * Changed function name from propagation_engine to solvenlse2.
% * Updated help.
%
% Revision 1.21  2008/08/15 15:40:28  graceej
% * Make command line backward compatible.
% * Fix bug in grid down sizing.
% * Make alias tolerance a little tighter by default.
%
% Revision 1.20  2008/08/15 13:33:19  graceej
% * Square symmetry still present, but should not be implicitly assumed in propagation_engine.
%
% Revision 1.19  2008/08/15 13:28:07  graceej
% * Modified to break assumption of a square grid.
%
% Revision 1.18  2008/08/15 13:09:40  graceej
% * First set of changes for extension to asymmetric mesh.
% * Explicitly add other dimension, but use Ny everywhere.
% * Currently do not consider x, will change at next checkin.
%
% Revision 1.17  2008/08/15 12:01:34  graceej
% * Operate assuming the N we are refering to is Ny, to make it easier to convert to  the asymmetric case.
% * Moved helper functions out to seperate M files as they will be the same for the asymmetric case.
%
% Revision 1.16  2008/08/15 00:21:15  graceej
% * Return KX as well for convenience.
%
% Revision 1.15  2008/08/14 23:57:24  graceej
% * Aliasing or unaliasing limits are set to be related to the golden ratio.
%
% Revision 1.14  2008/08/14 23:29:08  graceej
% * Fixed edge case in final dz when close to ZMax.
% * Added function for improving the estimate of the step size to use.
%
% Revision 1.13  2008/08/14 21:14:22  graceej
% * Made default initial conditions more conservative.
% * Fixed minor bug in final step selection for completion of the step to Zmax
%
% Revision 1.12  2008/08/14 19:49:00  graceej
% * Better estimate of initial step size.
% * Fixed minor tracing bug.
%
% Revision 1.11  2008/08/14 18:46:37  graceej
% * Fixed bug in truncation of over specified signal.
% * Improved comments.
%
% Revision 1.10  2008/08/14 17:19:53  graceej
% * Describe what the options do and how to set them.
%
% Revision 1.9  2008/08/14 17:02:46  graceej
% * Merged changes to head that delt with refactoring of the code.
%
% Revision 1.8.2.5  2008/08/14 16:59:40  graceej
% * Moved code that determines upscaling to seperate function.
% * Added lengthy commentary justifying the upscaling factor.
% * Removed redundent argument from downscaling function.
%
% Revision 1.8.2.4  2008/08/14 16:28:32  graceej
% * Moved repeated code to a function that attempts to down size the grid if it is not too big.
%
% Revision 1.8.2.3  2008/08/14 15:38:51  graceej
% * Inlined structmerge to manage the merger of options structures.
%
%

Contact us at files@mathworks.com