%RRT Class for rapidly-exploring random tree navigation
%
% A concrete subclass of the abstract Navigation class that implements the rapidly
% exploring random tree (RRT) algorithm.  This is a kinodynamic planner
% that takes into account the motion constraints of the vehicle.
%
% Methods::
%  RRT          Constructor
%  plan         Compute the tree
%  query        Compute a path
%  plot         Display the tree
%  display      Display the parameters in human readable form
%  char         Convert to string
%
% Properties (read only)::
%  graph        A PGraph object describign the tree
%
% Example::
%        goal = [0,0,0];
%        start = [0,2,0];
%        veh = Bicycle('steermax', 1.2);
%        rrt = RRT(veh, 'goal', goal, 'range', 5);
%        rrt.plan()             % create navigation tree
%        rrt.query(start, goal)  % animate path from this start location
%
% References::
% - Randomized kinodynamic planning,
%   S. LaValle and J. Kuffner,
%   International Journal of Robotics Research vol. 20, pp. 378-400, May 2001.
% - Probabilistic roadmaps for path planning in high dimensional configuration spaces,
%   L. Kavraki, P. Svestka, J. Latombe, and M. Overmars,
%   IEEE Transactions on Robotics and Automation, vol. 12, pp. 566-580, Aug 1996.
% - Robotics, Vision & Control, Section 5.2.5,
%   P. Corke, Springer 2011.
%
% See also Navigation, PRM, DXform, Dstar, PGraph.

% Copyright (C) 1993-2017, by Peter I. Corke
%
% This file is part of The Robotics Toolbox for MATLAB (RTB).
%
% RTB is free software: you can redistribute it and/or modify
% it under the terms of the GNU Lesser General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% RTB is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU Lesser General Public License for more details.
%
% You should have received a copy of the GNU Leser General Public License
% along with RTB.  If not, see <http://www.gnu.org/licenses/>.
%
% http://www.petercorke.com

% Peter Corke 8/2009.

%TODO
%   more info to the display method
%   distance metric choice or weightings
%   pass time and model options to the simulation

classdef RRT < Navigation

    properties
        npoints         % number of points to find
        graph           % graph Object representing random nodes

        simtime         % path simulation time

        xrange          % range of x coordinates
        yrange          % range of y coordinates

        speed           % speed of vehicle
        vehicle         % Vehicle class object describes kinematics

        revcost         % penalty for going backwards

        root            % coordinate of the root of the tree (3x1)
    end

    methods

        function rrt = RRT(vehicle, varargin)
        %RRT.RRT Create an RRT navigation object
        %
        % R = RRT.RRT(VEH, OPTIONS) is a rapidly exploring tree navigation
        % object  for a vehicle kinematic model given by a Vehicle subclass object VEH.
        %
        % R = RRT.RRT(VEH, MAP, OPTIONS) as above but for a region with obstacles
        % defined by the occupancy grid MAP.
        %
        % Options::
        % 'npoints',N    Number of nodes in the tree (default 500)
        % 'simtime',T    Interval over which to simulate kinematic model toward
        %                random point (default 0.5s)
        % 'goal',P       Goal position (1x2) or pose (1x3) in workspace
        % 'speed',S      Speed of vehicle [m/s] (default 1)
        % 'root',R       Configuration of tree root (3x1) (default [0,0,0])
        % 'revcost',C    Cost penalty for going backwards (default 1)
        % 'range',R      Specify rectangular bounds of robot's workspace:
        %                - R scalar; X: -R to +R, Y: -R to +R
        %                - R (1x2); X: -R(1) to +R(1), Y: -R(2) to +R(2)
        %                - R (1x4); X: R(1) to R(2), Y: R(3) to R(4)
        %
        % Other options are provided by the Navigation superclass.
        %
        % Notes::
        % - 'range' option is ignored if an occupacy grid is provided.
        %
        % Reference::
        % - Robotics, Vision & Control
        %   Peter Corke, Springer 2011.  p102.
        %
        % See also Vehicle, Bicycle, Unicycle.

            % invoke the superclass constructor, it handles some options
            rrt = rrt@Navigation(varargin{:});

            rrt.vehicle = vehicle;

            % handle the options not done by Navigation superclass
            opt.npoints = 500;
            opt.simtime = 0.5;
            opt.speed = vehicle.speedmax;
            opt.revcost = 1;
            opt.root = [0 0 0];

            [rrt,args] = tb_optparse(opt, varargin, rrt);

            if isempty(rrt.occgrid)
                opt = [];
                opt.range = 5;
                [opt,args] = tb_optparse(opt, args);

                % range can be specified as scalar, min/max, different min/max per
                % direction
                switch length(opt.range)
                    case 1
                        rrt.xrange = [-opt.range opt.range];
                        rrt.yrange = [-opt.range opt.range];
                    case 2
                        rrt.xrange = [-opt.range(1) opt.range(1)];
                        rrt.yrange = [-opt.range(2) opt.range(2)];
                    case 4
                        rrt.xrange = [opt.range(1) opt.range(2)];
                        rrt.yrange = [opt.range(3) opt.range(4)];
                    otherwise
                        error('bad range specified');
                end
            else
                rrt.xrange = [1 numcols(rrt.occgrid)];
                rrt.yrange = [1 numrows(rrt.occgrid)];
            end

            rrt.graph = PGraph(3, 'distance', 'SE2', ...
                'dweight', 2*pi/norm(sum([rrt.xrange; rrt.yrange])) );  % graph of points in SE(2)

        end

        function plan(rrt, varargin)
        %RRT.plan Create a rapidly exploring tree
        %
        % R.plan(OPTIONS) creates the tree roadmap by driving the vehicle
        % model toward random goal points.  The resulting graph is kept
        % within the object.
        %
        % Options::
        % 'goal',P        Goal pose (1x3)
        % 'ntrials',N     Number of path trials (default 50)
        % 'noprogress'    Don't show the progress bar
        % 'samples'       Show progress in a plot of the workspace
        %                 - '.' for each random point x_rand
        %                 - 'o' for the nearest point which is added to the tree
        %                 - red line for the best path
        %
        % Notes::
        % - At each iteration we need to find a vehicle path/control that moves it
        %   from a random point towards a point on the graph.  We sample ntrials of
        %   random steer angles and velocities and choose the one that gets us
        %   closest (computationally slow, since each path has to be integrated
        %   over time).

            opt.progress = true;
            opt.samples = false;
            opt.goal = [];
            opt.ntrials = 50;

            opt = tb_optparse(opt, varargin);

            if ~isempty(opt.goal)
                rrt.goal = opt.goal;
            end

            % build a graph over the free space
            rrt.message('create the graph');
            rrt.graph.clear();

            if rrt.verbose
                clf
                %idisp(1-rrt.occgrid, 'ynormal', 'nogui');
                hold on
            end

            % check root node sanity
            if isempty(rrt.root)
                error('no root node specified');
            end
            if ~isvec(rrt.root, 3)
                error('root must be 3-vector');
            end
            assert( ~rrt.isoccupied(rrt.root(1:2)), 'root node cell is occupied')

            % add the goal point as the first node
            vroot = rrt.graph.add_node(rrt.root);
            data.vel = 0;
            data.path = [];
            rrt.graph.setvdata(vroot, data);

            % graphics setup
            if opt.progress
                h = Navigation.progress_init('RRT planning...');
            end
            if opt.samples
                clf
                hold on
                xlabel('x'); ylabel('y');
            end

            npoints = 0;
            while npoints < rrt.npoints       % build the tree

                % Step 3
                % find random state x,y

                % pick a point not in obstacle
                while true
                    xy = rrt.randxy();  % get random coordinate (x,y)

                    if isempty(rrt.occgrid)
                        break
                    else
                        % we have an occgrid
                        xy = round( xy );  % round it to a grid cell coordinate

                        % test if lies in the obstacle map
                        try
                            if ~rrt.isoccupied(xy)
                                break;
                            end
                        catch
                            % index error, point must be off the map
                            continue;
                        end
                    end
                end
                theta = rrt.rand*2*pi;
                xrand = [xy, theta]';
                if opt.samples
                    plot(xy(1), xy(2), '.')
                end

                % Step 4
                % find the existing node closest in state space

                vnear = rrt.graph.closest(xrand);   % nearest vertex
                xnear = rrt.graph.coord(vnear);     % coord of nearest vertex
%                 if rrt.graph.distance_metric(xnear, xrand) < 0.25
%                     continue;
%                 end

                rrt.message('xrand (%g, %g) node %d', xy, vnear);

                % Step 5
                % figure how to drive the robot from xnear to xrand

                best = rrt.bestpath(xnear, xrand, opt.ntrials);

                xnew = best.path(:,best.k);
                if opt.samples
                    plot(xnew(1), xnew(2), 'o');
                    plot2(best.path', 'r');
                    drawnow
                end

%                 % ensure that the path is collision free
%                 if ~rrt.clearpath(y(:,1:2))
%                     disp('path collision');
%                     continue;
%                 end

                % Step 7,8
                % add xnew to the graph, with an edge from xnear
                vnew = rrt.graph.add_node(xnew);

                if rrt.graph.vdata(vnear).vel * best.vel < 0
                    % we changed direction, penalise that
                    cost = rrt.revcost;
                else
                    cost = 1;
                end
                rrt.graph.add_edge(vnear, vnew, cost);

                rrt.graph.setvdata(vnew, best);

                npoints = npoints + 1;
                if opt.progress
                    Navigation.progress(h, npoints / rrt.npoints);
                end

            end

            if opt.progress
                Navigation.progress_delete(h)
            end
            rrt.message('graph create done');
        end

        function p_ = query(rrt, xstart, xgoal)
        %RRT.query Find a path between two points
        %
        % X = R.path(START, GOAL) finds a path (Nx3) from pose START (1x3)
        % to pose GOAL (1x3).  The pose is expressed as [X,Y,THETA].
        %
        % R.path(START, GOAL) as above but plots the path in 3D, where the vertical
        % axis is vehicle heading angle.  The nodes are shown as circles and the
        % line segments are blue for forward motion and red for backward motion.
        %
        % Notes::
        % - The path starts at the vertex closest to the START state, and ends
        %   at the vertex closest to the GOAL state.  If the tree is sparse this
        %   might be a poor approximation to the desired start and end.
        %
        % See also RRT.plot.

            assert(rrt.graph.n > 0, 'RTB:RRT: there is no plan');
            rrt.checkquery(xstart, xgoal);

            g = rrt.graph;
            vstart = g.closest(xstart);
            vgoal = g.closest(xgoal);

            % find path through the graph using A* search
            [path,cost] = g.Astar(vstart, vgoal);

            fprintf('A* path cost %g\n', cost);

            % concatenate the vehicle motion segments
            cpath = [];
            for i = 1:length(path)
                p = path(i);
                data = g.vdata(p);
                if ~isempty(data)
                    if i >= length(path) || g.edgedir(p, path(i+1)) > 0
                        cpath = [cpath data.path];
                    else
                        cpath = [cpath data.path(:,end:-1:1)];
                    end
                end
            end

            if nargout == 0
                % plot the path
                clf; hold on

                plot2(g.coord(path)', 'o');     % plot the node coordinates

                for i = 1:length(path)
                    p = path(i);
                    b = g.vdata(p);            % get path data for segment

                    % draw segment with direction dependent color
                    if ~isempty(b)
                        % if the vertex has a path leading to it

                        if i >= length(path) || g.edgedir(p, path(i+1)) > 0
                            % positive edge
                            %  draw from prev vertex to end of path
                            seg = [g.coord(path(i-1)) b.path]';
                        else
                            % negative edge
                            %  draw reverse path to next next vertex
                            seg = [  b.path(:,end:-1:1)  g.coord(path(i+1))]';
                        end

                        if b.vel > 0
                            plot2(seg, 'b');
                        else
                            plot2(seg, 'r');
                        end
                    end
                end

                xlabel('x'); ylabel('y'); zlabel('\theta');
                grid
            else
                p_ = cpath';
            end
        end

        function plot(rrt, varargin)
        %RRT.plot Visualize navigation environment
        %
        % R.plot() displays the navigation tree in 3D, where the vertical axis is
        % vehicle heading angle.  If an occupancy grid was provided this is also
        % displayed.

            % display the occgrid background
            rrt.plot_bg(varargin{:});

            % display the graph
            %rrt.graph.plot('noedges', 'NodeSize', 3, 'NodeFaceColor', 'm', 'NodeEdgeColor', 'm', 'edges');

            rrt.graph.plot('noedges', 'nocomponentcolor', 'NodeSize', 3, 'NodeFaceColor', 'b', 'NodeEdgeColor', 'b', 'edges');
hold on

            % display the occgrid background
            rrt.plot_fg(varargin{:});
            axis([rrt.xrange rrt.yrange])
            xlabel('x'); ylabel('y'); zlabel('\theta');
            grid on; hold off
            view(0,90);
            axis equal
            rotate3d
        end

        % required by abstract superclass
        function next(rrt)
        end

        function s = char(rrt)
        %RRT.char  Convert to string
        %
        % R.char() is a string representing the state of the RRT
        % object in human-readable form.
        %

            % invoke the superclass char() method
            s = char@Navigation(rrt);

            % add RRT specific stuff information
            s = char(s, sprintf('  region: X %f : %f; Y %f : %f', rrt.xrange, rrt.yrange));
            s = char(s, sprintf('  sim time: %f', rrt.simtime));
            s = char(s, sprintf('  speed: %f', rrt.speed));
            s = char(s, sprintf(' Graph:'));
            s = char(s, char(rrt.graph) );
            if ~isempty(rrt.vehicle)
                s = char(s, char(rrt.vehicle) );
            end
        end

    end % methods

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%    P R I V A T E    M E T H O D S
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

    methods (Access='protected')

        function best = bestpath(rrt, x0, xg, N)

            % initial and final state as column vectors
            x0 = x0(:); xg = xg(:);

            best.d = Inf;
            for i=1:N   % for multiple trials

                %choose random direction of motion and random steer angle
                if rand > 0.5
                    vel = rrt.speed;
                else
                    vel = -rrt.speed;
                end
                steer = (2*rrt.rand - 1) * rrt.vehicle.steermax;    % uniformly distributed

                % simulate motion of vehicle for this speed and steer angle which
                % results in a path
                x = rrt.vehicle.run2(rrt.simtime, x0, vel, steer)';

                %% find point on the path closest to xg
                % distance of all path points from goal
                d = colnorm( [bsxfun(@minus, x(1:2,:), xg(1:2)); angdiff(x(3,:), xg(3))] );
                % the closest one
                [dmin,k] = min(d);

                % is it the best so far?
                if dmin < best.d
                    % yes it is!  save it and the inputs that led to it
                    best.d = dmin;
                    best.path = x;
                    best.steer = steer;
                    best.vel = vel;
                    best.k = k;
                end
            end
        end

        % generate a random coordinate within the working region
        function xy = randxy(rrt)
            xy = rrt.rand(1,2) .* [rrt.xrange(2)-rrt.xrange(1) rrt.yrange(2)-rrt.yrange(1)] + ...
                [rrt.xrange(1) rrt.yrange(1)];
        end

        % test if a path is obstacle free
        function c = clearpath(rrt, xy)
            if isempty(rrt.occgrid)
                c = true;
                return;
            end

            xy = round(xy);
            try
                % test that all points along the path do not lie within an obstacle
                for pp=xy'
                    if rrt.isoccupied(pp) > 0
                        c = false;
                        return;
                    end
                end
                c = true;
            catch
                % come here if we index out of bounds
                c = false;
                return;
            end
        end

    end % private methods
end % class


function [H,Rest,activeUEs] = functionExampleSetup_Quadriga(L,Kdrop,B,noiseVariancedBm,Kmax,f,M,polarizations)
%This function generates the channel realizations between UEs at random
%locations and the BSs in the running example, defined in Section 4.1.3.
%BSs with cylindrical arrays and channel are generated using QuaDRiGa from
%the Fraunhofer Heinrich Hertz Institute. The Urban Microcell NLOS scenario
%is used for channel modeling. QuaDRiGa needs to be installed separately
%(http://www.quadriga-channel-model.de) and is delivered with a separate
%license. This function has been tested using QuaDRiGa version 1.4.8-571.
%
%INPUT:
%L                = Number of BSs and cells
%Kdrop            = Number of UEs to be dropped in the square around a BS
%B                = Bandwidth in Hz
%noiseVariancedBm = Noise variance in dBm
%Kmax             = Maximum number of UEs served by a BS
%f                = Pilot reuse factor, giving pilot length Kmax*f
%M                = Number of BS antennas
%polarizations    = Select number of antenna polarizations (1 or 2)
%
%OUTPUT:
%H         = M x 400 x K x L x L matrix with the channel realizations over
%            400 subcarriers at one time instance
%Rest      = M x M x K x L x L matrix with estimates of the spatial
%            correlation matrices for all UEs in the network.
%            Rest(:,:,k,j,l) is the correlation matrix for the channel
%            between UE k in cell j and the BS in cell l.
%activeUEs = Kmax x L with zeros and ones. activeUEs(k,l)==1 means that
%            pilot k is used by a UE in cell l
%
%
%This Matlab function was developed to generate simulation results to:
%
%Emil Bjornson, Jakob Hoydis and Luca Sanguinetti (2017),
%"Massive MIMO Networks: Spectral, Energy, and Hardware Efficiency",
%Foundations and Trends in Signal Processing: Vol. 11, No. 3-4,
%pp. 154-655. DOI: 10.1561/2000000093.
%
%For further information, visit: https://www.massivemimobook.com
%
%This is version 1.0 (Last edited: 2017-11-04)
%
%License: This code is licensed under the GPLv2 license. If you in any way
%use this code for research that results in publications, please cite our
%monograph as described above.

%% Create new Quadriga layout

%Set irrelevant parameters
s = simulation_parameters;
s.sample_density = 1;
s.use_absolute_delays = 1;

%Set center frequency
center_frequency = 2e9;
s.center_frequency = center_frequency;

%Number of subcarriers
nbrOfSubcarriers = 400;

%Create new layout from general parameters
lay = layout(s);

%Generate BSs
lay.no_tx = L;

%Set BS heights
lay.tx_position(3,:) = 25;

%Set the length in meters of the total square area
squareLength = 1000;

%Number of BSs per dimension
nbrBSsPerDim = sqrt(L);

%% Deploy BSs

%Minimum distance between BSs and UEs
minDistance = 35;

%Distance between BSs in vertical/horizontal direction
interSiteDistance = squareLength/nbrBSsPerDim;

%Deploy BSs on the grid
locationsGridHorizontal = repmat(interSiteDistance/2:interSiteDistance:squareLength-interSiteDistance/2,[nbrBSsPerDim 1]);
locationsGridVertical = locationsGridHorizontal';
BSpositions = locationsGridHorizontal(:) + 1i*locationsGridVertical(:);

for j = 1:length(BSpositions)

    lay.tx_position(1:2,j) = [real(BSpositions(j)); imag(BSpositions(j))];

end

%% Create a circular antenna array for each BS
M_V = 5; %Number of vertical antennas
M_H = M/M_V; %Number of antennas on each horizontal circle

%Define the antenna spacing (in number of wavelengths)
antennaSpacing = 1/2; %Half wavelength distance

if polarizations == 1
    PolarizationIndicator = 1; %Single polarization antennas switching between vertical and horizontal
elseif polarizations == 2
    PolarizationIndicator = 3; %Dual +/-45deg polarized antennas
end

%Compute height of array
arrayHeight = (M_V-1)*antennaSpacing*3e8/center_frequency;

if (PolarizationIndicator==1) %Single polarized elements

    circumference = M_H*antennaSpacing*3e8/center_frequency;
    radius = circumference/(2*pi);
    delta_angle = 2*pi/M_H;

    %Go through all BSs
    for b = 1:L

        %Create rectangular array of size M_V x M_H
        lay.tx_array(b).generate('3gpp-3d', 1, M_V, M_H, center_frequency, PolarizationIndicator, 0, antennaSpacing);

        %Place antennas on a circle and rotate radiation patters
        for i = 1:M_V
            for j = 1:M_H
                indices = (i-1)*M_H + j;
                angle = (j-1)*delta_angle;
                lay.tx_array(b).element_position(1, indices) = radius*cos(angle);
                lay.tx_array(b).element_position(2, indices) = radius*sin(angle);
                lay.tx_array(b).element_position(3, indices) = (i-1)*antennaSpacing*3e8/center_frequency - arrayHeight/2;
                lay.tx_array(b).rotate_pattern(rad2deg(angle), 'z', indices, 0);

                if mod(indices,2) == 0 %Switch between vertical and horizontal polarization
                    lay.tx_array(b).rotate_pattern(90, 'y', indices, 2);
                end
            end
        end
    end

elseif (PolarizationIndicator==3) %Dual polarized elements

    circumference = M_H/2*antennaSpacing*3e8/center_frequency;
    radius = circumference/(2*pi);
    delta_angle = 2*pi/(M_H/2);

    %Go through all BSs
    for b = 1:L

        %Create rectangular array of size M_V x M_H/2
        lay.tx_array(b).generate('3gpp-3d', 1, M_V, M_H/2, center_frequency, PolarizationIndicator, 0, antennaSpacing);

        %Place antennas on a circle and rotate radiation patters (while keeping co-located antennas together)
        for i = 1:M_V
            for j = 1:M_H/2
                indices = (i-1)*M_H + 2*j-1 : (i-1)*M_H + 2*j;
                angle = (j-1)*delta_angle;
                lay.tx_array(b).element_position(1, indices) = radius*cos(angle);
                lay.tx_array(b).element_position(2, indices) = radius*sin(angle);
                lay.tx_array(b).element_position(3, indices) = (i-1)*antennaSpacing*3e8/center_frequency - arrayHeight/2;
                lay.tx_array(b).rotate_pattern(rad2deg(angle), 'z', indices, 0);
            end
        end
    end
end

%Compute all nine alternatives of the BS locations when using wrap around
wrapHorizontal = repmat([-squareLength 0 squareLength],[3 1]);
wrapVertical = wrapHorizontal';
wrapLocations = wrapHorizontal(:)' + 1i*wrapVertical(:)';

%Compute the exact dimension of the square where the users are located
maxDistance = interSiteDistance;

%Prepare to put out UEs in the cells
UEpositions = zeros(Kdrop,L);
UEpositionsWrapped = zeros(Kdrop,L,length(wrapLocations));
perBS = zeros(L,1);

%Go through all the cells
for l = 1:L

    %Put out K UEs in the cell, uniformly at random. The procedure is
    %iterative since UEs that do not satisfy the minimum distance are
    %replaced with new UEs
    while perBS(l)<Kdrop

        %Put out users
        UEremaining = Kdrop-perBS(l);
        posX = rand(UEremaining,1)*maxDistance - maxDistance/2;
        posY = rand(UEremaining,1)*maxDistance - maxDistance/2;
        posXY = posX + 1i*posY;

        %Keep those that satisfy the minimum distance
        posXY = posXY(abs(posXY)>=minDistance);

        %Store new UEs
        UEpositions(perBS(l)+1:perBS(l)+length(posXY),l) = posXY + BSpositions(l);
        perBS(l) = perBS(l)+length(posXY);

    end

    %Create alternative UE positions using wrap around
    for k = 1:Kdrop

        UEpositionsWrapped(k,l,:) = UEpositions(k,l) + wrapLocations;

    end

end

%% Configure UEs

Ktotal = Kdrop*L*length(wrapLocations); %Total number of UEs

%Define UE heights
UE_heights = 1.5*ones(Kdrop,L);
UE_heightsWrapped = repmat(UE_heights,[1 1 length(wrapLocations)]);

%Generate UEs
lay.no_rx = Ktotal;

%Define UE antennas
lay.rx_array.generate('omni');

%% Simulate channels

% Randomly distribute UEs
lay.rx_position =  [real(UEpositionsWrapped(:))'; imag(UEpositionsWrapped(:))'; UE_heightsWrapped(:)'];

%Define tracks for each UE, assuming a fixed UE location
for k=1:Ktotal
    lay.track(k).generate('linear',0,0) %Define a linear track consisting of only one position
    lay.track(k).scenario = '3GPP_3D_UMi_NLOS'; %Select the Urban Microcell NLOS scenario
end

%Generate pilot patterns
if f == 1

    pilotPattern = ones(L,1);

elseif f == 2 %Only works for 16 BSs

    pilotPattern = kron(ones(2,1),[1; 2; 1; 2; 2; 1; 2; 1]);

end

%Randomize pilot allocation in each cell
randOrder = zeros(Kmax*f,L);

for j = 1:L

    randOrder(1+(pilotPattern(j)-1)*Kmax:pilotPattern(j)*Kmax,j) = randperm(Kmax)+(pilotPattern(j)-1)*Kmax;

end

%Compute variance and standard deviation of the noise
noiseVar = 10^(noiseVariancedBm/10);
noiseStd = sqrt(noiseVar);

%Prepare to store channel realizations
H = zeros(M,nbrOfSubcarriers,Kmax,L,L);
Rest = zeros(M,M,Kmax,L,L);
perBS = zeros(L,1);
activeUEs = zeros(Kmax,L);

%% Go through all cells
for j = 1:L

    %Output simulation progress
    disp([num2str(j) ' cells generated out of ' num2str(L)]);

    %Go through all UEs
    for k = 1:Kdrop

        Huser = zeros(M,nbrOfSubcarriers,1,1,L);
        Ruser = zeros(M,M,1,1,L);

        %Extract the channels to all BSs
        for l = 1:L

            [~,minr] = min(abs(UEpositionsWrapped(k,j,:)-BSpositions(l)));

            userind = k+(j-1)*Kdrop+(minr-1)*Kdrop*L;

            [ h_channel, ~ ] = lay.get_channels_seg(l, userind);
            Hextract = h_channel.fr(B, nbrOfSubcarriers);

            Huser(:,:,1,1,l) = reshape(Hextract,[M nbrOfSubcarriers])/noiseStd;
            Ruser(:,:,1,1,l) = diag(mean(abs(Huser(:,:,1,1,l)).^2,2)/noiseVar);

        end

        %Determine which BS should serve the UE
        [~,bestBS] = max(mean(sum(abs(Huser(:,:,1,1,:)).^2,1),2));

        %Check if the selected BS has pilots available
        if perBS(bestBS)<Kmax

            %Add the UE to the cell of the selected BS
            perBS(bestBS) = perBS(bestBS) + 1;
            H(:,:,randOrder(perBS(bestBS)+(pilotPattern(bestBS)-1)*Kmax,bestBS),bestBS,:) = Huser;
            Rest(:,:,randOrder(perBS(bestBS)+(pilotPattern(bestBS)-1)*Kmax,bestBS),bestBS,:) = Ruser;
            activeUEs(randOrder(perBS(bestBS)+(pilotPattern(bestBS)-1)*Kmax,bestBS),bestBS) = 1;

        end

    end

end


function [gp, indA] = passgp(gp, x, y, varargin)
%PASSGP   Optimize active set and hyperparameters of PASS-GP
%
%  Description
%    [GP, INDA] = PASSGP(GP, X, Y, OPTIONS)
%    Returns GP structure with hyperparameters optimized according to
%    PASS-GP routine (Henao & Winther, 2012) and active set indices
%    INDA for X and Y
%
%   OPTIONS is optional parameter-value pair
%      npass - Number of passes the algorithm takes over the whole training
%              data set
%      ninit - Initial active set size
%      nsub  - Number of subsets we process at each pass (in how many
%              parts we divide the whole data)
%      pinc  - Predictive density threshold for inclusion in active set.
%      pdel  - LOO-predictive density threshold for deletion from active
%              set
%      pexc  - Exchange proportion for fixed PASS-GP
%      opt   - Options structure for optimizer
%      fixed - Whether we use fixed size of active set or not. Default
%               'off'
%      display - Whether to display additional info or not. Default 'off'
%      optimn - Whether to optimize always or only after every nth
%               deletion/addition to active set. Default 1 (every time). If
%               given e.g. value 3, optimizes after every 3rd
%               addition/deletion.
%
%  See also
%    GP_SET, LIK_*
%
%  Reference:
%    Ricardo Henao & Ole Winther (2012). Preditive active set selection
%    methods for Gaussian processes. Neurocomputing 80 (2012), 10-18.

% Copyright (c) 2013 Ville Tolvanen

% This software is distributed under the GNU General Public
% License (version 3 or later); please refer to the file
% License.txt, included with the software, for details.

  ip=inputParser;
  ip.FunctionName = 'PASSGP';
  ip.addRequired('gp',@(x) isstruct(x) || iscell(x));
  ip.addRequired('x', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
  ip.addRequired('y', @(x) ~isempty(x) && isreal(x) && all(isfinite(x(:))))
  ip.addParamValue('npass', 3, @(x) isscalar(x) && x > 0)
  ip.addParamValue('ninit', 100, @(x) isscalar(x) && x > 0)
  ip.addParamValue('nsub', 10, @(x) isscalar(x) && x > 0)
  ip.addParamValue('pinc', 0.5, @(x) isscalar(x) && x > 0)
  ip.addParamValue('pdel', 0.99, @(x) isscalar(x) && x > 0)
  ip.addParamValue('pexc', 0.1, @(x) isscalar(x) && x > 0)
  ip.addParamValue('opt', [], @(x) isstruct(x))
  ip.addParamValue('fixed', 'off', @(x) ismember(x,{'on','off'}))
  ip.addParamValue('display', 'off', @(x) ismember(x,{'on','off'}))
  ip.addParamValue('optimn', 1', @(x) isscalar(x) && x > 0 && rem(10*x,2)==0)
  ip.addParamValue('z', [], @(x) isreal(x) && all(isfinite(x(:))))
  ip.parse(gp, x, y, varargin{:});
  opt=ip.Results.opt;
  fixed=ip.Results.fixed;
  options.z = ip.Results.z;
  npass=ip.Results.npass;
  ninit=ip.Results.ninit;
  nsub=ip.Results.nsub;
  pinc=ip.Results.pinc;
  pdel=ip.Results.pdel;
  pexc=ip.Results.pexc;
  display=ip.Results.display;
  optimn=ip.Results.optimn;

  if isequal(display,'on')
    display=1;
  else
    display=0;
  end
  if isequal(fixed, 'on')
    fixed=1;
  else
    fixed=0;
  end

  if ninit > size(x,1)
    error('Initial active set must be subset of original data');
  end
  if nsub > (size(x,1) - ninit)
    error('nsub must be lower than size(x,1) - ninit');
  end

  [n,nin]=size(x);

  % Initial active set

  indA=sort(randperm(n, ninit),'ascend');
  % Inclusions/deletions per iteration for fixed pass-gp
  nexc=floor(ninit*pexc);
  iter=optimn-1;
  for i=1:npass
    if display
      fprintf('Pass %d / %d.\n', i, npass)
    end
    [tmp,indSub]=cvit(n, nsub, floor(10*rand(1)));
    for j=1:nsub
      iter=iter+1;
      inds=indSub{j};
      % Remove indices that are already in active set
      inds(ismember(inds,indA))=[];

      if iter==optimn
        % Optimize hyperparameters
        gp = gp_optim(gp, x(indA,:), y(indA), 'opt', opt, options);
        iter=0;
      end

      % Calculate weights for active set inputs (loo predictive densities)
      [tmp,tmp,lpyt]=gp_loopred(gp,x(indA,:),y(indA), options);

      % Remove active set indices according to removal rule
      if ~fixed
        indA(find(exp(lpyt)>pdel))=[];
      else
        [tmp,ii]=sort(lpyt, 'descend');
        indA(ii(1:nexc))=[];
      end
      % Calculate weights for inputs not in active set (predictive density)
      [tmp,tmp,lpyt]=gp_pred(gp, x(indA,:), y(indA), x(inds,:), 'yt', y(inds), options);

      % Add indices to active set according to addition rule
      if ~fixed
        ind=find(exp(lpyt)<pinc)';
      else
        [tmp,ii]=sort(lpyt, 'ascend');
        ind=ii(1:nexc);
      end
      indA=[indA inds(ind)];

    end

  end

end


function [ResultsAllCellLines,OverViewResults] = setQuantConstraints(model, samples, tol, minGrowth, obj, no_secretion, no_uptake, medium, addExtraExch, addExtraExch_value, path)
% This function takes a model and quantitative extracellular metabolomic data and returns a model in which the data is integrated as constraints described
% in "MetaboTools: A Comprehensive Toolbox for Analysis of Genome-Scale Metabolic Models", 2016, Front. Physiol., and the supplemental data "Tutorial II: Workflow for the integration
% of quantitative extracellular metabolomic data into the network context." (Data Sheet 3.pdf)
%
% USAGE:
%
%    [ResultsAllCellLines, OverViewResults] = setQuantConstraints(model, samples, tol, minGrowth, obj, no_secretion, no_uptake, medium, addExtraExch, addExtraExch_value, path, epsilon)
%
% INPUTS:
%       model:                Global metabolic model (Recon)
%       samples:              Vector specifying the samples used (there must be an output file of function .... for each sample)
%       tol:                  Cutoff value for small numbers (e.g., -1e-8). All number smaller than tol will be treated as zero
%       minGrowth:            Will be the lower bound of the objective function (e.g., 0.008). Forces the output model(s) to be able to produce a minimal objective value
%       obj:                  Objective function, e.g. `biomass_reaction2`
%       no_secretion:         Define metabolites that should not be secreted (e.g., {`EX_o2(e)`})
%       no_uptake:            Define metabolites that should not be consumed (e.g., {`EX_o2s(e)`, `EX_h2o2(e)`})
%       medium:               Define if certain exchanges should be excluded from minimization of exchanges (e.g., {}, if no medium except the exometabolomic data has been defined)
%       addExtraExch:         After adding secretions, models are still not growing, this variable allows one to recover exchanges with a defined small value
%       addExtraExch_value:   e.g. 1 as arbitrary small flux value / the resulting ub = 1, lb = -1.
%       path:                 Location of the .mat files for samples.
%
%
% OUTPUTS:
%       ResultsAllCellLines:  Structure that contains pruned and unpruned model, Vector of the Exchange_reactions, the exchange reactions added by `minExCard`, `minFLux` and `maxFlux` of the added reactions, maximal objective value, and the results of the gene deletion
%       OverViewResults:      Overview of model statistics, e.g., number of reactions, metabolites, genes, number of essential genes, min and max objective values for easy comparison between sets of models
%
%
% Depends on `changeRxnBounds`, `fluxVariability`, `optimizeCbModel`, `generateCompactExchModel` as well its dependent functions `pruneModel`, `findMinCardModel`, `findOptExchRxns`
%
% .. Author: - Maike K. Aurich 18/02/15

cntO=1;
% Set overview variable
OverViewResults{1,cntO} ='cell line';cntO = cntO+1;
OverViewResults{1,cntO} = 'num Added Rxns';cntO = cntO+1;
OverViewResults{1,cntO} = 'num rxns pruned model';cntO = cntO+1;
OverViewResults{1,cntO} = 'num mets pruned model';cntO = cntO+1;
OverViewResults{1,cntO} = 'num genes pruned model';cntO = cntO+1;
OverViewResults{1,cntO} = 'max growth rate';cntO = cntO+1;
OverViewResults{1,cntO} = 'num exch rxns';cntO = cntO+1;
OverViewResults{1,cntO} = 'O2 requirement';cntO = cntO+1;
OverViewResults{1,cntO} = 'extra Exchanges Added';cntO = cntO+1;
OverViewResults{1,cntO} = 'num recovered exchanges';cntO = cntO+1;

for j = 1:length(samples)

    j
    %   load([path filesep 'model.mat']); %% uncomment for Recon1
    ExtraExchAdded = 0;

    FILENAME = char(samples(j,1));
    load([path filesep FILENAME '.mat'])%, 'uptake_value', 'secr_value' , 'uptake' ,'secretion', 'cell_line' );

    % Map exo-metabolomic data
    model2=model;
    model2.lb(find(ismember(model2.rxns,obj)))=minGrowth;% based on slowlest cell line in data

    % Map uptake
    for k=1:length(uptake)
        Uptake_rxns=uptake(k);
        lb = uptake_value(k,2);
        ub = uptake_value(k,3);
        model2 = changeRxnBounds(model2,Uptake_rxns,ub,'u'); %enforce uptake of metabolites taken up by cells in the experiment
        model2 = changeRxnBounds(model2,Uptake_rxns,lb,'l'); %enforce uptake of metabolites taken up by cells in the experiment

    end
    clear A sol
    model2ori=model2;
    SecretionRxnsRecovered =[];
    cnt2=1;

    % Map secretion
    for k=1:length(secretion)
        secretion_rxns= secretion(k);
        lb = secr_value(k,3);
        ub = secr_value(k,2);
        model2 = changeRxnBounds(model2, secretion_rxns,lb,'l');% enforce secretion of metabolites that are secreted by the cells in the experiment
        model2 = changeRxnBounds(model2, secretion_rxns,ub,'u');% enforce secretion of metabolites that are secreted by the cells in the experiment

        % check if model works after adding constraints
        SOL = optimizeCbModel(model2);
        if SOL.f<=abs(tol)
            sol(k,1)=SOL.f;
            secretion_rxns
            model2=model2ori; % model2ori is overwritten after each successful iteration so its not the original model (without any secretions)
            model2 = changeRxnBounds(model2, secretion_rxns,ub,'u');% set secretion of metabolite to measured upper bound nevertheless
            SecretionRxnsRecovered{cnt2,1}=secretion_rxns{1,:}; cnt2 = cnt2 +1;
        else
            sol(k,1)=SOL.f;
            model2ori=model2;
        end
    end

    % sets bounds to the lowest measured uptake/secretion rate
    for k=1:length(No_upt_secr)
        No_upt_secr_rxns= No_upt_secr(k);
        bound = min(abs([max(uptake_value(:,3));min(secr_value(:,3))]));
        model2 = changeRxnBounds(model2, No_upt_secr_rxns,-bound,'l');
        model2 = changeRxnBounds(model2, No_upt_secr_rxns,bound,'u');% sets bounds to the lowest measured uptake/secretion rate
    end

    % do not allow secretion or uptake of certain metabolites
    model2 = changeRxnBounds(model2, no_secretion,0,'u');% enforce secretion of metabolites that are secreted by the cells in the experiment
    model2 = changeRxnBounds(model2, no_uptake,0,'l');% enforce secretion of metabolites that are secreted by the cells in the experiment

    %% test if model can grow AT ALL
    SOL = optimizeCbModel(model2);
    if abs(SOL.f) < abs(tol)

        model2 = changeRxnBounds(model2, addExtraExch,-addExtraExch_value,'l');% sets bounds to the lowest measured uptake/secretion rate
        model2 = changeRxnBounds(model2, addExtraExch,addExtraExch_value,'u');% sets bounds to the lowest measured uptake/secretion rate
        ExtraExchAdded = 1;
    end

    SOL = optimizeCbModel(model2);
    if abs(SOL.f) > abs(tol)
        %Generate submodel
        [modelMin, modelPruned, Ex_Rxns] = generateCompactExchModel(model2,minGrowth);

        %added 23/07/2015
        modelPruned.c = zeros(length(modelPruned.rxns),1);
        modelPruned.c(find(ismember(modelPruned.rxns,obj)),1)=1;
        %added 23/07/2015

        sol= optimizeCbModel(modelPruned);
        Ex_RxnsAdded = Ex_Rxns;

        % remove reactions that are in uptake and secretion
        US = unique([secretion;uptake]);
        Ex_RxnsAdded(ismember(Ex_RxnsAdded,US))=[];
        [a(:,1),a(:,2)]=fluxVariability(modelPruned,1,[],Ex_RxnsAdded);

        % print Results

        ResultsAllCellLines.(samples{j}).modelPruned = modelPruned;
        ResultsAllCellLines.(samples{j}).modelMin = modelMin;
        ResultsAllCellLines.(samples{j}).Ex_Rxns = Ex_Rxns;
        ResultsAllCellLines.(samples{j}).Ex_RxnsAdded = Ex_RxnsAdded;
        ResultsAllCellLines.(samples{j}).MinMaxAddedRxns = a;
        ResultsAllCellLines.(samples{j}).maxBiomass = sol;

        ResultsAllCellLines.(samples{j}).SecretionRxnsRecovered = SecretionRxnsRecovered;

        cntO=1;
        OverViewResults{j+1,cntO} = samples{j};cntO = cntO+1;% cell line
        OverViewResults{j+1,cntO} = num2str(length(ResultsAllCellLines.(samples{j}).Ex_RxnsAdded));cntO = cntO+1;% num Added Rxns
        OverViewResults{j+1,cntO} = num2str(length(ResultsAllCellLines.(samples{j}).modelPruned.rxns));cntO = cntO+1;% num rxns
        OverViewResults{j+1,cntO} = num2str(length(ResultsAllCellLines.(samples{j}).modelPruned.mets));cntO = cntO+1;% num mets
        OverViewResults{j+1,cntO} = num2str(length(ResultsAllCellLines.(samples{j}).modelPruned.genes));cntO = cntO+1;% num genes
        OverViewResults{j+1,cntO} = num2str(ResultsAllCellLines.(samples{j}).maxBiomass.f);cntO = cntO+1;% max growth rate
        OverViewResults{j+1,cntO} = num2str(length(ResultsAllCellLines.(samples{j}).Ex_Rxns));cntO = cntO+1;% num Exchange rxns
        if ~isempty(strmatch('EX_o2(e)',ResultsAllCellLines.(samples{j}).Ex_RxnsAdded))
            OverViewResults{j+1,cntO} = num2str(1);cntO = cntO+1;% O2 requirement
        else
            OverViewResults{j+1,cntO} = num2str(0);cntO = cntO+1;% O2 requirement
        end

        OverViewResults{j+1,cntO} = num2str(ExtraExchAdded);cntO = cntO+1;% num Exchange rxns
        OverViewResults{j+1,cntO} = num2str(length(SecretionRxnsRecovered));cntO = cntO+1;% num Exchange rxns

    else
        ResultsAllCellLines.(samples{j}).model = [];
        ResultsAllCellLines.(samples{j}).AddedExchange = [];
        ResultsAllCellLines.(samples{j}).maxBiomass = [];
        cntO=1;
        OverViewResults{j+1,cntO} = samples{j};cntO = cntO+1;% cell line
    end
    clear sol Extra* addExtraExch* model2 modelMin* modelU* modelP* model2ori No* upt* secr* gr* Rxn* cnt* Blocked* h* FBA* j k m bound ans lb Close* Ex_Rxns1* Ex_Rxns2*  Sol U* Ori* i* ma* rev* t un* us* w a SOL a1 ub ToDe* ReP* Ex_* Secr* AddedExchang*
    save([path filesep 'setQuantConstraints.mat'], '-v7.3');
end

end


% run_simMegaExTEShaped.m
% Jamie Near, McGill University 2014.
%
% USAGE:
% This script is run simply by editing the input parameters and then
% clicking "Run".
%
% DESCRIPTION:
% This script simulates an ExTE-MEGA-PRESS experiment with fully shaped editing
% and refocusing pulses.  Phase cycling of both the editing and refocusing
% pulses is performed.  Simulations are run at various
% locations in space to account for the within-voxel spatial variation of
% the GABA signal.  Summation across phase cycles and spatial positions is
% performed.  As a result of the phase cycling and spatially resolved simulations,
% this code takes a long time to run.  Therefore, the MATLAB parallel computing
% toolbox (parfor loop) was used to accelerate the siumulations.  Accelration
% is currently performed in the direction of the slice selective pulse along
% the x-direction, but this can be changed.  Up to a factor of 12 acceleration
% can be achieved using this approach.  To enable the use of the MATLAB
% parallel computing toolbox, initialize the multiple worked nodes using
% "matlabpool size X" where "X" is the number of available processing
% nodes.  If the parallel processing toolbox is not available, then replace
% the "parfor" loop with a "for" loop.
%
% INPUTS:
% To run this script, edit the parameters below as desired and then click
% "run":
% refocWaveform     = name of refocusing pulse waveform.
% editWaveform      = name of editing pulse waveform.
% editOnFreq        = freqeucny of edit on pulse[ppm]
% editOffFreq       = frequency of edit off pulse[ppm]
% refTp             = duration of refocusing pulses[ms]
% editTp            = duration of editing pulses[ms]
% Bfield            = Magnetic field strength in [T]
% Npts              = number of spectral points
% sw                = spectral width [Hz]
% Bfield            = magnetic field strength [Tesla]
% lw                = linewidth of the output spectrum [Hz]
% thkX              = slice thickness of x refocusing pulse [cm]
% thkY              = slice thickness of y refocusing pulse [cm]
% x                 = vector of X positions to simulate [cm]
% y                 = vector of y positions to simulate [cm]
% taus              = vector of pulse sequence timings  [ms]
% spinSys           = spin system to simulate
% editPhCyc1        = vector of phase cycling steps for 1st editing pulse [degrees]
% editPhCyc2        = vector of phase cycling steps for 2nd editing pulse [degrees]
% refPhCyc1         = vector of phase cycling steps for 1st refocusing pulse [degrees]
% refPhCyc2         = vector of phase cycling steps for 2nd refocusing pulse [degrees]
%
% OUTPUTS:
% outON_posxy       = Simulated ExTE-MEGA-PRESS edit-ON spectrum, spatially resolved.
% outOFF_posxy      = Simulated ExTE-MEGA-PRESS edit-OFF spectrum, spatially resolved.
% outDIFF_posxy     = Simulated ExTE-MEGA-PRESS difference spectrum, spatially resolved.
% outON             = Simulated ExTE-MEGA-PRESS edit-ON spectrum, summed over
%                     all positions.
% outOFF            = Simulated ExTE-MEGA-PRESS edit-OFF spectrum, summed over
%                     all positions.
% outDIFF           = Simulated ExTE-MEGA-PRESS difference spectrum, summed over
%                     all positions.

% ************INPUT PARAMETERS**********************************
refocWaveform='sampleRefocPulse.pta'; %name of refocusing pulse waveform.
editWaveform='sampleEditPulse.pta'; %name of editing pulse waveform.
editOnFreq=1.88; %freqeucny of edit on pulse[ppm]
refTp=5.2; %duration of refocusing pulses[ms]
editTp=14; %duration of editing pulses[ms]
Npts=2048; %number of spectral points
sw=2000; %spectral width [Hz]
lw=2; %linewidth of the output spectrum [Hz]
Bfield=2.89; %Magnetic field strength in [T]
thkX=3.5; %slice thickness of x refocusing pulse [cm]
thkY=3.5; %slice thickness of y refocusing pulse [cm]
x=linspace(-2.0125,2.0125,12); %X positions to simulate [cm]
y=linspace(-2.0125,2.0125,12); %y positions to simulate [cm]
taus_inv=...  %Timing for J-Inverted scan
        [4.9,...    %time from excitation to 1st refoc pulse [ms]
        67.4885,... %time from 1st refoc pulse to 1st editing pulse [ms]
        33.5115,... %time from 1st editing pulse to 2nd refoc pulse [ms]
        33.4885,... %time from 2nd refoc pulse to 2nd editing pulse [ms]
        62.6115];   %time from 2nd editing pulse to ADC onset [ms]
taus_ref=... %Timing for J-refocused scan
        [4.9,...    %time from excitation to 1st refoc pulse [ms]
        50.4885,... %time from 1st refoc pulse to 1st editing pulse [ms]
        50.5115,... %time from 1st editing pulse to 2nd refoc pulse [ms]
        50.4885,... %time from 2nd refoc pulse to 2nd editing pulse [ms]
        45.6115];   %time from 2nd editing pulse to ADC onset [ms]
spinSys='GABA'; %spin system to simulate
centreFreq=3.0; %Centre frequency of MR spectrum [ppm]
editPhCyc1=[0 90]; %phase cycling steps for 1st editing pulse [degrees]
editPhCyc2=[0 90]; %phase cycling steps for 2nd editing pulse [degrees]
refPhCyc1=[0,90]; %phase cycling steps for 1st refocusing pulse [degrees]
refPhCyc2=[0,90]; %phase cycling steps for 2nd refocusing pulse [degrees]
% ************END OF INPUT PARAMETERS**********************************

%Load RF waveforms
refRF=io_loadRFwaveform(refocWaveform,'ref',0);
editRF=io_loadRFwaveform(editWaveform,'inv',0);

gamma=42577000; %gyromagnetic ratio

%Load spin systems
load spinSystems
if strcmp(spinSys,'MM');
    sys=sysGABA;
    sys.shifts(3)=1.7;
    sys.shifts(4)=1.7;
else
    sys=eval(['sys' spinSys]);
end

%Resample refocusing RF pulse from 400 pts to 100 pts to reduce
%computational workload
refRF=rf_resample(refRF,100);

%This is the step where the editing pulse waveform (initially a pulse with
%zero-frequency) is frequency shifted to produce and edit-on and an
%edit-off pulse;
editRFon=rf_freqshift(editRF,editTp,(centreFreq-editOnFreq)*Bfield*gamma/1e6);

Gx=(refRF.tbw/(refTp/1000))/(gamma*thkX/10000); %[G/cm]
Gy=(refRF.tbw/(refTp/1000))/(gamma*thkY/10000); %[G/cm]

[DX,DY]=meshgrid(x,y);

%n=1;
%totalIters=length(x)*length(y)*length(editPhCyc1)*length(editPhCyc2)*length(refPhCyc1)*length(refPhCyc2);

%Initialize structures:
outON_posxy_epc_rpc=cell(length(x),length(y),length(editPhCyc1),length(editPhCyc2),length(refPhCyc1),length(refPhCyc2));
outOFF_posxy_epc_rpc=cell(length(x),length(y),length(editPhCyc1),length(editPhCyc2),length(refPhCyc1),length(refPhCyc2));
outON_posxy_epc=cell(length(x),length(y),length(editPhCyc1),length(editPhCyc2));
outOFF_posxy_epc=cell(length(x),length(y),length(editPhCyc1),length(editPhCyc2));
outON_posxy=cell(length(x),length(y));
outOFF_posxy=cell(length(x),length(y));
outDIFF_posxy=cell(length(x),length(y));
outON=struct([]);
outOFF=struct([]);

%loop through space: Don't forget to initialize the parallel processing
%toolbox workers using 'matlabpool open N' (for N workers, 12 max).

for X=1:length(x);
%parfor X=1:length(x);
    for Y=1:length(y);
        for EP1=1:length(editPhCyc1)
            for EP2=1:length(editPhCyc2)
                for RP1=1:length(refPhCyc1)
                    for RP2=1:length(refPhCyc2)
                        disp(['Executing X-position ' num2str(X) ' of ' num2str(length(x)) ', '...
                            'Y-position ' num2str(Y) ' of ' num2str(length(y)) ', '...
                            'First Edit phase cycle ' num2str(EP1) ' of ' num2str(length(editPhCyc1)) ', '...
                            'Second Edit phase cycle ' num2str(EP2) ' of ' num2str(length(editPhCyc2)) ', '...
                            'First Refoc phase cycle ' num2str(RP1) ' of ' num2str(length(refPhCyc1)) ', '...
                            'Second Refoc phase cycle ' num2str(RP2) ' of ' num2str(length(refPhCyc2)) '!!!']);
                        outON_posxy_epc_rpc{X}{Y}{EP1}{EP2}{RP1}{RP2}=sim_megapress_shaped(Npts,sw,Bfield,lw,taus_ref,sys,...
                            editRFon,editTp,editPhCyc1(EP1),editPhCyc2(EP2),...
                            refRF,refTp,Gx,Gy,x(X),y(Y),refPhCyc1(RP1),refPhCyc2(RP2));
                        outOFF_posxy_epc_rpc{X}{Y}{EP1}{EP2}{RP1}{RP2}=sim_megapress_shaped(Npts,sw,Bfield,lw,taus_inv,sys,...
                            editRFon,editTp,editPhCyc1(EP1),editPhCyc2(EP2),...
                            refRF,refTp,Gx,Gy,x(X),y(Y),refPhCyc1(RP1),refPhCyc2(RP2));

                        if RP1==1 && RP2==1
                            outON_posxy_epc{X}{Y}{EP1}{EP2}=outON_posxy_epc_rpc{X}{Y}{EP1}{EP2}{RP1}{RP2};
                            outOFF_posxy_epc{X}{Y}{EP1}{EP2}=outOFF_posxy_epc_rpc{X}{Y}{EP1}{EP2}{RP1}{RP2};
                        else
                            outON_posxy_epc{X}{Y}{EP1}{EP2}=op_addScans(outON_posxy_epc{X}{Y}{EP1}{EP2},outON_posxy_epc_rpc{X}{Y}{EP1}{EP2}{RP1}{RP2},xor(RP1==length(refPhCyc1),RP2==length(refPhCyc2)));
                            outOFF_posxy_epc{X}{Y}{EP1}{EP2}=op_addScans(outOFF_posxy_epc{X}{Y}{EP1}{EP2},outOFF_posxy_epc_rpc{X}{Y}{EP1}{EP2}{RP1}{RP2},xor(RP1==length(refPhCyc1),RP2==length(refPhCyc2)));
                        end
                    end %end of 1st refocusing phase cycle loop
                end %end of 2nd refocusing phase cycle loop.

                if EP1==1 && EP2==1
                    outON_posxy{X}{Y}=outON_posxy_epc{X}{Y}{EP1}{EP2};
                    outOFF_posxy{X}{Y}=outOFF_posxy_epc{X}{Y}{EP1}{EP2};
                else
                    outON_posxy{X}{Y}=op_addScans(outON_posxy{X}{Y},outON_posxy_epc{X}{Y}{EP1}{EP2});
                    outOFF_posxy{X}{Y}=op_addScans(outOFF_posxy{X}{Y},outOFF_posxy_epc{X}{Y}{EP1}{EP2});
                end
                outDIFF_posxy{X}{Y}=op_subtractScans(outON_posxy{X}{Y},outOFF_posxy{X}{Y});
            end %end of 1st editing phase cycle loop.
        end %end of 2nd editing phase cycle loop.

        outON=op_addScans(outON,outON_posxy{X}{Y});
        outOFF=op_addScans(outOFF,outOFF_posxy{X}{Y});

    end %end of spatial loop (parfor) in y direction.
end %end of spatial loop (parfor) in x direction.

outDIFF=op_subtractScans(outON,outOFF);

figure
sim_make2DSimPlot(outON_posxy,2.75,3.25);
figure
sim_make2DSimPlot(outOFF_posxy,2.75,3.25);
figure
sim_make2DSimPlot(outDIFF_posxy,2.75,3.25);


function [lf] = ft_compute_leadfield(dippos, sens, headmodel, varargin)

% FT_COMPUTE_LEADFIELD computes a forward solution for a dipole in a a volume
% conductor model. The forward solution is expressed as the leadfield
% matrix (Nchan*3), where each column corresponds with the potential or field
% distributions on all sensors for one of the x,y,z-orientations of the
% dipole.
%
% Use as
%   [lf] = ft_compute_leadfield(dippos, sens, headmodel, ...)
% with input arguments
%   dippos    = position dipole (1*3 or Ndip*3)
%   sens      = structure with gradiometer or electrode definition
%   headmodel = structure with volume conductor definition
%
% The headmodel represents a volume conductor model, its contents
% depend on the type of model. The sens structure represents a sensor
% array, i.e. EEG electrodes or MEG gradiometers.
%
% It is possible to compute a simultaneous forward solution for EEG and MEG
% by specifying sens and grad as two cell-arrays, e.g.
%   sens       = {senseeg, sensmeg}
%   headmodel  = {voleeg,  volmeg}
% This results in the computation of the leadfield of the first element of
% sens and headmodel, followed by the second, etc. The leadfields of the
% different imaging modalities are subsequently concatenated.
%
% Additional input arguments can be specified as key-value pairs, supported
% optional arguments are
%   'reducerank'      = 'no' or number
%   'normalize'       = 'no', 'yes' or 'column'
%   'normalizeparam'  = parameter for depth normalization (default = 0.5)
%   'weight'          = number or 1xN vector, weight for each dipole position to compensate for the size of the corresponding patch (default = 1)
%   'backproject'     = 'yes' (default) or 'no', in the case of a rank reduction this parameter determines whether the result will be backprojected onto the original subspace
%
% The leadfield weight may be used to specify a (normalized)
% corresponding surface area for each dipole, e.g. when the dipoles
% represent a folded cortical surface with varying triangle size.
%
% Depending on the specific input arguments for the sensor and volume, this
% function will select the appropriate low-level EEG or MEG forward model.
% The leadfield matrix for EEG will have an average reference over all the
% electrodes.
%
% The supported forward solutions for MEG are
%   infinite homogenous medium
%   single sphere (Cuffin and Cohen, 1977)
%   multiple spheres with one sphere per channel (Huang et al, 1999)
%   realistic single shell using superposition of basis functions (Nolte, 2003)
%   leadfield interpolation using a precomputed sourcemodel
%   boundary element method (BEM)
%
% The supported forward solutions for EEG are
%   infinite homogenous medium
%   infinite halfspace homogenous medium
%   single sphere
%   multiple concentric spheres (up to 4 spheres)
%   leadfield interpolation using a precomputed sourcemodel
%   boundary element method (BEM)
%
% See also FT_PREPARE_VOL_SENS, FT_HEADMODEL_ASA, FT_HEADMODEL_BEMCP,
% FT_HEADMODEL_CONCENTRICSPHERES, FT_HEADMODEL_DIPOLI, FT_HEADMODEL_HALFSPACE,
% FT_HEADMODEL_INFINITE, FT_HEADMODEL_LOCALSPHERES, FT_HEADMODEL_OPENMEEG,
% FT_HEADMODEL_SINGLESHELL, FT_HEADMODEL_SINGLESPHERE,
% FT_HEADMODEL_HALFSPACE

% Copyright (C) 2004-2016, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
%    FieldTrip is free software: you can redistribute it and/or modify
%    it under the terms of the GNU General Public License as published by
%    the Free Software Foundation, either version 3 of the License, or
%    (at your option) any later version.
%
%    FieldTrip is distributed in the hope that it will be useful,
%    but WITHOUT ANY WARRANTY; without even the implied warranty of
%    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
%    GNU General Public License for more details.
%
%    You should have received a copy of the GNU General Public License
%    along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$

if iscell(sens) && iscell(headmodel) && numel(sens)==numel(headmodel)
  % this represents combined EEG, ECoG and/or MEG
  % use recursion to compute all leadfields
  lf = cell(1, numel(sens));
  for i=1:length(sens)
    lf{i} = ft_compute_leadfield(dippos, sens{i}, headmodel{i}, varargin{:});
  end
  lf = cat(1, lf{:});
  return;
end

% get the optional input arguments
reducerank      = ft_getopt(varargin, 'reducerank'); % default is handled below
backproject     = ft_getopt(varargin, 'backproject', 'yes');
normalize       = ft_getopt(varargin, 'normalize' , 'no');
normalizeparam  = ft_getopt(varargin, 'normalizeparam', 0.5);
weight          = ft_getopt(varargin, 'weight');
chanunit        = ft_getopt(varargin, 'chanunit');   % this is something like V, T, or T/m
dipoleunit      = ft_getopt(varargin, 'dipoleunit'); % this is something like nA*m

if any(strcmp(varargin(1:2:end), 'unit'))
  ft_error('the ''unit'' option is not supported any more, please use ''chanunit''');
end
if any(strcmp(varargin(1:2:end), 'units'))
  ft_error('the ''units'' option is not supported any more, please use ''chanunit''');
end

if ~isstruct(sens) && size(sens, 2)==3
  % definition of electrode positions only, restructure it
  sens = struct('elecpos', sens);
end

% ft_prepare_vol_sens should be called prior to ft_compute_leadfield
% to ensure that the sens and headmodel are up to date, since the backward
% compatibility check should not be performed for each dipole location
% sens       = ft_datatype_sens(sens);
% headmodel  = ft_datatype_headmodel(headmodel);

% determine whether it is EEG or MEG
iseeg = ft_senstype(sens, 'eeg');
ismeg = ft_senstype(sens, 'meg');

% determine the default for this option
if isempty(reducerank)
  if iseeg
    reducerank = 'no';    % for EEG
  elseif ismeg && ft_headmodeltype(headmodel, 'infinite')
    reducerank = 'no';    % for MEG with a magnetic dipole, e.g. a HPI coil
  else
    reducerank = 'yes';   % for MEG with a current dipole in a volume conductor
  end
end

% multiple dipoles can be represented either as a 1x(N*3) vector or as a
% as a Nx3 matrix, i.e. [x1 y1 z1 x2 y2 z2] or [x1 y1 z1; x2 y2 z2]
Ndipoles = numel(dippos)/3;
if all(size(dippos)==[1 3*Ndipoles])
  dippos = reshape(dippos, 3, Ndipoles)';
end

if isfield(headmodel, 'unit') && isfield(sens, 'unit') && ~strcmp(headmodel.unit, sens.unit)
  ft_error('inconsistency in the units of the volume conductor and the sensor array');
end

if ismeg && iseeg
  % this is something that could be implemented relatively easily
  ft_error('simultaneous EEG and MEG not supported');

elseif ~ismeg && ~iseeg
  ft_error('the input does not look like EEG, nor like MEG');

elseif ismeg
  switch ft_headmodeltype(headmodel)

    case 'singlesphere'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % MEG single-sphere volume conductor model
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      coilpos = sens.coilpos; % position of each coil
      coilori = sens.coilori; % orientation of each coil

      if isfield(headmodel, 'o')
        % shift dipole and magnetometers to origin of sphere
        dippos  = dippos  - repmat(headmodel.o, Ndipoles, 1);
        coilpos = coilpos - repmat(headmodel.o, size(coilpos, 1), 1);
      end

      if Ndipoles>1
        % loop over multiple dipoles
        lf = zeros(size(coilpos, 1), 3*Ndipoles);
        for i=1:Ndipoles
          lf(:, (3*i-2):(3*i)) = meg_leadfield1(dippos(i, :), coilpos, coilori);
        end
      else
        % only single dipole
        lf = meg_leadfield1(dippos, coilpos, coilori);
      end

      if isfield(sens, 'tra')
        % this appears to be the modern complex gradiometer definition
        % construct the channels from a linear combination of all magnetometers
        lf = sens.tra * lf;
      end

    case 'localspheres'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % MEG multiple overlapping sphere volume conductor model
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      ncoils = length(sens.coilpos);

      if size(headmodel.r, 1)~=ncoils
        ft_error('number of spheres is not equal to the number of coils')
      end

      if size(headmodel.o, 1)~=ncoils
        ft_error('number of spheres is not equal to the number of coils');
      end

      lf = zeros(ncoils, 3*Ndipoles);
      for coil=1:ncoils
        for dip=1:Ndipoles
          % shift dipole and magnetometer coil to origin of sphere
          tmppos  = dippos(dip, :) - headmodel.o(coil, :);
          coilpos = sens.coilpos(coil, :) - headmodel.o(coil, :);
          tmp = meg_leadfield1(tmppos, coilpos, sens.coilori(coil, :));
          lf(coil, (3*dip-2):(3*dip)) = tmp;
        end
      end

      if isfield(sens, 'tra')
        % this appears to be the modern complex gradiometer definition
        % construct the channels from a linear combination of all magnetometers
        lf = sens.tra * lf;
      end

    case 'neuromag'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % use external Neuromag toolbox for forward computation
      % this requires that "megmodel" is initialized, which is done in PREPARE_VOL_SENS
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % compute the forward model for all channels
      % tmp1 = ones(1, Ndipoles);
      % tmp2 = 0.01*dippos'; %convert to cm
      % lf = megfield([tmp2 tmp2 tmp2], [[1 0 0]'*tmp1 [0 1 0]'*tmp1 [0 0 1]'*tmp1]);
      for dip=1:Ndipoles
        R = 0.01*dippos(dip, :)'; % convert from cm to m
        Qx = [1 0 0];
        Qy = [0 1 0];
        Qz = [0 0 1];
        lf(:, (3*(dip-1)+1)) = megfield(R, Qx);
        lf(:, (3*(dip-1)+2)) = megfield(R, Qy);
        lf(:, (3*(dip-1)+3)) = megfield(R, Qz);
      end
      % select only those channels from the forward model that are part of the gradiometer definition
      lf = lf(headmodel.chansel, :);

    case 'singleshell'
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % use code from Guido Nolte for the forward computation
      % this requires that "meg_ini" is initialized, which is done in PREPARE_VOL_SENS
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % the dipole position and orientation should be combined in a single matrix
      % furthermore, here I want to compute the leadfield for each of the
      % orthogonal x/y/z directions
      dippar = zeros(Ndipoles*3, 6);
      for i=1:Ndipoles
        dippar((i-1)*3+1, :) = [headmodel.forwpar.scale*dippos(i, :) 1 0 0]; % single dipole with unit strength, x-orientation
        dippar((i-1)*3+2, :) = [headmodel.forwpar.scale*dippos(i, :) 0 1 0]; % single dipole with unit strength, y-orientation
        dippar((i-1)*3+3, :) = [headmodel.forwpar.scale*dippos(i, :) 0 0 1]; % single dipole with unit strength, z-orientation
      end
      % compute the leadfield for each individual coil
      lf = meg_forward(dippar, headmodel.forwpar);
      % the leadfield is computed for cm units, convert it to the desired units
      lf = lf*headmodel.forwpar.scale^2;
      if isfield(sens, 'tra')
        % compute the leadfield for each gradiometer (linear combination of coils)
        lf = sens.tra * lf;
      end

    case 'openmeeg'
        % OpenMEEG lead field already computed in ft_prepare_leadfield;
        % load here so any post-processing options (e.g. normalization) may
        % be applied
        lf = ft_getopt(varargin, 'lf');

    case {'infinite_magneticdipole', 'infinite'}
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % magnetic dipole instead of electric (current) dipole in an infinite vacuum
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      coilpos = sens.coilpos; % position of each coil
      coilori = sens.coilori; % orientation of each coil

      if Ndipoles>1
        % loop over multiple dipoles
        lf = zeros(size(coilpos, 1), 3*Ndipoles);
        for i=1:Ndipoles
          lf(:, (3*i-2):(3*i)) = magnetic_dipole(dippos(i, :), coilpos, coilori);
        end
      else
        % only single dipole
        lf = magnetic_dipole(dippos, coilpos, coilori);
      end

      if isfield(sens, 'tra')
        % construct the channels from a linear combination of all magnetometer coils
        lf = sens.tra * lf;
      end

    case {'infinite_currentdipole'}
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % current dipole in an infinite homogenous conducting medium
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      coilpos = sens.coilpos; % position of each coil
      coilori = sens.coilori; % orientation of each coil

      if Ndipoles>1
        % loop over multiple dipoles
        lf = zeros(size(coilpos, 1), 3*Ndipoles);
        for i=1:Ndipoles
          lf(:, (3*i-2):(3*i)) = current_dipole(dippos(i, :), coilpos, coilori);
        end
      else
        % only single dipole
        lf = current_dipole(dippos, coilpos, coilori);
      end

      if isfield(sens, 'tra')
        % construct the channels from a linear combination of all magnetometer coils
        lf = sens.tra * lf;
      end

    otherwise
      ft_error('unsupported volume conductor model for MEG');
  end % switch type for MEG

elseif iseeg
  switch ft_headmodeltype(headmodel)

    case 'multisphere'
      % Based on the approximation of the potential due to a single dipole in
      % a multishell sphere by three dipoles in a homogeneous sphere, code
      % contributed by Punita Christopher. Note that this one should not get
      % confused with the MEG localspheres model.

      Nelec    = size(sens.elecpos, 1);
      Nspheres = length(headmodel.r);

      % the center of the spherical volume conduction model does not have
      % to be in the origin, therefore shift the spheres, the electrodes
      % and the dipole
      if isfield(headmodel, 'o')
        center = headmodel.o;
      else
        center = [0 0 0];
      end

      % sort the spheres from the smallest to the largest
      % furthermore, the radius should be one (?)
      [radii, indx] = sort(headmodel.r/max(headmodel.r));
      sigma = headmodel.cond(indx);
      r = (sens.elecpos-repmat(center, Nelec, 1))./max(headmodel.r);
      dippos = dippos./max(headmodel.r);

      if Ndipoles>1
        % loop over multiple dipoles
        lf = zeros(Nelec, 3*Ndipoles);
        for i=1:Ndipoles
          rq = dippos(i, :) - center;
          % compute the potential for each dipole ortientation
          % it would be much more efficient to change the punita function
          q1 = [1 0 0]; lf(:, (3*i-2)) = multisphere(Nspheres, radii, sigma, r, rq, q1);
          q1 = [0 1 0]; lf(:, (3*i-1)) = multisphere(Nspheres, radii, sigma, r, rq, q1);
          q1 = [0 0 1]; lf(:, (3*i )) = multisphere(Nspheres, radii, sigma, r, rq, q1);
        end
      else
        % only single dipole
        lf = zeros(Nelec, 3);
        rq = dippos - center;
        % compute the potential for each dipole ortientation
        % it would be much more efficient to change the punita function
        q1 = [1 0 0] ; lf(:, 1) = multisphere(Nspheres, radii, sigma, r, rq, q1);
        q1 = [0 1 0] ; lf(:, 2) = multisphere(Nspheres, radii, sigma, r, rq, q1);
        q1 = [0 0 1] ; lf(:, 3) = multisphere(Nspheres, radii, sigma, r, rq, q1);
      end

    case {'singlesphere', 'concentricspheres'}
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % EEG spherical volume conductor model
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

      % FIXME, this is not consistent between spherical and BEM
      % sort the spheres from the smallest to the largest
      [headmodel.r, indx] = sort(headmodel.r);
      headmodel.cond = headmodel.cond(indx);

      Nspheres = length(headmodel.cond);
      if length(headmodel.r)~=Nspheres
        ft_error('the number of spheres in the volume conductor model is ambiguous');
      end

      if isfield(headmodel, 'o')
        % shift the origin of the spheres, electrodes and dipole
        sens.elecpos = sens.elecpos - repmat(headmodel.o, size(sens.elecpos, 1), 1);
        dippos = dippos - repmat(headmodel.o, Ndipoles, 1);
      end

      switch Nspheres
        case 1
          funnam = 'eeg_leadfield1';
        case 2
          headmodel.r = [headmodel.r(1) headmodel.r(2) headmodel.r(2) headmodel.r(2)];
          headmodel.cond = [headmodel.cond(1) headmodel.cond(2) headmodel.cond(2) headmodel.cond(2)];
          funnam = 'eeg_leadfield4';
        case 3
          headmodel.r = [headmodel.r(1) headmodel.r(2) headmodel.r(3) headmodel.r(3)];
          headmodel.cond = [headmodel.cond(1) headmodel.cond(2) headmodel.cond(3) headmodel.cond(3)];
          funnam = 'eeg_leadfield4';
        case 4
          headmodel.r = [headmodel.r(1) headmodel.r(2) headmodel.r(3) headmodel.r(4)];
          headmodel.cond = [headmodel.cond(1) headmodel.cond(2) headmodel.cond(3) headmodel.cond(4)];
          funnam = 'eeg_leadfield4';
        otherwise
          ft_error('more than 4 concentric spheres are not supported')
      end

      lf = zeros(size(sens.elecpos, 1), 3*Ndipoles);
      for i=1:Ndipoles
        lf(:, (3*i-2):(3*i)) = feval(funnam, dippos(i, :), sens.elecpos, headmodel);
      end

    case {'bem', 'dipoli', 'asa', 'bemcp'}
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      % EEG boundary element method volume conductor model
      %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
      lf = eeg_leadfieldb(dippos, sens.elecpos, headmodel);

    case 'openmeeg'
        % OpenMEEG lead field already computed in ft_prepare_leadfield;
        % load here so any post-processing options (e.g. normalization) may
        % be applied
        lf = ft_getopt(varargin, 'lf');

    case {'infinite_currentdipole' 'infinite'}
      lf = eeg_infinite_dipole(dippos, sens.elecpos, headmodel);

    case 'halfspace'
      lf = eeg_halfspace_dipole(dippos, sens.elecpos, headmodel);

    case 'infinite_monopole'
      lf = eeg_infinite_monopole(dippos, sens.elecpos, headmodel);

    case 'halfspace_monopole'
      lf = eeg_halfspace_monopole(dippos, sens.elecpos, headmodel);

    case 'slab_monopole'
      lf = eeg_slab_monopole(dippos, sens.elecpos, headmodel);

    case 'simbio'
      ft_hastoolbox('simbio', 1);
      % note that the electrode information is contained in the headmodel (thanks to ft_prepare_vol_sens)
      lf = leadfield_simbio(dippos, headmodel);

    case 'metufem'
      p3 = zeros(Ndipoles * 3, 6);
      for i = 1:Ndipoles
        p3((3*i - 2) : (3 * i), 1:3) = [dippos(i, :); dippos(i, :); dippos(i, :)];
        p3((3*i - 2) : (3 * i), 4:6) = [1 0 0; 0 1 0; 0 0 1];
      end
      lf = metufem('pot', p3', 'interp');

    case 'metubem'
      session = headmodel.session;
      p3 = zeros(Ndipoles * 3, 6);
      for i = 1:Ndipoles
        p3((3*i - 2) : (3 * i), 1:3) = [dippos(i, :); dippos(i, :); dippos(i, :)];
        p3((3*i - 2) : (3 * i), 4:6) = [1 0 0; 0 1 0; 0 0 1];
      end
      [lf, session] = bem_solve_lfm_eeg(session, p3);

    case 'fns'
      % note that the electrode information is contained in the headmodel
      % tolerance = 1e-8;
      lf = leadfield_fns(dippos, headmodel);

    case 'interpolate'
      % note that the electrode information is contained in the headmodel
      lf = leadfield_interpolate(dippos, headmodel);
      % the leadfield is already correctly referenced, i.e. it represents the
      % channel values rather than the electrode values. Prevent that the
      % referencing is done once more.
      sens.tra = speye(length(headmodel.filename));

    otherwise
      ft_error('unsupported volume conductor model for EEG');

  end % switch type for EEG

  % the forward model potential is computed on the electrodes relative to
  % an unknown reference, not on the channels. Therefore the data has to be
  % explicitly referenced here.
  if isfield(sens, 'tra')
    % apply the correct montage to the leadfield
    lf = sens.tra*lf;
  else
    % compute average reference for EEG leadfield
    for i=1:size(lf,2)
      lf(:,i) = lf(:,i) - mean(lf(:,i));
    end
  end

end % iseeg or ismeg

% optionally apply leadfield rank reduction
switch reducerank
  case 'yes'
    reducerank = 2;
  case 'no'
    reducerank = 3;
  otherwise
    % assume that it is specified as a number, keep it like this
end

if reducerank<size(lf,2)
  % decompose the leadfield
  for ii=1:Ndipoles
    tmplfd=lf(:, (3*ii-2):(3*ii));
    [u, s, v] = svd(tmplfd);
    r = diag(s);
    s(:) = 0;
    for j=1:reducerank
      s(j, j) = r(j);
    end

    if istrue(backproject)
      % recompose the leadfield with reduced rank
      lf(:, (3*ii-2):(3*ii)) = u * s * v';
    else
      % if not backprojected, the new leadfield has a different dimension
      if ii==1
        newlf    = zeros(size(lf,1), Ndipoles*reducerank);
        origrank = size(lf,2)./Ndipoles;
      end
      newlf(:, reducerank*(ii-1) + (1:reducerank)) = lf(:, origrank*(ii-1) + (1:origrank))*v(:,1:reducerank);
    end
  end

  if ~istrue(backproject)
    lf = newlf;
  end
  clear newlf;
end

% optionally apply leadfield normalization
switch normalize
  case 'yes'
    for ii=1:Ndipoles
      tmplf = lf(:, (3*ii-2):(3*ii));
      if normalizeparam==0.5
        % normalize the leadfield by the Frobenius norm of the matrix
        % this is the same as below in case normalizeparam is 0.5
        nrm = norm(tmplf, 'fro');
      else
        % normalize the leadfield by sum of squares of the elements of the leadfield matrix to the power "normalizeparam"
        % this is the same as the Frobenius norm if normalizeparam is 0.5
        nrm = sum(tmplf(:).^2)^normalizeparam;
      end
      if nrm>0
        tmplf = tmplf ./ nrm;
      end
      lf(:, (3*ii-2):(3*ii)) = tmplf;
    end
  case 'column'
    % normalize each column of the leadfield by its norm
    for ii=1:Ndipoles
      tmplf = lf(:, (3*ii-2):(3*ii));
      for j=1:size(tmplf, 2)
        nrm = sum(tmplf(:, j).^2)^normalizeparam;
        tmplf(:, j) = tmplf(:, j)./nrm;
      end
      lf(:, (3*ii-2):(3*ii)) = tmplf;
    end
end

% optionally apply a weight to the leadfield for each dipole location
if ~isempty(weight)
  for i=1:Ndipoles
    lf(:, 3*(i-1)+1) = lf(:, 3*(i-1)+1) * weight(i); % the leadfield for the x-direction
    lf(:, 3*(i-1)+2) = lf(:, 3*(i-2)+1) * weight(i); % the leadfield for the y-direction
    lf(:, 3*(i-1)+3) = lf(:, 3*(i-3)+1) * weight(i); % the leadfield for the z-direction
  end
end

if ~isempty(chanunit) || ~isempty(dipoleunit)
  assert(strcmp(headmodel.unit,  'm'), 'unit conversion only possible for SI input units');
  assert(strcmp(sens.unit, 'm'), 'unit conversion only possible for SI input units');
end

if ~isempty(chanunit)
  assert(all(strcmp(sens.chanunit, 'V') | strcmp(sens.chanunit, 'V/m') | strcmp(sens.chanunit, 'T') | strcmp(sens.chanunit, 'T/m')), 'unit conversion only possible for SI input units');
  % compute conversion factor and multiply each row of the matrix
  scale = cellfun(@ft_scalingfactor, sens.chanunit(:), chanunit(:));
  lf = bsxfun(@times, lf, scale(:));
  % prior to this conversion, the units might be  (T/m)/(A*m) for planar gradients or   (V/m)/(A*m) for bipolar EEG
  % after this conversion, the units will be     (T/cm)/(A*m)                      or (uV/mm)/(A*m)
end

if ~isempty(dipoleunit)
  scale = ft_scalingfactor('A*m', dipoleunit); % compue the scaling factor from A*m to the desired dipoleunit
  lf    = lf/scale;                         % the leadfield is expressed in chanunit per dipoleunit, i.e. chanunit/dipoleunit
end


function [D info] = ojw_stereo(images, P, disps, sz, options)
%OJW_STEREO  Global stereo with 2nd-order smoothness prior & occlusion model
%
%   [D info] = ojw_stereo(images, P, disps, sz, options)
%
% Generates a disparity (1/depth) map for the first input image. This
% algorithm implements a "global" stereo algorithm, with asymmetrical
% occlusion modelling, using an alpha-expansion graph cuts style approach,
% but with arbitrary disparity proposals.
%
%IN:
%   images - 1xN cell array of input images, the reference image being
%            images{1}.
%   P - 3x4xN array of projection matrices for the input images, relative
%       to the output image.
%   disps - 1xM list of disparities to sample at.
%   sz - 1x2 vector of output image dimensions: [H W].
%   options - a structure containing the following input parameters:
%       col_thresh - scalar noise parameter for data likelihood.
%       occl_const - scalar occlusion cost.
%       disp_thresh - scalar disparity threshold for smoothness prior.
%       smoothness_kernel - index denoting which smoothness kernel to use.
%                           1: truncated linear; 2: truncated quadratic.
%       lambda_l - scalar smoothness prior weight for cliques crossing
%                  segmentation boundaries.
%       lambda_h - scalar smoothness prior weight for cliques not crossing
%                  segmentation boundaries.
%       seg_params - 1x3 vector of parameters for the mean-shift
%                    over-segmentation of the reference image.
%       visibility - boolean indicating whether to employ the geometrical
%                    visbility contstraint.
%       connect - scalar neighbourhood system of the graph, 4 or 8
%                 connected.
%       max_iters - scalar number of iterations to halt after, if
%                   convergence is not achieved first.
%       converge - scalar percentage decrease in energy per iteration at
%                  which optimization stops.
%       average_over - scalar number of iterations to average over when
%                      checking convergence.
%       contract - scalar number of QPBOP iterations to do.
%       improve - scalar indicating which method to use to label unlabelled
%                 nodes. 0: QPBO-F, 1: QPBOI-F, 2: QPBO-R, 3: QPBO-L,
%                 4: QPBOI-R.
%       independent - boolean indicating whether to use independent, or
%                     merely strongly-connected, regions for improve
%                     methods 2 & 4.
%
%OUT:
%   D - HxW disparity map.
%   info - structure containing other outputs from the algorithm.

% $Id: ojw_stereo.m,v 1.5 2008/11/17 11:27:35 ojw Exp $

% Crude check for a reference image
if max(abs(P([1:6 9])-[1 0 0 0 1 0 1])) > 1e-12
    error('First image must be reference image');
end

% Initialize data arrays
R = images{1}(round(P(8))+(1:sz(1)),round(P(7))+(1:sz(2)),:);
vals.I = images(2:end);
vals.P = permute(P(:,:,2:end), [2 1 3]);
vals.sz = sz;
colors = size(R, 3);
num_in = numel(images);
Rorig = uint8(R);
if colors == 1
    Rorig = repmat(Rorig, [1 1 3]);
end
vals.R = repmat(reshape(single(R), [], colors), [2 1]);
vals.d_min = disps(end);
vals.d_step = disps(1) - vals.d_min;
vals.ndisps = numel(disps);

T = reshape(uint32(1:prod(sz)), sz);
if options.planar
    % Use 2nd order smoothness prior
    SEI = [reshape(T(1:end-2,:), 1, []) reshape(T(:,1:end-2), 1, []); ...
           reshape(T(2:end-1,:), 1, []) reshape(T(:,2:end-1), 1, []); ...
           reshape(T(3:end,:), 1, []) reshape(T(:,3:end), 1, [])];
    if options.connect == 8
        SEI = [SEI [reshape(T(1:end-2,1:end-2), 1, []) reshape(T(3:end,1:end-2), 1, []); ...
                    reshape(T(2:end-1,2:end-1), 1, []) reshape(T(2:end-1,2:end-1), 1, []); ...
                    reshape(T(3:end,3:end), 1, []) reshape(T(1:end-2,3:end), 1, [])]];
    end
else
    % Use 1st order smoothness prior
    SEI = [reshape(T(1:end-1,:), 1, []) reshape(T(:,1:end-1), 1, []); ...
           reshape(T(2:end,:), 1, []) reshape(T(:,2:end), 1, [])];
    if options.connect == 8
        SEI = [SEI [reshape(T(1:end-1,1:end-1), 1, []) reshape(T(2:end,1:end-1), 1, []); ...
                    reshape(T(2:end,2:end), 1, []) reshape(T(1:end-1,2:end), 1, [])]];
    end
end
clear T

% Initialise display
vals.show_output = options.show_output;
if vals.show_output
    vals.show_output = gcf;
    set(0, 'CurrentFigure', vals.show_output);
    subplot('Position', [0 0.5 1/3 0.5]);
    sc(R, [0 255]);
end

% Segment the image using mean shift
info.segment = vgg_segment_ms(Rorig, options.seg_params(1), options.seg_params(2), options.seg_params(3));
% Find smoothness edges which don't cross segmentation boundaries
EW = reshape(~any(diff(int32(info.segment(SEI))), 1), 1, []);
EW = EW * options.lambda_h + ~EW * options.lambda_l;
EW = EW * (num_in / ((options.connect==8) + 1));
EW = reshape(repmat(EW, [4*(1+(options.planar~=0)) 1]), [], 1);

% Set up values for ibr_fuse_depths
vals.visibility = (options.visibility ~= 0) * 1e4;
vals.improve = options.improve;
vals.contract = options.contract;
vals.independent = options.independent;
vals.compress_graph = options.compress_graph;

% Set up our robust kernels
vals.ephoto = @(F) log(2) - log(exp(sum(F .^ 2, 2)*(-1/(options.col_thresh*colors)))+1);
switch options.smoothness_kernel
    case 1
        vals.esmooth = @(F) EW .* min(abs(F), options.disp_thresh);
    case 2
        EW = EW / options.disp_thresh;
        vals.esmooth = @(F) EW .* min(F.^2, options.disp_thresh^2);
    otherwise
        error('Unknown smoothness kernel specified');
end
vals.occl_val = options.occl_const + log(2);
vals.SEI = SEI;
clear T SEI EW Rorig

if nargout > 1
    % Save parameters
    info.params.disp_thresh = options.disp_thresh;
    info.params.col_thresh = options.col_thresh;
    info.params.occl_const = options.occl_const;
    info.params.lambda_l = options.lambda_l;
    info.params.lambda_h = options.lambda_h;
end

if isnumeric(options.proposal_method) && size(options.proposal_method, 1) == 1
    % Use the proposal methods:
    for a = options.proposal_method
        switch a
            case 0
                % Ordered fronto-parallel
                [D info.samedisc_optim] = ojw_stereo_optim(vals, @(n) 3, options);
                info.samedisc_optim.D = D;
            case 1
                % SameUni (random fronto-parallel)
                [D info.sameuni_optim] = ojw_stereo_optim(vals, @(n) 1, options);
                info.sameuni_optim.D = D;
            case 2
                % SegPln (prototypical segment-based stereo proposals)
                [Dproposals info.segpln_gen] = ojw_segpln(images, P, disps, R, options);
                clear R
                Dproposals = @(n) Dproposals(:,:,mod(n-1, size(Dproposals, 3))+1);
                [D info.segpln_optim] = ojw_stereo_optim(vals, Dproposals, options);
                clear Dproposals
                info.segpln_optim.D = D;
            case 3
                % Smooth*
                Dproposals = {info.segpln_optim.D, info.sameuni_optim.D, 2, 2, 2, 2};
                Dproposals = @(n) Dproposals{mod(n-1, 6)+1};
                [D info.smooth_optim] = ojw_stereo_optim(vals, Dproposals, options);
                clear Dproposals
                info.smooth_optim.D = D;
            case 4
                % Smooth
                Dproposals = {D, 2};
                Dproposals = @(n) Dproposals{(n>1)+1};
                [D info.smooth2_optim] = ojw_stereo_optim(vals, Dproposals, options);
                clear Dproposals
                info.smooth2_optim.D = D;
        end
    end
else
    % Input fixed set of proposals
    if isnumeric(options.proposal_method)
        Dproposals = @(n) options.proposal_method(:,:,mod(n-1, size(options.proposal_method, 3))+1);
    else
        Dproposals = options.proposal_method;
    end
    [D info.udprop_optim] = ojw_stereo_optim(vals, Dproposals, options);
    clear Dproposals
    info.udprop_optim.D = D;
end
return


classdef m_45_prms_18p_7s < MARRMoT_model
% Class for hydrologic conceptual model: PRMS

% Copyright (C) 2019, 2021 Wouter J.M. Knoben, Luca Trotter
% This file is part of the Modular Assessment of Rainfall-Runoff Models
% Toolbox (MARRMoT).
% MARRMoT is a free software (GNU GPL v3) and distributed WITHOUT ANY
% WARRANTY. See <https://www.gnu.org/licenses/> for details.

% Model reference
% Leavesley, G. H., R. Lichty, B. Troutman, and L. Saindon (1983),
% Precipitation-Runo Modeling System: User's Manual. U.S. Geological
% Survey, Water-Resources Investigations Report 83-4238, 207
%
% Markstrom, S. L., S. Regan, L. E. Hay, R. J. Viger, R. M. T. Webb, R. A.
% Payn, and J. H. LaFontaine (2015), PRMS-IV, the Precipitation-Runoff
% Modeling System, Version 4. In U.S. Geological Survey Techniques and
% Methods, book 6, chap. B7, 158. doi: http://dx.doi.org/10.3133/tm6B7

    properties
        % model-specific attributes
    end
    methods

        % creator method
        function obj = m_45_prms_18p_7s()
            obj.numStores = 7;                                             % number of model stores
            obj.numFluxes = 25;                                            % number of model fluxes
            obj.numParams = 18;

            obj.JacobPattern  = [1,0,0,0,0,0,0;
                                 0,1,0,0,0,0,0;
                                 1,0,1,0,0,0,0;
                                 1,1,1,1,0,0,0;
                                 1,1,1,1,1,0,0;
                                 1,1,1,1,1,1,0;
                                 1,1,1,1,1,1,1];                           % Jacobian matrix of model store ODEs

            obj.parRanges = [-3,  5;         % tt, Temperature threshold for snowfall and melt [oC]
                              0, 20;         % ddf,  Degree-day factor for snowmelt [mm/oC/d]
                              0,  1;         % alpha, Fraction of rainfall on soil moisture going to interception [-]
                              0,  1;         % beta, Fraction of catchment where rain goes to soil moisture [-]
                              0,  5;         % stor, Maximum interception capcity [mm]
                              0, 50;         % retip, Maximum impervious area storage [mm]
                              0,  1;         % fscn, Fraction of SCX where SCN is located [-]
                              0,  1;         % scx, Maximum contributing fraction area to saturation excess flow [-]
                              0.005, 0.995;  % flz, Fraction of total soil moisture that is the lower zone [-]
                              1, 2000;       % stot, Total soil moisture storage [mm]: REMX+SMAX
                              0, 20;         % cgw, Constant drainage to deep groundwater [mm/d]
                              1, 300;        % resmax, Maximum flow routing reservoir storage (used for scaling only, there is no overflow) [mm]
                              0,  1;         % k1, Groundwater drainage coefficient [d-1]
                              1,  5;         % k2, Groundwater drainage non-linearity [-]
                              0,  1;         % k3, Interflow coefficient 1 [d-1]
                              0,  1;         % k4, Interflow coefficient 2 [mm-1 d-1]
                              0,  1;         % k5, Baseflow coefficient [d-1]
                              0,  1];        % k6, Groundwater sink coefficient [d-1]

            obj.StoreNames = {"S1", "S2" "S3" "S4" "S5" "S6" "S7"};         % Names for the stores
            obj.FluxNames  = {"ps",   "pr",  "pim", "psm", "pby"...
                              "pin",  "ptf", "m",   "mim", "msm"...
                              "sas",  "sro", "inf", "pc",  "excs"...
                              "qres", "sep", "gad", "ras", "bas"...
                              "snk",  "ein", "eim", "ea",  "et"};          % Names for the fluxes

            obj.FluxGroups.Ea = [22 23 24 25];                             % Index or indices of fluxes to add to Actual ET
            obj.FluxGroups.Q  = [11 12 19 20];                             % Index or indices of fluxes to add to Streamflow
            obj.FluxGroups.Sink  = 21;                                     % index of sink fluxes

        end

        % INITialisation function
        function obj = init(obj)
        end

        % MODEL_FUN are the model governing equations in state-space formulation
        function [dS, fluxes] = model_fun(obj, S)
            % parameters
            theta = obj.theta;
            tt      = theta(1);     % Temperature threshold for snowfall and melt [oC]
            ddf     = theta(2);     % Degree-day factor for snowmelt [mm/oC/d]
            alpha   = theta(3);     % Fraction of rainfall on soil moisture going to interception [-]
            beta    = theta(4);     % Fraction of catchment where rain goes to soil moisture [-]
            stor    = theta(5);     % Maximum interception capcity [mm]
            retip   = theta(6);     % Maximum impervious area storage [mm]
            fscn    = theta(7);     % Fraction of SCX where SCN is located [-]
            scx     = theta(8);     % Maximum contributing fraction area to saturation excess flow [-]
            scn     = fscn*scx;     % Minimum contributing fraction area to saturation excess flow [-]
            flz     = theta(9);     % Fraction of total soil moisture that is the lower zone [-]
            stot    = theta(10);    % Total soil moisture storage [mm]: REMX+SMAX
            remx    = (1-flz)*stot; % Maximum upper soil moisture storage [mm]
            smax    = flz*stot;     % Maximum lower soil moisture storage [mm]
            cgw     = theta(11);    % Constant drainage to deep groundwater [mm/d]
            resmax  = theta(12);    % Maximum flow routing reservoir storage (used for scaling only, there is no overflow) [mm]
            k1      = theta(13);    % Groundwater drainage coefficient [d-1]
            k2      = theta(14);    % Groundwater drainage non-linearity [-]
            k3      = theta(15);    % Interflow coefficient 1 [d-1]
            k4      = theta(16);    % Interflow coefficient 2 [mm-1 d-1]
            k5      = theta(17);    % Baseflow coefficient [d-1]
            k6      = theta(18);    % Groundwater sink coefficient [d-1]

            % delta_t
            delta_t = obj.delta_t;

            % stores
            S1 = S(1);
            S2 = S(2);
            S3 = S(3);
            S4 = S(4);
            S5 = S(5);
            S6 = S(6);
            S7 = S(7);

            % climate input
            t = obj.t;                             % this time step
            climate_in = obj.input_climate(t,:);   % climate at this step
            P  = climate_in(1);
            Ep = climate_in(2);
            T  = climate_in(3);

            % fluxes functions
            flux_ps  = snowfall_1(P,T,tt);
            flux_pr  = rainfall_1(P,T,tt);
            flux_pim = split_1(1-beta,flux_pr);
            flux_psm = split_1(beta,flux_pr);
            flux_pby = split_1(1-alpha,flux_psm);
            flux_pin = split_1(alpha,flux_psm);
            flux_ptf = interception_1(flux_pin,S2,stor);
            flux_m   = melt_1(ddf,tt,T,S1,delta_t);
            flux_mim = split_1(1-beta,flux_m);
            flux_msm = split_1(beta,flux_m);
            flux_sas = saturation_1(flux_pim+flux_mim,S3,retip);
            flux_sro = saturation_8(scn,scx,S4,remx,flux_msm+flux_ptf+flux_pby);
            flux_inf = effective_1(flux_msm+flux_ptf+flux_pby,flux_sro);
            flux_pc  = saturation_1(flux_inf,S4,remx);
            flux_excs= saturation_1(flux_pc,S5,smax);
            flux_sep = recharge_7(cgw,flux_excs);
            flux_qres= effective_1(flux_excs,flux_sep);
            flux_gad = recharge_2(k2,S6,resmax,k1);
            flux_ras = interflow_4(k3,k4,S6);
            flux_bas = baseflow_1(k5,S7);
            flux_snk = baseflow_1(k6,S7);
            flux_ein = evap_1(S2,beta*Ep,delta_t);
            flux_eim = evap_1(S3,(1-beta)*Ep,delta_t);
            flux_ea  = evap_7(S4,remx,Ep-flux_ein-flux_eim,delta_t);
            flux_et  = evap_15(Ep-flux_ein-flux_eim-flux_ea,S5,smax,S4,Ep-flux_ein-flux_eim,delta_t);

            % stores ODEs
            dS1 = flux_ps  - flux_m;
            dS2 = flux_pin - flux_ein - flux_ptf;
            dS3 = flux_pim + flux_mim - flux_eim - flux_sas;
            dS4 = flux_inf - flux_ea  - flux_pc;
            dS5 = flux_pc  - flux_et  - flux_excs;
            dS6 = flux_qres- flux_gad - flux_ras;
            dS7 = flux_sep + flux_gad - flux_bas - flux_snk;

            % outputs
            dS = [dS1 dS2 dS3 dS4 dS5 dS6 dS7];
            fluxes = [flux_ps,   flux_pr,  flux_pim, flux_psm, flux_pby...
                      flux_pin,  flux_ptf, flux_m,   flux_mim, flux_msm...
                      flux_sas,  flux_sro, flux_inf, flux_pc,  flux_excs...
                      flux_qres, flux_sep, flux_gad, flux_ras, flux_bas...
                      flux_snk,  flux_ein, flux_eim, flux_ea,  flux_et];
        end

        % STEP runs at the end of every timestep.
        function obj = step(obj)
        end
    end
end